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 helperconst posts = await wp('wp/v2/posts')if (posts && posts.length) {// return your desired propsreturn { posts }}throw new Error('No posts found')} catch (err) {// fallback props if necessaryreturn { 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 queriesexport 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].jsxfunction 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].jsxfunction 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].tsxfunction 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] formatpaths: posts.map(({ slug }) => ({ params: { slug } }))}}// fallback to empty path if no posts foundreturn {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
# npmnpm i next@canary# yarnyarn 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.
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.
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.
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
# npmnpm i next@canary# yarnyarn 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.