Static Site Generation with Next.js and TypeScript (Part V) - Build Time Access Tokens and Exporting Static HTML

Responses (0)

Clap
2|0|

Disclaimer - Please read the fourth part of this blog post here before proceeding. It demonstrates how to statically generate pages with dynamic routes using the getStaticPath() function. If you just want to jump straight into this tutorial, then clone the project repository and install the dependencies.

In the previous part of this tutorial series, we encountered a big problem: each getStaticProps() and getStaticPath() function required us to obtain an access token before being able to request any data from the Petfinder API. This meant that anytime we built the Next.js application for production, we had to obtain several access tokens for the Petfinder API:

  • One in getStaticProps() in pages/index.tsx to retrieve a list of pet animal types.

  • One in getStaticProps() in pages/types/[type].tsx to retrieve a list of pet animal breeds and a list of recently adopted pets.

  • One in getStaticPath() in pages/types/[type].tsx to generate paths (/types/:type).

If we were to add more statically generated pages to the Next.js application that depend on data from the Petfinder API, then we would continue to accumulate more access tokens that are scattered throughout the Next.js application.

Unfortunately, the Next.js's custom <App /> component does not support data fetching functions like getStaticProps() and getStaticPath(). This means we don't have the option of obtaining a single access token, fetching all of the necessary data (e.g., a list of pet animal types and lists of recently adopted pets) in the getStaticProps() function of the custom <App /> component and passing the data as props to every page component at build time.

One way to make the access token globally available to all page components at build time is to inject it as an environment variable.

Below, I'm going to show you how to build a Next.js application with a single access token. We will obtain an access token from the Petfinder API via the cURL CLI tool, set it to an environment variable named PETFINDER_ACCESS_TOKEN and execute the npm run build command with this environment variable.

Then, I'm going to show you how to export the Next.js application to static HTML. This allows us to deploy and serve the Next.js application on fast, static hosting solutions like Cloudflare Pages and GitHub Pages, all without ever having to spin up a Node.js server.

Installation and Setup#

To get started, clone the project repository and install the dependencies.

If you're coming from the fourth part of this tutorial series, then you can continue from where the fourth part left off.

Within the project directory, create a Makefile:

With a Makefile, we can define rules that each run a set of commands. Rules are similar, purpose-wise, to npm scripts in package.json files. Each rule consists of, at a minimum, a target and a command. The target is the name of the rule, and the command is the actual command to execute.

Inside of the Makefile, add two rules: dev and build.

Note: Each indentation should be a tab that's four spaces wide. Otherwise, you may encounter the error *** missing separator. Stop..

Here, invoking the make command with the dev rule as the target (make dev) runs npm run dev, and invoking the make command with the build rule as the target (make build) runs npm run build.

The Makefile allows us to store the result of shell commands into variables.

For example, suppose we add the following line to the top of the Makefile.

In the above example, we set the variable PETFINDER_ACCESS_TOKEN to the output of the echo command, which is the string "abcdef." The shell function performs command expansion, which means taking a command as an argument, running the command and returning the command's output. Once the shell function returns the command's output, we assign this output to the simply expanded variable PETFINDER_ACCESS_TOKEN.

Anytime we reference a simply expanded variable, whose value is assigned with :=, the variable gets evaluated once (at the time of assignment) and procedurally, much like what you would expect in a typical, imperative programming language like JavaScript. So if we were to reference the variable's value with $(), then the value will just be the string "abcdef."

(Makefile)

GNU make comes with another "flavor" of variable, recursively expanded variable, which evaluates a variable's value completely different than what most developers are used to. It's out of the scope of this tutorial, but you can read more about them here.

If you print the value of the PETFINDER_ACCESS_TOKEN environment variable in the <HomePage /> page component's getStaticProps() function, then you will see the value "abcdef" logged in the terminal when you...

  • Run the make dev command and visit http://localhost:3000 in the browser.

  • Run the make build command.

(pages/index.tsx)

Note: The PETFINDER_ACCESS_TOKEN environment variable's name will not be prefixed with NEXT_PUBLIC_.

Notice that the command (along with the value of environment variables passed to it), PETFINDER_ACCESS_TOKEN=abcdef npm run dev gets logged to the terminal. To tell make to suppress this echoing, you can prepend @ to lines that you want suppressed. For simple commands like echo, you can suppress the echoing by prepending @ to the command itself, like so:

