Supercharging your Next.js site with getStaticProps and getStaticPaths

Supercharging your Next.js site with getStaticProps and getStaticPaths

Or, how I learned to stop worrying and live on the bleeding edge for once.

I've been diving deep into Next.js lately. It has grown from just merely a framework for server-rendered React apps, to a full-fledged framework for building any React-based full-stack apps, be it server-rendered, statically-generated, or a combination of both. And with the upcoming changes, we will see some incredible new features to unlock the full potential of both server rendering and static generation.

In this post, we'll take a quick look at these new features, and see how well it compares to all previous versions of Next.js.

The beginning: getInitialProps

The power behind Next.js always lies behind the getInitialProps API. Whilst other frameworks decide to go the extra mile by including complicated boilerplates inside of the framework itself just to pull content, Next.js provides a simple intuitive API that doesn't care how you prerender content into your app.

In summary, getInitialProps is how you fetch content into a certain Next.js page before it is rendered.

jsx
import * as React from 'react'
function IndexPage({ posts }) {
// render page content
}
// Gets props during prerendering (server-side or static)
IndexPage.getInitialProps = async (ctx) => {
try {
// fetch content (e.g. using a WordPress API helper
const posts = await wp('wp/v2/posts')
if (posts && posts.length) {
// return your desired props
return { posts }
}
throw new Error('No posts found')
} catch (err) {
// fallback props if necessary
return { errors }
}
}
export default IndexPage

It's so freaking simple. You can always trust the good folks at ZEIT to design simple, but intuitive APIs on every library they build.

The problem? It's hybrid. This means that despite the initial load of a site being pre-rendered, any subsequent route changes in your app will run another client-side fetch in order to get the new content. For dynamic content this is fine, but for static sites pulling static content through a headless CMS API, this can be a bit wasteful on resources.

And as a knock-on effect on how this API works, generating static pages also requires a bit of boilerplate using the exportPathMap option in your Next.js config file.

But fortunately, with changes coming to Next.js, everything's going to be much easier.

Improved static-site generation

About a few months back, the team behind Next.js published an RFC detailing how they're trying to improve static-site generation (SSG) within Next.js. This introduces several new Next.js lifecyle methods, including getStaticProps and getStaticPaths.

First things first, getStaticProps will render any content passed through it statically at build time. This fits well into the JAMstack workflow, since all content is generated at build time. You can do any types of content fetching in this lifecycle, just as you would with getInitialProps and it will still work as it has been. The difference? Your content will now be pre-generated by Next.js as a static JSON, and any subsequent client-side routing will fetch from these files.

jsx
// pages/index.jsx
// getStaticProps is only called server-side
// In theory you could do direct database queries
export async function getStaticProps(context) {
return {
// Unlike `getInitialProps` the props are returned under a props key
// The reasoning behind this is that there's potentially more options
// that will be introduced in the future.
// For example to allow you to further control behavior per-page.
props: {}
}
}

Note that we pass in all of the props inside a props key. This is to make room for any additional configurations that may be added in the future.

To alleviate the burden of exportPathMap, the getStaticPaths lifecycle is also introduced. This allows you to return a list of pages to render with certain parameters. This will then be used by Next.js to prerender any static pages from dynamic routes.

jsx
// pages/blog/[slug].jsx
function BlogPage() {
// render posts content here
}
// `getStaticPaths` allows the user to return a list of parameters to
// render to HTML at build time.
export async function getStaticPaths() {
return {
paths: [
// this renders /blog/hello-world to HTML at build time
{ params: { slug: 'hello-world' } }
]
}
}
export default BlogPage

Note that we return all the path parameters inside a paths key. Just like in getStaticProps this is to make room for any additional configurations that may be added in the future. For example, we can add in fallback: false to disable the default fallback behaviour within Next.js, which were described in the RFC document.

This also works with catch-all dynamic routes, for example:

jsx
// pages/blog/[...slug].jsx
function BlogPage() {
// render posts content here
}
// `getStaticPaths` allows the user to return a list of parameters to
// render to HTML at build time.
export async function getStaticPaths() {
return {
paths: [
// this renders /blog/2020/03/hello-world to HTML at build time
{ params: { slug: ['2020', '03', 'hello-world'] } }
]
}
}
export default BlogPage

So, how do we hook it up with, say, the WordPress API? Here's a quick example:

jsx
// pages/blog/[slug].tsx
function BlogPage() {
// render posts content here
}
export async function getStaticPaths() {
// fetch content (e.g. using a WordPress API helper...
const posts = await wp('wp/v2/posts')
// then return all of the rendered paths here:
if (posts && posts.length) {
return {
// put the slugs in with /blog/[slug] format
paths: posts.map(({ slug }) => ({ params: { slug } }))
}
}
// fallback to empty path if no posts found
return {
paths: []
}
}
export default BlogPage

If you still want the full capabilities of dynamic content, you can also look into the getServerSideProps lifecycle. This is beyond the scope of this post, though you can still look into the full RFC document for its implementation detail.

These new features have been implemented in the canary version of Next.js for everyone to try. You can install the canary version of Next.js by running the following commands:

bash
# npm
npm i next@canary
# yarn
yarn add next@canary

The results

Over the past week, I've been helping the team at Kawal COVID-19 to build their website. We're a group of volunteers from many backgrounds (including, but not limited to, medical practicioners, technologists, and data scientists), helping to provide accurate and factual information regarding the recent outbreak of the coronavirus COVID-19, which has hit several countries, including Indonesia. Our channels so far include Facebook, Twitter, and as of recently, our website.

We initiated the project the day before the first confirmed case of COVID-19 hit Indonesia, so we had to move fast. Everything from the architectural decision, to development and deployment of our website took 3-4 days.

The architecture we decided to go with is a statically-generated Next.js site which pulls content from a WordPress backend. If that sounded familiar to you, I tried a similar architecture where I work. The difference is we're running a new version of Next.js, therefore we can utilise new features like dynamic routes.

Going static helps us unlock the true possibilities of a JAMstack site, and improves the speed, stability, and security of our website from back to front. However, over the past couple days, we started to notice bottlenecks. As users start to roll in once we announced the website's launch, we're starting to see increased response time in our WordPress backend.

before.jpg

Since getInitialProps is hybrid, and only the first load of the page is pre-rendered, every client-side fetching triggered by route changes includes additional roundtrip into our WordPress backend. This causes WordPress REST API response times to increase as more people from across the country access our site.

So we had to figure out another way to keep API roundtrips to a minimum. Fortunately, I remembered about the upcoming SSG improvements in Next.js, so I made the call to switch to the canary version and implement these new features.

It didn't take a long time to migrate everything from getInitialProps to getStaticProps. However, converting from exportPathMap to getStaticPaths might have to depend on the complexity of your routes, and we're lucky to have made the call to switch when there's not much content yet.

The result? I'll let the following GIF speak for itself.

comparison.gif

The first load remains as fast as it used to be, but every subsequent route changes now loads from pre-generated data from our local build, therefore reducing API roundtrip and making the content load much faster.

This also helps us reduces dependency on our WordPress API to zero, therefore reducing its load resulting from any API calls.

after.jpg

Give it a try!

As mentioned earlier, you can try these features before they are included in the next public release by installing the Next.js canary build. You can install the canary build of Next.js here:

bash
# npm
npm i next@canary
# yarn
yarn add next@canary

Currently, the docs for it resides only in the RFC document, but the ZEIT team will publish the necessary docs for it once it's ready.

Resi Respati

Web developer based in Jakarta, Indonesia.