Using Next.js as a Static Site Generator
Tags: react, webdev, javascript, todayilearned
Published: 2020-08-21 09:55:10

<p>Static Site Generators are becoming extremely popular in the world of <a rel="noreferrer noopener" href="https://pagepro.co/web-app-development.html" target="_blank">web development</a> and Next.js is a proud competitor in this race. </p>
<h2><b>What are Static Site Generators?</b></h2>
<p><strong>🚀 STATIC = BLAZINGLY FAST 🚀</strong></p>
<p>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.</p>
<p><strong>💎 STATIC = SAFE 💎</strong></p>
<p>You will just publish static files, which means there is no direct connection to the database, dependencies, user data or other sensitive information.</p>
<p><strong>😎 STATIC = ENHANCED USER EXPERIENCE 😎</strong></p>
<p>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. </p>
<h2><b>What is Next.js?</b></h2>
<p>“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.”</p>
<p>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.</p>
<p>Let's go! </p>
<h2><b>Creating an app</b></h2>
<p>We will start by creating a new next app. To do that simply run the command:</p>
```php
yarn create next-app
# or if you are using npm
npx create-next-app
```
<p>Now, let’s take a look at our project structure:</p>
<img src="https://pagepro.co/blog/wp-content/uploads/2020/07/structre.png" alt="next app structure"/>
<p><b><em>pages</em></b> - this directory contains all our pages and defines routing for our app (<a rel="noreferrer noopener nofollow" href="https://nextjs.org/docs/routing/introduction" target="_blank">more about routing here</a>).</p>
<p><b><em>pages/api</em></b> - here we can add our API endpoint if we need some, in our case we can remove this folder safely (<a rel="noreferrer noopener nofollow" href="https://nextjs.org/docs/api-routes/introduction" target="_blank">more about API routes here</a>).</p>
<p><b><em>public</em></b> - we can put all our static assets in this directory.</p>
<p>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:</p>
```javascript
"dev": "next dev" -> # remove
"build": "next build" -> "build": "next build && next export"
"start": "next start" -> "start": "next dev"
```
<p>So now our scripts section should look like this:</p>
```javascript
{
  ...
  "scripts": {
    "build": "next build && next export",
    "start": "next dev"
  },
  ...
}
```
<p>Now we can test our configuration, run the build command:</p>
```php
yarn run build
# or if you are using npm
npm run build
```
<p>Next should create <strong>out</strong> directory at the root of our project with all the static html files and assets ready to host. We can change the <strong>out</strong> directory by adding a <strong>-o </strong>flag to our <strong>next export</strong> command like this:</p>
```javascript
{
  ...
  "scripts": {
    "build": "next build && next export -o build",
    ...
  },
  ...
}
```
<h2><b>Adding content to page</b></h2>
<p>Let’s head to <strong>pages\index.js</strong> file and remove the content of <strong>Home</strong> component and change it like this:</p>
```javascript
const Home = () => {
  return (
    <p>
      Hello From my next.js app!
    </p>
  )
}

export default Home;
```
<p>And start our app by running command:</p>
```php
yarn run build
# or if you are using npm
npm run build
```
<p>Now you should be able to access http://localhost:3000 and see our Home component content.</p>
<p>Now let’s add some content that will be evaluated at the build time. We can do that by using <strong>getStaticProps</strong> 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 <strong>getStaticProps</strong> work.</p>
```javascript
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;
```
<p>We will see that <strong>buildTimestamp</strong> changes every refresh, this will not be the case when we build our app because <strong>getStaticProps</strong> is called only once when app is building.</p>
<h2><b>Adding data source</b></h2>
<p>We already know how <strong>getStaticProps</strong> 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. </p>
<p>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 <strong>src\setup\apolloClient.js</strong> file where our apollo client will be created.</p>
```javascript
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
```
<p>Additionally we will have to install some apollo realated packages. Simply run the command:</p>
```php
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
```
<p>As you can see we will be using GitLab graphql api (<a rel="noreferrer noopener nofollow" href="https://gitlab.com/-/graphql-explorer" target="_blank">explorer is available here</a>).</p>
<h2><b>Creating pages and fetching data</b></h2>
<p>Now we can fetch some data, let’s fetch some repositories. First we have to create graphql query:</p>
```javascript
const PROJECTS_QUERY = gql`
  query {
    projects (first: 10) {
      nodes {
        id
        name
        description
      }
    }
  }
