Using Next.js as a Static Site Generator for Netlify

Next.js is an incredible React framework, being the premier Server-side Rendering solution for React which is absolutely critical for performance and SEO of non-static React apps. It also comes with first-class TypeScript Support, Hook-based Routing, and a fantastic scoped CSS-in-JS solution.

Three Export Modes

One misperception among the community (certainly something I used to have) is that it has other export modes. Next.js can build to a standard Node.js app with next build, however, there are actually three export modes:

  • Serverful Node.js: created with the next build command
  • Serverless Node.js: with a serverless target in next.config.js and the next build command
  • Static HTML: created with the next export command

While Zeit’s now v1 is the best host for Serverful Next.js, and now v2 is the best host for Serverless Next.js, the Static HTML mode is a diamond in the rough - literally exporting the app to a folder of static assets (both HTML/JS/CSS bundles as well as prefetched data) that can be served from as simple a utility as Zeit’s serve CLI!

Static is Universal

What is great about a folder you can statically export is that it is the one configuration that literally everybody supports. You can host it on a Raspberry Pi if you wish, or on an AWS S3 Bucket, or on a Node server, or heck you can print it out and have a dance crew do an interpretive dance of the code if you want (results may vary)! And if you ever outgrow your chosen hosting solution, you can pick up and move to the next one that suits you best!

This is the heart of the JAMstack idea - reducing vendor lock-in, promoting fast and secure defaults (because there is nothing as fast as serving a static file with all pre-computation already done). Even concerns about programming environment, like operating system and Node.js version, melt away because it literally doesn’t matter, therefore you never need to debug them.

The best part of all, the only configuration to do gets no more complex than figuring out what folder to upload. It harkens back to the drag-and-drop, FTP days that have been with us since the dawn of Web Development.

Deploying Next.js Static Export to Netlify

For any basic Next.js app (you can grab one here if you don’t have one), all you need to do to turn it into a static export app is run:

next build
next export

This builds your site to an out folder, which you can host locally by running npx serve out.

You can then use the Netlify CLI to deploy this as a static site:

## in case you need to download the CLI and log in
# npm i -g netlify-cli
# netlify login
netlify init
## or alternatively
netlify deploy

The only thing you need to know to configure the app is your “build command” and “publish directory”. You can answer these in the CLI prompts, or skip that by creating a netlify.toml config file:

## netlify.toml
[build]
  command = "yarn build && yarn export"
  publish = "out"

If you have your project on a Git provider like GitHub, GitLab, and Bitbucket, you can also set up Continuous Deployment to build and deploy your site after every merge. This is your gateway drug to adding on the rest of Netlify’s functionality onto your Next.js app:

Next.js as a Static Site Generator

By default, Next.js’s static export works on a 1 for 1 basis: if you have a potato.js file in your /pages folder, that directly maps to potato.html. This is fine for authoring static pages with React components and the rest of the React ecosystem, but to truly qualify as a Static Site Generator, it should generate pages from data. So for example, given a /content folder of markdown files, I should be able to generate a corresponding list of HTML pages from a template. Gatsby and React-Static are best known for doing this in the React ecosystem, but you may be surprised to learn that Next.js holds it’s own here!

Data-driven static generation has always been possible with Next.js, but it used to involve a lot of nonstandard custom server coding that raised the bar too high for something predicated on simplicity.

However, with Dynamic Route Segments released in Next.js 9, as well as constantly improving static export bugfixes and support, data-driven static site generation in Next.js has never been easier!

First we write a next.config.js file with the routes we want Next.js to statically export:

module.exports = {
  exportPathMap: function() {
    return {
      '/': { page: '/' }
      '/about': { page: '/about' }
      // etc...
    }
  }
}

In the static export process we previously wrote about, we didn’t need to write this file because Next.js autogenerates your route map since v6. However, there is no way for Next.js to know ahead of time what dynamically generated routes to statically render. That’s where Dynamic Route Segments come in.

