Static Site Generation (SSG) with Next.js | MDN Blog

Static Site Generation (SSG) with Next.js | MDN Blog

Now that we know how to fetch data from a public API at build time and populate a page template with the response for static pages, we might need to do something more complex.

We have a post archive at /posts that lists all the post titles, but we want to create individual pages for each post.
We will use the numeric post id for this (e.g., /posts/12), but you can imagine it might also be interesting to use the title, for example, /posts/my-cool-post.

This poses an interesting problem because we might not know the IDs of the posts we’re fetching at build time.
How do we create template pages for every post? We can use dynamic routes to handle this case, which lets us add special handling to the routing but keeps static site generation as the preferred output.

To add dynamic routes for numeric blog IDs, make a directory with the special Next.js format [segmentName] (in this case [id]).
If you’re following these steps on a different machine using a shell like zsh, you will have to quote the square brackets like '[id]':

  1. Create a new directory [id] within the src/app/posts directory, and navigate into it.
    mkdir "[id]"
    cd "[id]"
    
  2. Create a new JavaScript file.
    nano page.js
    
  3. Copy and paste the below code into the page.js file.
    import React from "react";
    import { notFound } from "next/navigation";
    import styles from "../../page.module.css";
    
    async function getPost(id) {
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/posts/${id}`,
      );
      if (!res.ok) {
        return null;
      }
      return res.json();
    }
    
    export async function generateStaticParams() {
      const res = await fetch("https://jsonplaceholder.typicode.com/posts");
      const posts = await res.json();
    
      return posts.map((post) => ({
        id: post.id.toString(),
      }));
    }
    
    export default async function PostPage({ params }) {
      const post = await getPost(params.id);
    
      if (!post) {
        notFound();
      }
    
      return (
        <main className={styles.main}>
          <h1>{post.title}</h1>
          <p>{post.body}</p>
        </main>
      );
    }
    
  4. Save and close the file.
  5. Recreate the production build and restart the application:
    
    npm run build && npm run start
    

You should see the following:

Creating an optimized production build ...
? Compiled successfully
? Linting and checking validity of types
? Collecting page data
? Generating static pages (106/106)
? Collecting build traces
? Finalizing page optimization

Route (app)                              Size     First Load JS
? ? /                                    5.42 kB        92.4 kB
? ? /_not-found                          871 B          87.9 kB
? ? /posts                               338 B          87.4 kB
? ? /posts/[id]                          338 B          87.4 kB
   ? /posts/1
   ? /posts/2
   ? /posts/3
   ? [+97 more paths]

Note that we’ve generated 106 pages this time, so the additional 100 posts are created at build time, and the page paths are pre-rendered depending on external data.
If you want to explore other options, you can also look at getStaticPaths and the Next.js examples repository for other ways to add dynamic routes to an SSG build.

You can now visit the server at http://<server-ip>:3000/posts and explore each post at the numeric ID, like http://<server-ip>:3000/posts/2.