What is Static Site Generation (SSG)?
Static Site Generation (SSG) is a process of generating HTML pages at build time. This means you can fetch the data from the server, create a static page, prerender it, and serve it to users as static files.
SSG is beneficial if you create documentation about a specific product because data does not change frequently. You can make a static page once and display it to the user once you get the request. You don’t need database fetching on each request; send a static page each time, saving lots of cost and time.
How does SSG Work in Next.js?
Here’s how the SSG works in Next:
Build time rendering
As the name suggests, when you run the command “next build“, Next.js prerenders HTML pages with data once and saves them as static pages.
Static files
In SSG, prerendered pages are called static files. You can serve these static files directly to users via a server or CDN (Content Delivery Network). Static files do not change frequently, making them highly performant.
Data Fetching
Since everything happens at build time in SSG, data fetching is also done at build time.
What happens behind the scenes is that during the build time, we can fetch data from APIs, databases, or other sources using the getStaticProps function provided by Next.js. This data is then used to generate the static HTML.
Incremental Static Regeneration (ISR)
If you want to extend SSG, use Incremental Static Regeneration (ISR).
ISR allows us to regenerate static pages on the fly at runtime. You can set a revalidation interval that regenerates the page after a certain period, enabling the content to stay up-to-date without a complete rebuild.
In order to create SSG, we need to use two main functions:
- getStaticProps
- getStaticPaths
What is getStaticProps?
The getStaticProps() function provided by Next.js fetches data at build time. Before Next.js creates static files, we will use the getStaticProps() function to fetch the necessary data for that static file.
What is getStaticPaths?
The getStaticPaths() function is used in conjunction with dynamic routes. Based on your data allows us to define which dynamic routes should be prerendered at build time.
Project Overview
Let’s create a simple project demonstrating Static Site Generation using getStaticProps and getStaticPaths, incorporating Bootstrap 5 for styling.
Here is the step-by-step guide:
Step 1: Initialize the Next Project
Create a new Next project using the command below:
npx create-next-app@latest ssg-blog-example
While creating a new project, make sure that you select Page Router instead of App Router and use JavaScript instead of TypeScript.
Now, install the project-related dependencies Bootstrap, which we will use to style our application.
npm install bootstrap
Step 2: Import Bootstrap CSS
After installing a Bootstrap package, we now need to import the CSS. But where should we import it? Well, Inside the “src” directory, you will see the “pages” folder, and inside the pages folder, you will see the “_app.js” file. Open that file in the editor, and we will import the Bootstrap CSS file in that file like this:
// src/pages/_app.js import 'bootstrap/dist/css/bootstrap.min.css'; import "@/styles/globals.css"; export default function App({ Component, pageProps }) { return <Component {...pageProps} />; }
Step 3: Create a Layout, Header, and Footer
Under the “src” folder, create a new folder called “components”.
Under the “components” folder, create the following three files:
- Layout.jsx
- Header.jsx
- Footer.jsx
Here is the code for the Header.jsx file:
// src/components/Header.jsx import Link from "next/link"; const Header = () => { return ( <header className="bg-dark text-light py-3"> <div className="container"> <nav className="navbar navbar-expand navbar-dark"> <Link href="/" className="navbar-brand"> SSG Blog Example </Link> <ul className="navbar-nav ms-auto"> <li className="nav-item"> <Link href="/" className="nav-link"> Home </Link> </li> </ul> </nav> </div> </header> ); }; export default Header;
Here is the code for the Footer.jsx file:
// src/components/Footer.jsx const Footer = () => { return ( <footer className="bg-dark text-light py-3 mt-auto"> <div className="container text-center"> <p className="mb-0">© 2024 SSG Blog Example</p> </div> </footer> ); }; export default Footer;
Here is the code for the Layout.jsx file:
// src/components/Layout.jsx import Header from "./Header"; import Footer from "./Footer"; const Layout = ({ children }) => { return ( <div className="d-flex flex-column min-vh-100"> <Header /> <main className="flex-grow-1 container py-4">{children}</main> <Footer /> </div> ); }; export default Layout;
Step 4: Create an API to fetch the data
Since we want to display a list of posts, we need to fetch the posts first from an API endpoint.
We will use a FREE ONLINE API called JSONPlaceholder.
We use the built-in fetch() function to send a network request to API.
Under the “src” folder, create a new folder called “lib”.
Inside the “lib” folder, create a new file called “api.js” and add the code below:
// src/lib/api.js const API_URL = 'https://jsonplaceholder.typicode.com'; export const getAllPosts = async () => { const response = await fetch(`${API_URL}/posts`); if (!response.ok) { throw new Error('Failed to fetch posts'); } return response.json(); }; export const getPostById = async (id) => { const response = await fetch(`${API_URL}/posts/${id}`); if (!response.ok) { throw new Error('Failed to fetch post'); } return response.json(); }; export const getPostIds = async () => { const posts = await getAllPosts(); return posts.map(post => ({ params: { id: post.id.toString() } })); };
Here, we have three functions that will return total posts, individual posts by ID, and post IDs.
We will consume these API functions to display data properly.
Step 5: Displaying the data
This is the part where we use the getStaticProps() function to fetch and prerender the HTML with data.
Let’s update the Home page to import the Layout component and display the data structure.
Add the below code in src/pages/index.js file:
// src/pages/index.js import Link from "next/link"; import Layout from "../components/Layout"; import { getAllPosts } from "../lib/api"; export default function Home({ posts }) { return ( <Layout> <h1 className="mb-4">Latest Blog Posts</h1> <div className="row"> {posts.map((post) => ( <div key={post.id} className="col-md-4 mb-4"> <div className="card h-100"> <div className="card-body"> <h5 className="card-title">{post.title}</h5> <p className="card-text">{post.body.substring(0, 100)}...</p> <Link href={`/posts/${post.id}`} className="btn btn-primary"> Read More </Link> </div> </div> </div> ))} </div> </Layout> ); } export async function getStaticProps() { const posts = await getAllPosts(); return { props: { posts, }, revalidate: 60, // Optional: revalidate every 60 seconds }; }
In this code, we used the getStaticProps() function to fetch data at build time for a page component.
The getStaticProps() function fetches data at build time for a Next.js page. The fetched data (posts) is passed to the page component as props.
The revalidate option ensures that the page is re-generated every 60 seconds, keeping the content somewhat dynamic without fully relying on server-side rendering for each request.
Step 6: Displaying an individual post by id
Under the “pages” folder, create a new folder called “posts”.
Under the “posts” folder, create a new file called “[id].jsx”.
Add the below code in “[id].jsx” file:
// src/pages/posts/[id].jsx import Layout from "../../components/Layout"; import { getPostById, getPostIds } from "../../lib/api"; import Link from "next/link"; export default function Post({ post }) { if (!post) return <div>Post not found</div>; return ( <Layout> <article className="blog-post"> <h1 className="mb-4">{post.title}</h1> <p className="lead">{post.body}</p> <Link href="/" className="btn btn-secondary mt-4"> Back to Home </Link> </article> </Layout> ); } export async function getStaticPaths() { const paths = await getPostIds(); return { paths, fallback: false, }; } export async function getStaticProps({ params }) { const post = await getPostById(params.id); return { props: { post, }, revalidate: 60, // Optional: revalidate every 60 seconds }; }
In this code, we used the getStaticPaths() function that tells Next.js which paths to pre-render at build time.
The getPostIds() function presumably returns an array of post IDs.
The fallback = false means that if a user tries to access a path that has not been generated, they will receive a 404 page.
Basically, in this code, we defined a page component that displays individual blog posts.
The getStaticPaths() function generates all the possible blog post pages based on available post IDs.
The getStaticProps() function fetches the content of a specific post at build time and passes it as a prop to the Post component.
Step 7: Run the project
Let’s run this Next.js project using the command below:
npm run dev
Go to this URL: http://localhost:3000/
If you click on the Read More button, you will be redirected to an individual post by ID:
Here is the GitHub Code.