`
```
<p>Now we can import apollo client and use it with above query in our <strong>getStaticProps</strong>:</p>
```javascript
export const getStaticProps = async () => {
  const { data } = await apolloClient.query({
    query: PROJECTS_QUERY
  })

  return {
    props: {
      projects: data.projects
    }
  }
}
```
<p>Now list of GitLab projects is available in our <strong>Home</strong> component props, let’s render it.</p>
```javascript
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> 
  )
}
```
<p>And that’s it, we have a working GitLab projects list. </p>
<p>Now let’s add project detail page. </p>
<p>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:</p>
<img src="https://pagepro.co/blog/wp-content/uploads/2020/07/square.png" alt="a square presenting how to save a project with square brackets"/>
<p><a rel="noreferrer noopener nofollow" href="https://nextjs.org/docs/routing/dynamic-routes" target="_blank">More about dynamic routing here.</a></p>
<p>When we are building our pages statically Next.js requires us to export a function called <strong>getStaticPaths</strong> from our page. </p>
<p>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 <strong>getStaticPaths</strong> to generate urls.</p>
<p>First let’s add links to projects details on homepage. We have to add fullPath field to our query:</p>
```javascript
const PROJECTS_QUERY = gql`
  query {
    projects (first: 10) {
      nodes {
        id
        name
        description
        fullPath
      }
    }
  }
`
```
<p>And render next link for every project:</p>
```javascript
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>
  )
}
```
<p>Now we can add code to our project details page:</p>
```javascript
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
    },
  }
}
```
<p>Let’s explain some key parts here:</p>
<ol><li>We map our projects list to an array of paths required by Next.js. Structure that Nexts.js expects looks like this:</li></ol>
```javascript
{
  paths: [
    {
       params: {
         (all params that we want to pass to getStaticProps)
       }
    }
  ]
}
```
<p>Also when we use Catch All route we have to pass our param as array that’s why we use split here.</p>
<ol start="2"><li>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. <a href="https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required" target="_blank" rel="noreferrer noopener nofollow">More about fallback here</a>.</li><li> We merge our fullPath param to single string so we can display it nicely in our component.</li></ol>
<p>Now we can change <strong>getStaticProps</strong> to be more useful and fetch some project data for us. First we need query for project details:</p>
```javascript
const PROJECT_DETAILS_QUERY = gql`
  query ($fullPath: ID!) {
    project(fullPath: $fullPath) {
      name
      descriptionHtml
      repository {
        empty
        tree {
          lastCommit {
            sha
          }
        }
      }
    }
  }
`
```
<p>And run it inside of our <strong>getStaticProps</strong></p>
```javascript
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
    }
  }
}
```
<p>Now we can display some data on our project details page:</p>
```javascript
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>
  )
}
```
<p>And that’s it, we have created a static page displaying first 10 GitLab projects and their details. </p>
<p>We can now build it and serve to check if everything is working properly.</p>
```php
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
```
<p><a rel="noreferrer noopener" href="https://github.com/Pagepro/nextjs-static-site-generator" target="_blank">Access the full project here. </a></p>
<h2><b>Next.js vs GatsbyJS</b></h2>
<p>As we can see <strong>Next.js</strong> can successfully generate static pages like <strong>GatsbyJS</strong> does and someone could ask:</p>
<p>“Can Next.js replace GatsbyJS?”</p>
<img src="https://pagepro.co/blog/wp-content/uploads/2020/07/yes-and-no.png" alt="a cartoon pirate is saying you cannot actually replace GatsbyJS with Next.JS"/>
<p>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. </p>
<p>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.</p>
<p>Keep in mind that <strong>Next.js has one big advantage over GatsbyJS</strong>: </p>
<ul><li>you can create and app which is partially static and dynamic. </li></ul>
<p>For example you can prerender first 10 posts from your blog and the rest can be rendered when the page is requested by user.</p>
<p>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.</p>