How I finally made sense of Next.js' getStaticProps
and getStaticPaths
functions to statically generate pages for a blog
Click here for a quick summary.
Background
I recently started buidling a blog for my partner. Based on the features we’ve decided to include, I’ve identified this project as a great opportunity to use Next.js for the first time. Even though the the blog has quite a bit of progress to go before it’s finished, it’s already been a good learning experience.
What’s Next.js?
Next.js is a React application framework (or distro, if you prefer) which was released in 2016 and sits nicely in the Jamstack world. It provides abstractions for building websites with React* that can use pre-rendered with SSG, SSR, or a mix of the two. Like React itself, Next.js is unopinionated about styling or state management.
The momentum behind Next.js on a community-building level is super impressive. For instance, Vercel, the company which owns and primarily develops Next.js, has worked with the Google Chrome team on image optimization and has hired the author of webpack. Suffice to say, Next.js is a tool worth keeping an eye out for—and in my opinion, one worth learning.
I’ve been following developers who make/use Next.js on Twitter for about a year. It’s very cool to see that the framework can create sophisticated sites while maintaining an excellent developer experience with features like fast refresh and automatic code splitting. In addition to its production use in sites for big names including GitHub, Nike, and Square Enix; here are some Next.js sites that stand out to me:
- GeoGuessr, a game where you guess where in the world you are, based on a random Google Street View
- istheshipstillstuck.com, a live-updating static page to show the status of the March 2021 Suez Canal obstruction, which scaled to handle traffic in the millions
- Pinecraft, a digital lathe app by a studio which plants a real tree for every tree you carve/paint
- next-adventure, a choose-your-own-adventure game with dynamic routing based on your story choices
Each Next.js page is a React component which lives in its own file in the project’s pages
directory. For example, an about page might look like this:
This concept of pages as components allows you to create new pages with little effort while leveraging React’s flexibility and scalability. It also allows you to automatically generate pages with dynamic routes.
What’s dynamic routing?
Dynamic routing refers to generating routes (URLs) to serve individual pages based on data which is subject to change.
Let’s say we’re making a blog using any old static site generator. The following data for our blog posts lives on a CMS which is queried by our static site generator at build time:
After our blog is built, we want to be able to access pages for each of these posts based on their slugs, i.e., https://our.blog/post/my-first-post and https://our.blog/post/another-post. We also want to do this without having to manually write a file like post/my-first-post/index.html
—a process which would get very tedious with dozens or hundreds of posts. This is where static site generators shine!
Let’s say we add a new post:
With dynamic routing, we want to automatically create a new page, https://our.blog/post/the-newest-post, when this post is added.
How does Next.js handle dynamic routing?
In Next.js, generating blog post pages based on JSON data is done by creating a page with a special filename, and implementing two functions: getStaticProps
and getStaticPaths
. These functions are the secret sauce behind how Next.js statically generates a bunch of pages.
I first tried to learn about them by reading Next.js’ docs. I didn’t really understand what I read, because I didn’t have any live code samples to examine and tinker with.* I thought, maybe I can just make and break stuff until I see how these functions work together.
Let’s make a basic blog
I got started on this project by following along with Chris Sev’s tutorial on how to make a blog with Strapi and Next.js. Over the course of an hour, Chris demonstrates how to set up a database and API endpoints to store blog posts with the Strapi CMS, and how to retrieve and render those blog posts with Next.js.
For this toy example, let’s make the following assumptions:
- The body of each blog post is just a single paragraph. It’s fairly straightforward to store post content as Markdown and transform the Markdown into HTML, but that’s outside of the scope of this blog post.
- I’m modeling the example API endpoints/responses off of the ones provided by Strapi.
- Posts are served at the
post/
subdirectory.
After following along with Chris’ tutorial, I had a homepage plus a page for each post, like this:
Let’s see how those blog post pages are implemented.*
Blog post page implementation
As I stated earlier, dynamically rendering blog posts is done by implementing the getStaticProps
and getStaticPaths
functions in a page file with a special name. If you’ll bear with me, I’ll work backwards and wait to give definitions for these functions until we see how they interact with each other.
This is more or less what the code for my blog post page looked like after completing that tutorial:
There’s a lot going on here, so let’s break it down:
Page component definition
Like the About
component above, Post
is a function which returns a page. The text content of this page is determined by the post
property of the object that Post
receives as a prop.
Where does that prop come from?
getStaticProps
getStaticProps
returns an object with a props
key, whose value becomes the prop object for the Post
component.
In this case, getStaticProps
makes an async API request to grab a post with a particular slug. The slug we want is determined by the params
property of the object that getStaticProps
receives as an argument.
Likewise, that argument object containing params
has to come from somewhere too.
getStaticPaths
That somewhere is getStaticPaths
, which returns an object with two properties:
paths
- An array of objects which each have aparams
keyfallback
- A boolean that indicates whether a fallback page has been defined. Since it’s set tofalse
here, trying to navigate to any page that’s not included inpaths
(for example, https://our.blog/post/blargen-fezibble-nohip) will result in a 404 response.
Note that in this example, the shape and size of paths
correspond to the list of all blog posts that our API gives us.
Filename
Last but not least, it’s crucial that:
- The location of this file corresponds to what we want the page’s URL to look like, i.e., it’s in the correct directory in our project. Since we want to serve posts from the
post/
subdirectory of the built site, this file must be placed in a project folder that’s also calledpost/
. - The file’s name is the page’s dynamic route parameter (in this case,
slug
, which is defined in each object in thepaths
array ofgetStaticPaths
), wrapped in square brackets.
Adding categories
Now we’re getting to why I decided to write this blog post.
I encountered a fun learning opportunity in the blog I’m making because it’s really going to be several blogs in one, separated by category. These categories are kind of like tags commonly seen in blogs, except that each post belongs to exactly one category.
In Strapi, this is done by creating a new collection for categories, then setting up a one-to-many mapping between categories and posts.
With our three blog posts, let’s say one category contains two posts, and the other category contains the remaining post. So this is what the category collection looks like:
Now, when we fetch blog posts, the data we receive looks like this:
Category page implementation
This is where the docs and tutorials ended. Seeing as there’s no page in the Next.js docs about adding category pages for blog posts, I winged creating a new file to dynamically generate category pages, by duplicating and modifying post/[slug].js
.
Writing the new page for categories wasn’t too hard, but naming it was. Initially I had posts served from the root directory, like https://our.blog/my-first-post. I wanted to serve categories from the root directory instead, like https://our.blog/gold. This caused a conflict, since the slug property for both posts and categories is called slug
—you can’t have two files in the same directory named [slug].js
! This is why I moved posts to the post/
subdirectory. Plus I figured that having /post/
in a post’s URL would make that URL easier to read when shared.
Also, Next.js will tell you if the statically generated page’s filename isn’t right. If we change the filename to [wumbo].js
, we get this error in the browser:
The bracketed filename determines what property Next.js looks for when statically generating pages. That is, this file’s pages will fail to build if the objects in the paths
array returned by getStaticPaths
do not contain the bracketed word.
Next.js’ error messages made these errors clear and quick to diagnose. After working around them, I was pleasantly surprised to arrive at this implementation of category pages in what seemed like no time at all:
Seeing that this thing I hacked together just worked put a huge grin on my face. It was like magic.
Summary
After writing the above implementation, I ended up with this understanding of Next.js’ SSG functions:
What does getStaticProps
do?
getStaticProps
defines the props that a statically generated page will receive.
What does getStaticPaths
do?
getStaticPaths
defines which and how many pages will be statically generated at build time.
I was delighted to reread Next.js’ docs at this point and find that what they said about getStaticProps
and getStaticPaths
actually made sense to me, because it matched my own newly acquired comprehension.
The big picture
This is a rather specific example of dynamic routing. And React-based static site generation is arguably overkill for a simple blog. But I think I can use lessons from this to apply to other situations. Maybe you can too!
At first, I didn’t quite get how Next.js could fit into ecommerce sites. After figuring out this whole category pages thing, it clicked and I thought, of course Next.js can do ecommerce. Easily. For example, you can can define a route, product/[id].js
, to generate a page for every single product in your catalog database. It’s amazing to me that process can be orchestrated in JavaScript.
So Next.js’ secret sauce is not dynamic routing itself, but great API design. I’ve heard that one of Next.js’ best features is how its conventions allow developers who are unfamiliar with it to quickly become productive with it.
Me: “I really hope Next routing works like [this]”
Next Docs, in clear language: “We work exactly like [this]”
Such a pleasure using this at work omg 😬
— Adam Rackis (@AdamRackis)
May 27, 2021
Also, this was a learning moment that made me feel knowledgeable and capable about JavaScript and React. As an early-career developer with plenty of impostor syndrome, that’s kind of a big deal. It reminded me of why I enjoy programming in the first place!
I love that tipping point where you have finally learned enough of the basic concepts of a new language or framework and you can actually apply to them a project of your own creation.
— Kelly Vaughn🐞 (@kvlly) June 9, 2021