Next.js Tutorial Notes

This blog post contains some notes I took following the Next.js tutorial at nextjs.org/learn/basics/create-nextjs-app, in which you walk through building a simple blog app.

Initializing a Next.js App

Initialize a Next.js app similarly to how you would with Create React App:

Copy
npx create-next-app nextjs-blog \
--use-npm --example \
"https://github.com/vercel/next-learn-starter/tree/master/navigate-between-pages-starter"

Navigate Between Pages

Pages in Next.js

Creating pages in Next.js involves writing JSX and using React components, instead of writing HTML.

To create a page in your app you need to define a JS file in the pages directory in the root of your project.

The path to that file becomes the URL path.

Suppose I’ve got a blog built with Next.js at myblog.com and my path directory just contains an index.js, then pages/index.js will correspond to the / route.

If I want to add a blog post I can simply create posts/first-post.js in pages with content similar to:

export default function FirstPost() {
  return <h1>First Post</h1>
}

Now navigating to myblog.com/posts/first-post will display that page.

Note: the component can have any name, but you must export it as a default export.

Link Component

To link between pages you use the <Link> component.

Instead of using <a href"..."> you use <Link href="..."> and put an <a> tag inside.

So to link back to the homepage from the first-post page edit pages/posts/first-post.js to be:

import Link from 'next/link'

export default function FirstPost() {
  return (
    <>
      <h1>First Post</h1>
      <h2>
        <Link href="/">
          <a>Back to home</a>
        </Link>
      </h2>
    </>
  )
}

Client-Side Navigation

This part of the tutorial is when I started getting excited about Next.js because this section introduces some of the cool stuff Next.js does to optimize application performance.

Next.js uses client-side navigation which as the tutorial states means that “page transition happens using JavaScript, which is faster than the default navigation done by the browser [(which always reloads the entire page)]”.

Next.js performs code splitting automatically so that each page only loads what is necessary. So when you visit a new page only the code for that page is served, the code for other pages is not.

As a side benefit only loading the code of the page you requested isolates that page so that if it throws an error the rest of the application still works.

In production Next.js also performs prefetching. Whenever a <Link> component appears in the browser’s viewport, Next.js prefetches the code for the linked page in the background. This way when you click on the link the code for the page will already be loaded making the page transition extra fast.

Assets, Metadata, and CSS

Assets

Next.js serves static assets under the top-level public directory.

Image Component

Next.js provides an Image component to handle things the regular HTML img tag doesn’t.

With the Image component, Next.js:

Here’s an example using the Image component:

import Image from 'next/image'

const YourComponent = () => (
  <Image
    src="/images/profile.jpg" // Route of the image file
    height={144} // Desired size with correct aspect ratio
    width={144} // Desired size with correct aspect ratio
    alt="Your Name"
  />
)

Metadata

Next.js has a built in React component named <Head> (uppercase H) which you can use like the regular <head> HTML tag:

import Head from 'next/head'
export default function FirstPost() {
  return (
    <>
      <Head>
        <title>First Post</title>
      </Head>
      <h1>First Post</h1>
      <h2>
        <Link href="/">
          <a>Back to home</a>
        </Link>
      </h2>
    </>
  )
}

CSS Styling

Next.js supports multiple CSS-in-JS libraries, you can also import .css and .scss files.

Layout Component

One way of applying style to elements in a React Component is to use the Layout component provided by Next.js, along with CSS Modules.

CSS Modules:

An example for how to use CSS Modules:

  1. Create a components directory at the root of your project
  2. Create a layout.module.css file in the components directory with the style you want to apply, such as:
.container {
  max-width: 36rem;
  padding: 0 1rem;
  margin: 3rem auto 6rem;
}
  1. Create a layout.js file in components with content:
import styles from './layout.module.css'

export default function Layout({ children }) {
  return <div className={styles.container}>{children}</div>
}
  1. Wrap the elements you want to style in your React component with the Layout component:
import Head from 'next/head'
import Link from 'next/link'
import Layout from '../../components/layout'