With Next.js 9, we can declare a file structure like this:

- /pages
  - index.js
  - about.js
  - /blog
    - [slug].js

Within [slug].js, we can receive that parameter and render the content in React accordingly, effectively turning it into a page template!

import { useRouter } from 'next/router'
export default () => {
  const router = useRouter()
  return <h1>Slug: {router.query.slug}</h1>
}

However this still isn’t good enough to make Next.js into a Static Site Generator. We need to pass other information other than the slug to the page, and we also need to tell Next.js how many of each slug it should render. It turns out that next.config.js is a very nice place to do this!

// parses a folder of markdown files into objects. very handy!
const jdown = require('jdown') 

module.exports = {
  exportPathMap: async function() {
    // pages we know about beforehand
    const paths = {
      '/': { page: '/' },
      '/about': { page: '/about' }
    }
    // dynamic, data-generated pages
    const content = await jdown('content') // assumes some markdown files in a `/content` folder, with frontmatter that offers a slug
    const posts = [] // build up array of objects for the top level list
    Object.entries(content).forEach(([filename, fileContent]) => {
      // the filename becomes the slug
      paths[`/blog/${filename}`] = { page: '/blog/[slug]', query: { 
          slug: filename, 
          ...fileContent 
        } 
      }
    })
    return paths
  }
}

Now we are telling Next.js everything it needs to know - the full path with the slug, and passing in the extra information it needs to fill out the [slug].js template.

And that’s that! If you are familiar with React Static, you’ll see strong parallels with getRoutes and getData in static.config.js. Gatsby opts for a Redux action based model, and so you use the provided createPage function to progammatically create pages in plugins and themes.

With this many choices for static site generation in React, the only task left to do is the hardest one: go forth and write that content!

4 Likes

If I host my public assets in public folder for the NextJs project with the Netlify Large Media. How would next export work?

Would Netlify Large Media tracked files get duplicated on Netlify servers? (from public folder to out/public)

Hi, @saw-mon_and_natalie, the answer depends on what the build does with those files. This the limitations documentation for Netlify Large Media (NLM) says this:

Files tracked with Large Media are uploaded directly to the Netlify Large Media storage service on push, completely bypassing the site build. This saves build time, but also means that the files are not available to tools that process asset files during the build, such as Hugo’s image processing or the gatsby-image plugin. Depending on your needs, you may be able to replace this functionality with Netlify’s image transformation service.

So, if Next.js copied the files (which will just be text file pointers in the local repo) to the publish directory, then the tracked files will be available in the deployed site.

However, if Next.js is trying to somehow process or modify the files when the site is built that won’t work. That is because the actual files won’t exist in the build system (because they are replaced with the text pointer files there).

To summarize, if your build copies NLM tracked files unchanged, that will work. If the build modifies the files during the build process, that won’t work and NLM should not be used for that use case.

If there are other questions, please let us know.

1 Like

Thank you @luke. So the build process copies the text file pointers. Would we be able to symlink files from outside of the publish directory in the publish directory?

Hi, @saw-mon_and_natalie. I’m not sure. I suspect it will work though.

Would you be willing to test and let us know if it works or not?

Doesn’t Netlify cover all this on its own? I’m not being rhetorical, I really need to know…

I Asked in another thread:

What if I wanted to use the serverful or serverless option?

Due to my site settings (next-i18next not compatible with next export), I can’t use static HTML. What are the build settings for these options. I’ve tried many different options even with dummy projects and I don’t succeed to deploy the site.

FYI, this article is far out of date, so I am locking this thread with this pointer to our current best practices (which are both easier, and better performing, and more complete than they were when this post was written over a year and a half ago):

This implementation (that I’ve linked to details about), includes native i18n support, @jguix :slight_smile:

Please don’t feel like you can’t talk about next.js and Netlify, just please start a new thread to do so :slight_smile:

Thanks!

1 Like