How To Use Next.js as a Static Site Generator

Share

Static Site Generators are becoming extremely popular in the world of web development and Next.js is a proud competitor in this race.

What are Static Site Generators?

🚀 STATIC = BLAZINGLY FAST 🚀

Static Site Generator are extremely fast. Once loaded, they prefetches resources for other pages so clicking around the site feels like a blonk of an eye.

💎 STATIC = SAFE 💎

You will just publish static files, which means there is no direct connection to the database, dependencies, user data or other sensitive information.

😎 STATIC = ENHANCED USER EXPERIENCE 😎

Simply because clicking and walking through your website feels like a walk in a park on a sunny day with no unexpected turns, stairs, or dead ends.

What is Next.js?

“Next is a framework which we can use to build React sites with Server Side Rendering or generate static pages from our react code. All that configured for us by Next itself.”

And in this post we will be taking a look at the static pages export functionality and how we can make it with next.js.

Let’s go!

Creating an app

We will start by creating a new next app. To do that simply run the command:

yarn create next-app
# or if you are using npm
npx create-next-app

Now, let’s take a look at our project structure:

next app structure

pages – this directory contains all our pages and defines routing for our app (more about routing here).

pages/api – here we can add our API endpoint if we need some, in our case we can remove this folder safely (more about API routes here).

public – we can put all our static assets in this directory.

Let’s change our commands to reflect what we want to achieve in this project. Got to package.json and modify scripts section like this:

"dev": "next dev" -> # remove
"build": "next build" -> "build": "next build && next export"
"start": "next start" -> "start": "next dev"

So now our scripts section should look like this:

{
  ...
  "scripts": {
    "build": "next build && next export",
    "start": "next dev"
  },
  ...
}

Now we can test our configuration, run the build command:

yarn run build
# or if you are using npm
npm run build

Next should create out directory at the root of our project with all the static html files and assets ready to host. We can change the out directory by adding a -o flag to our next export command like this:

{
  ...
  "scripts": {
    "build": "next build && next export -o build",
    ...
  },
  ...
}

Adding content to page

Let’s head to pages\index.js file and remove the content of Home component and change it like this:

const Home = () => {
  return (
    <p>
      Hello From my next.js app!
    </p>
  )
}

export default Home;

And start our app by running command:

yarn run build
# or if you are using npm
npm run build

Now you should be able to access http://localhost:3000 and see our Home component content.

Now let’s add some content that will be evaluated at the build time. We can do that by using getStaticProps which is a function exported from our page. As we don’t have any data source configured yet we will do a simple example to show how getStaticProps work.

const Home = ({ buildTimestamp }) => {
  return (
    <p>
      Hello From my next.js app!
      App built at: {buildTimestamp}
    </p>
  )
}

export const getStaticProps = () => {
  return {
    props: {
      buildTimestamp: Date.now()
    }
  }
}

export default Home;

We will see that buildTimestamp changes every refresh, this will not be the case when we build our app because getStaticProps is called only once when app is building.

Adding data source

We already know how getStaticProps works, now we can make it more useful and add some external data source to our app. Next.js doesn’t come with any data provider built in (for example GatsbyJS has graphql) so we will have to add it manually.

Basically you can load your data however you like. In this tutorial we will use GraphQL with Apollo Client. First let’s add src directory where we will keep all our code shared between pages. Then create src\setup\apolloClient.js file where our apollo client will be created.

import { ApolloClient } from "apollo-client"
import { HttpLink } from "apollo-link-http"
import { InMemoryCache } from "apollo-cache-inmemory"

const apolloClient = new ApolloClient({
  link: new HttpLink({
    uri: 'https://gitlab.com/api/graphql',
  }),
  cache: new InMemoryCache()
})

export default apolloClient

Additionally we will have to install some apollo realated packages. Simply run the command:

yarn run add apollo-cache-inmemory apollo-client apollo-link-http graphql graphql-tag
# or if you are using npm
npm install --save apollo-cache-inmemory apollo-client apollo-link-http graphql graphql-tag

As you can see we will be using GitLab graphql api (explorer is available here).

Wondering if Next.js is a better choice than Gatsby for the project?

Creating pages and fetching data

Now we can fetch some data, let’s fetch some repositories. First we have to create graphql query:

const PROJECTS_QUERY = gql`
  query {
    projects (first: 10) {
      nodes {
        id
        name
        description
      }
    }
  }
`

Now we can import apollo client and use it with above query in our getStaticProps:

export const getStaticProps = async () => {
  const { data } = await apolloClient.query({
    query: PROJECTS_QUERY
  })

  return {
    props: {
      projects: data.projects
    }
  }
}

Now list of GitLab projects is available in our Home component props, let’s render it.

const Home = ({ projects }) => {
  return (
    <ul>
      {projects.nodes.map(({ name, description, id }) => (
        <li key={id}>
          <p><strong>{name}</strong></p>
          {description && <span>{description}</span>}
        </li>
      ))}
    </ul> 
  )
}

And that’s it, we have a working GitLab projects list.

Now let’s add project detail page.