export default function FirstPost() {
  return (
    <Layout>
      <Head>
        <title>First Post</title>
      </Head>
      <h1>First Post</h1>
      <h2>
        <Link href="/">
          <a>Back to home</a>
        </Link>
      </h2>
    </Layout>
  )
}

CSS Modules are useful for component-level styles, however Next.js supports another method to handle global CSS.

Global Styles

Next.js has an App component it uses to initialize pages. This component can be overridden to control page initialization.

To load global CSS files, override the App component by defining a file called pages/_app.js:

import '../styles/global.css'

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />
}

Notes:

Pre-rendering and Data Fetching

Pre-rendering

Pre-rendering is one of the most important concepts in Next.js.

Pre-rendering results in better performance and SEO.

Next.js pre-renders every page by default.

Next.js generates HTML for each page in advance instead of having it done by client-side JavaScript.

If you disable JavaScript you will still be able to see the UI of a Next.js app because Next.js pre-renders the app into static HTML.

The above doesn’t imply that disabling JavaScript means you can still interact with a Next.js app because:

“Each generated HTML is associated with minimal JavaScript code necessary for that page. When a page is loaded by the browser, its JavaScript code runs and makes the page fully interactive. (This process is called hydration.)”

Two Forms of Pre-rendering

Next.js has two forms of pre-rendering: Static Generation and Server-side Rendering.

The difference between the two forms of pre-rendering is in when Next.js generates the HTML:

Next.js lets you choose which pre-rendering form to use for each page.

Note: in development, every page is rendered on each request (including those that use Static Generation).

Next.js recommends using Static Generation whenever possible.

Static Generation with and without Data

Static Generation can be done with and without data.

For pages without data, there’s nothing much to it, Next.js just generates the HTML for the pages at build time.

Obviously the above is not always the case, sometimes you will need to fetch external data before a page can be statically generated. Next.js supports Static Generation with data out of the box.

How is this done? When exporting a page component you can export an async function called getStaticProps which:

export default function Home(props) { ... }

export async function getStaticProps() {
  // Get external data from the file system, API, DB, etc.
  const data = ...

  // The value of the `props` key will be
  //  passed to the `Home` component
  return {
    props: ...
  }
}

Note: in development, getStaticProps runs on each request.

Fetching Data at Request Time

As mentioned above, if you need to fetch data at request time use Server-side Rendering.

getStaticProps is only run at build time in production and so only runs server-side and never on the client.

You must export getServerSideProps in order to use Server-side Rendering.

export async function getServerSideProps(context) {
  return {
    props: {
      // props for your component
    }
  }
}

The context parameter contains request specific parameters.

Client-side Rendering

Client-side Rendering can be used when you do not need to pre-render page data. This approach involves:

Example uses of this approach are for user dashboard pages because:

SWR

There’s a React hook for data fetching called SWR which Next.js highly recommends for fetching data on the client side because it handles lots of things such as caching, revalidation, focus tracking…

import useSWR from 'swr'

function Profile() {
  const { data, error } = useSWR('/api/user', fetch)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

Dynamic Routes

Page Path Depends on External Data

Previously we saw that with getStaticProps we could fetch data required to render a page. Now we’ll see how we can do something similar for the page path using another async function called getStaticPaths.

If you want to statically generate a page at a path called /posts/<id> where <id> can be dynamic, then create a page at /pages/posts/[id].js containing:

import Layout from '../../components/layout'

export default function Post() {
  return <Layout>...</Layout>
}

export async function getStaticPaths() {
  // Return a list of possible value for id
}

export async function getStaticProps({ params }) {
  // Fetch necessary data for the blog post using params.id
}   

Dynamic Routes Details

In development getStaticPaths runs on every request, in production it runs at build time.

Dynamic routes can be extended to catch all paths by adding three dots inside the brackets:
pages/posts/[...id].js matches /posts/a as well as /posts/a/b and /posts/a/b/c and so on.

Conclusion

After my first encounter with Next.js I was left feeling excited to learn more and use it in future projects.

Next.js does so many cool things out of the box:

Next.js also provides React components like the Image component that do even more cool things for you like image optimization.

I forget why I chose to write my own static site generator to make this website… I’m thinking I might have to refactor the codebase to use Next.js now!