However, because the command we want suppressed begins with an environment variable, we wrap the entire command in @(), like so:

(Makefile)

When you re-run the make dev command, PETFINDER_ACCESS_TOKEN=abcdef npm run dev no longer gets logged to the terminal.

Obtaining an Access Token with cURL#

To obtain an access token from the Petfinder API via cURL, you must send a request to the POST https://api.petfinder.com/v2/oauth2/token endpoint with the grant type (grant_type), client ID (client_id) and client secret (client_secret).

This data can be passed by specifying a single -d option (short for --data) as a concatenated string of key=value pairs (delimited with an ampersand) or multiple -d options, providing a key=value pair for each one.

Here's what using the single -d option looks like:

And here's what using the multiple -d options looks like:

Here, we will use the single -d option.

When you run the cURL command, you will see that the access token is returned in stringified JSON.

We can pluck the access token from this stringified JSON by piping the output of the cURL command (the stringified JSON) to a sed command.

On Unix-based machines, the sed command performs many types of text processing tasks, from search to substitution. The -E option (short for the --regexp-extended option) tells the sed command to find a substring based on an extended regular expression, which requires special characters to be escaped if you want to match for them as literal characters.

When you run the cURL command with the piped sed command, you will see that only the access token is returned.

Setting the Access Token to an Environment Variable#

Within the Makefile, let's set the PETFINDER_ACCESS_TOKEN variable to the cURL command with the piped sed command, like so:

To pull the NEXT_PUBLIC_PETFINDER_CLIENT_ID, NEXT_PUBLIC_PETFINDER_CLIENT_SECRET and NEXT_PUBLIC_PETFINDER_API_URL environment variables from the .env file, we can use the include directive to pause reading from the current Makefile and read from the .env file before resuming.

Then, with the export directive, we can export the environment variables that were read from the .env file.

(Makefile)

When you re-run the make dev command and visit http://localhost:3000/ in a browser, you will find that the access token is immediately available to the <HomePage /> page component's getStaticProps() function.

Now we can remove all instances of fetching an access token within getStaticProps() and getStaticPath() functions from the Next.js application, and pass the PETFINDER_ACCESS_TOKEN environment variable to Authorization header of any request that's sent to the Petfinder API.

Also, you can now remove the console.log({ PETFINDER_ACCESS_TOKEN }) line from the <HomePage /> page component's getStaticProps() function.

(pages/index.tsx)

(pages/types/[type].tsx)

By passing the access token as an environment variable to the Next.js application, the Next.js application now makes ten fewer requests.

Like with any solution, this approach does come with a caveat. Since an access token from the Petfinder API expires one hour from the time it was issued, a caveat of this approach is that you will have to reset the development server every hour to refresh the access token.

Exporting the Next.js Application to Static HTML#

To export the Next.js application to static HTML, we must add an export npm script to the package.json file that:

  1. Builds an optimized, production-ready version of the Next.js application (next build).

  2. Using the build, generates an HTML file for each page of the Next.js application and outputs it to an out directory (next export).

(package.json)

Add a new rule named export_html to the Makefile that runs the export npm script with the PETFINDER_ACCESS_TOKEN environment variable:

(Makefile)

Note: Remember, export is already a GNU make directive. Therefore, you cannot name the rule export.

When you run the make export_html command, you will find that the Next.js application could not be exported as static HTML because it makes use of the image optimization feature, which requires a Node.js server.

To resolve this problem, we need to set experimental.images.unoptimized to true in the Next.js configuration to disable the image optimization feature. Specifically, we only want to disable this feature when the NEXT_EXPORT environment variable is present. The NEXT_EXPORT environment variable will only be set when exporting the Next.js application to static HTML.

(Makefile)

(next.config.js)

When you re-run the make export_html command, the Next.js application will be exported to static HTML.

Inside the project directory, you will find the exported HTML in an out directory:

To test the static pages, you can spin up a standalone HTTP server that serves the contents of the out directory:

If you visit http://localhost:8080/types/horse in a browser and disable JavaScript, then you will see that this page has already been pre-rendered at build time.

Next Steps#

If you find yourself stuck at any point during this tutorial, then feel free to check out the project's repository for this part of the tutorial here.

Proceed to the next part of this tutorial series to dive into building interactive, client-side features for the Next.js application.

If you want to learn more advanced techniques with TypeScript, React and Next.js, then check out our Fullstack React with TypeScript Masterclass:


Sources#

Clap
2|0