First we have to add dynamic route to our app, we can do that by creating a file with square brackets in its name, like this:

a square presenting how to save a project with square brackets

More about dynamic routing here.

When we are building our pages statically Next.js requires us to export a function called getStaticPaths from our page.

We have to do that because Next.js needs to know all the page urls and their params at the build time. We have fetched first 10 Gitlab projects at our homepage so now we have to do the same in our getStaticPaths to generate urls.

First let’s add links to projects details on homepage. We have to add fullPath field to our query:

const PROJECTS_QUERY = gql`
  query {
    projects (first: 10) {
      nodes {
        id
        name
        description
        fullPath
      }
    }
  }
`

And render next link for every project:

const Home = ({ projects }) => {
  return (
    <ul>
      {projects.nodes.map(({ name, description, id, fullPath }) => (
        <li key={id}>
          <p><strong>{name}</strong></p>
          {description && <span>{description}</span>}
          <div>
            <Link
              href="/project/[...fullPath]"
              as={`/project/${fullPath}`}
            >
              <a>Details</a>
            </Link>
          </div>
        </li>
      ))}
    </ul>
  )
}

Now we can add code to our project details page:

import gql from "graphql-tag";
import apolloClient from "../../src/setup/apolloClient";

const ProjectDetailsPage = ({ fullPath }) => {
  return <>Project details page {fullPath}</>
}

export default ProjectDetailsPage;

const PROJECTS_QUERY = gql`
  query {
    projects(first: 10) {
      nodes {
        fullPath
      }
    }
  }
`

export const getStaticPaths = async () => {
  const { data } = await apolloClient.query({
    query: PROJECTS_QUERY,
  })

  return {
    paths: data.projects.nodes.map(({ fullPath }) => ({ // 1
      params: { fullPath: fullPath.split('/') },
    })),
    fallback: false, // 2
  }
}

export const getStaticProps = ({ params }) => {
  return {
    props: {
      fullPath: params.fullPath.join('/') // 3
    },
  }
}

Let’s explain some key parts here:

  1. We map our projects list to an array of paths required by Next.js. Structure that Nexts.js expects looks like this:
{
  paths: [
    {
       params: {
         (all params that we want to pass to getStaticProps)
       }
    }
  ]
}

Also when we use Catch All route we have to pass our param as array that’s why we use split here.

  1. We have to tell next if we want to render missing pages live or just return 404. In our case we are making fully static page so we define fallback as false. More about fallback here.
  2. We merge our fullPath param to single string so we can display it nicely in our component.

Now we can change getStaticProps to be more useful and fetch some project data for us. First we need query for project details:

const PROJECT_DETAILS_QUERY = gql`
  query ($fullPath: ID!) {
    project(fullPath: $fullPath) {
      name
      descriptionHtml
      repository {
        empty
        tree {
          lastCommit {
            sha
          }
        }
      }
    }
  }
`

And run it inside of our getStaticProps

export const getStaticProps = async ({ params }) => {
  const fullPath = params.fullPath.join('/');
  const { data } = await apolloClient.query({
    query: PROJECT_DETAILS_QUERY,
    variables: {
      fullPath
    }
  })

  return {
    props: {
      project: data.project
    }
  }
}

Now we can display some data on our project details page:

const ProjectDetailsPage = ({ project }) => {
  const {
    name,
    descriptionHtml,
    repository: {
      tree
    }
  } = project
  const { lastCommit } = tree || {}
  const { sha } = lastCommit || {}

  return (
    <div>
      <h1>{name}</h1>
      <p dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
      {sha && <p>Last commit SHA: {sha}</p>}
    </div>
  )
}

And that’s it, we have created a static page displaying first 10 GitLab projects and their details.

We can now build it and serve to check if everything is working properly.

yarn run build
# or if you are using npm
npm run build

# if you are using yarn and don't have serve installed run this command first
yarn global add serve


# if you are using yarn
serve ./build
# or if you are using npm
npx serve ./build

Access the full project here.

Next.js vs GatsbyJS

As we can see Next.js can successfully generate static pages like GatsbyJS does and someone could ask:

“Can Next.js replace GatsbyJS?”

a cartoon pirate is saying you cannot actually replace GatsbyJS with NextJS

You can do all the things in Next.js that you do in GatsbyJS but it take much more time because you need to do everything by yourself.

GatsbyJS has a lot of plugins that help to solve many problems and optimize the page. More than that it has data fetching features built in (graphql available out of the box). So for now we will have to wait for Next.js ecosystem to grow and get more plugins so we can create static pages as fast as we do in GatsbyJS.

Keep in mind that Next.js has one big advantage over GatsbyJS:

  • you can create and app which is partially static and dynamic.

For example you can prerender first 10 posts from your blog and the rest can be rendered when the page is requested by user.

So for now Next.js is just a more flexible solution that requires more work and gives us some powerful extra features but in terms of development time GatsbyJS is still better.

Still not sure which is better for your project?

Comments
Leave a Reply

View Comments (0)...

Related articles:

Hosting Gatsby & Storybook on GitLab Pages

Building App With React, TypeScript, and Parcel