Next.js has emerged as a popular framework for building React applications. One of its standout features is static site generation (SSG), a method that pre-renders pages at build time, offering faster load times and improved SEO.
In this blog, we will delve into the intricacies of Next.js static site generation, exploring its benefits, implementation, and advanced techniques like incremental static regeneration (ISR).
Static site generation (SSG) is a method of pre-rendering your website's pages at build time, converting them into static HTML files. This means that your pages are generated once, during the build process, and then served as static files. In Next.js, static site generation allows you to create static pages that are highly performant and SEO-friendly.
In a typical Next.js project, you use the function export async function getStaticProps to fetch data at build time. This data is then used to generate static pages. Here's an example:
1// pages/index.js 2export async function getStaticProps() { 3 const res = await fetch('https://api.example.com/data'); 4 const data = await res.json(); 5 6 return { 7 props: { 8 data, 9 }, 10 }; 11} 12 13export default function HomePage({ data }) { 14 return ( 15 <div> 16 <h1>Welcome to My Next.js Static Site</h1> 17 <pre>{JSON.stringify(data, null, 2)}</pre> 18 </div> 19 ); 20}
• Static Site Generation (SSG): Pages are pre-rendered as static HTML files at build time. This leads to faster page loads and better SEO since the content is already available when a user's request hits the web server.
• Server-Side Rendering (SSR): Pages are rendered on the server at each user's request. This means that every time a user visits a page, the server generates the HTML dynamically.
• Client-Side Rendering (CSR): Pages are rendered in the browser using JavaScript. The initial HTML file is usually minimal, and JavaScript loads the rest of the content dynamically.
In Next.js, you can choose the best rendering method based on your needs. For example, use SSG for blog posts or marketing pages where the content doesn't change frequently. Use SSR for pages that require up-to-date data on every request, like a live feed.
Static site generation in Next.js offers several advantages:
Performance: Since static files are served directly by the web server without additional processing, your pages load faster. This leads to a better user experience and can improve your search engine rankings.
Scalability: Serving static files is easier to scale because it doesn't involve server-side processing for each request. You can leverage CDNs to distribute your static files globally, further enhancing load times.
Security: Static files are less prone to security vulnerabilities compared to server-side rendered pages because there is no server-side code execution on each request.
SEO Benefits: Static pages are pre-rendered, making it easier for search engines to index your content. This can lead to better visibility and higher rankings in search engine results.
Reduced Server Load: Since static pages are pre-generated, your server doesn't need to handle complex computations or database queries for each request, reducing the overall load on your server.
In Next.js, the build process is crucial for static site generation. When you run the build command (next build), Next.js performs several tasks to prepare your static site. The build process involves:
Compiling: Next.js compiles your React components and JavaScript code.
Bundling: It bundles your code and its dependencies into optimized static files.
Generating Pages: For each page defined in your pages directory, Next.js generates an HTML file and associated JavaScript.
This process results in a collection of static files, including pre-rendered HTML files, that can be served directly by a web server. These static files are placed in the out directory, which you can deploy to a hosting provider or CDN.
Next.js handles static generation using two key functions: getStaticProps and getStaticPaths. These functions allow you to fetch data at build time and generate static pages based on that data.
The getStaticProps and getStaticPaths functions are essential for static site generation in Next.js. They enable you to fetch data at build time and generate static pages based on that data. By using these functions, you can ensure that your pages are statically generated, leading to faster page loads and better SEO.
• getStaticProps: Fetches data at build time and passes it as props to the page component.
• getStaticPaths: Defines dynamic routes and pre-renders paths at build time.
To get started with static site generation in Next.js, you first need to set up a new Next.js project. Follow these steps:
1npx create-next-app my-static-site 2cd my-static-site
1npm run dev
This command will start the development server at http://localhost:3000.
Next, let's create some pages and use getStaticProps to implement static site generation. We'll start with a simple homepage that fetches data at build time.
1// pages/index.js 2export async function getStaticProps() { 3 const res = await fetch('https://api.example.com/data'); 4 const data = await res.json(); 5 6 return { 7 props: { 8 data, 9 }, 10 }; 11} 12 13export default function HomePage({ data }) { 14 return ( 15 <div> 16 <h1>Welcome to My Next.js Static Site</h1> 17 <pre>{JSON.stringify(data, null, 2)}</pre> 18 </div> 19 ); 20}
In this example, getStaticProps fetches data from an API endpoint at build time and passes it to the HomePage component as props. The data is then used to render the static page.
1// pages/posts/[id].js 2export async function getStaticPaths() { 3 const res = await fetch('https://api.example.com/posts'); 4 const posts = await res.json(); 5 6 const paths = posts.map((post) => ({ 7 params: { id: post.id.toString() }, 8 })); 9 10 return { 11 paths, 12 fallback: false, 13 }; 14} 15 16export async function getStaticProps({ params }) { 17 const res = await fetch(`https://api.example.com/posts/${params.id}`); 18 const post = await res.json(); 19 20 return { 21 props: { 22 post, 23 }, 24 }; 25} 26 27export default function PostPage({ post }) { 28 return ( 29 <div> 30 <h1>{post.title}</h1> 31 <p>{post.content}</p> 32 </div> 33 ); 34}
In this example, getStaticPaths fetches a list of posts and generates paths for each post based on its ID. The getStaticProps function fetches the data for each individual post and passes it to the PostPage component.
Incremental Static Regeneration (ISR) is a powerful feature in Next.js that combines the benefits of static site generation with the flexibility of server-side rendering. ISR allows you to update static pages after they have been built, without needing to rebuild the entire site. This means you can serve static pages that are as up-to-date as possible while maintaining the performance benefits of static generation.
ISR works by regenerating static pages on the fly when a user's request is received, based on a revalidation interval that you define. This interval determines how often the static pages are re-generated, ensuring that your content remains fresh.
Traditional static site generation involves building all static pages at build time. This approach is highly performant and efficient for content that doesn't change frequently. However, it requires a full rebuild of the site whenever content updates are needed, which can be time-consuming and resource-intensive.
ISR, on the other hand, allows you to specify a revalidation interval for each static page. Instead of rebuilding the entire site, ISR re-generates only the pages that need to be updated based on the specified interval. This approach provides several advantages:
Up-to-Date Content: Ensures that your static pages stay up to date with minimal delay.
Improved Performance: Maintains the performance benefits of static site generation by serving pre-rendered pages.
Scalability: Reduces the need for frequent full-site rebuilds, making it easier to scale your site.
Implementing ISR in a Next.js project is straightforward. You use the revalidate property in the getStaticProps function to specify the revalidation interval. Here's an example:
1// pages/posts/[id].js 2export async function getStaticPaths() { 3 const res = await fetch('https://api.example.com/posts'); 4 const posts = await res.json(); 5 6 const paths = posts.map((post) => ({ 7 params: { id: post.id.toString() }, 8 })); 9 10 return { 11 paths, 12 fallback: 'blocking', 13 }; 14} 15 16export async function getStaticProps({ params }) { 17 const res = await fetch(`https://api.example.com/posts/${params.id}`); 18 const post = await res.json(); 19 20 return { 21 props: { 22 post, 23 }, 24 revalidate: 10, // Re-generate the page at most once every 10 seconds 25 }; 26} 27 28export default function PostPage({ post }) { 29 return ( 30 <div> 31 <h1>{post.title}</h1> 32 <p>{post.content}</p> 33 </div> 34 ); 35}
In this example:
• getStaticPaths: Fetches a list of posts and generates paths for each post based on its ID. The fallback: 'blocking' setting ensures that new pages are generated on-demand if they haven't been pre-generated.
• getStaticProps: Fetches data for each post and passes it to the PostPage component. The revalidate property is set to 10 seconds, meaning the page will be regenerated at most once every 10 seconds.
Optimized Performance: By combining static generation and on-demand updates, ISR ensures that your site remains fast and responsive.
Flexibility: Allows you to specify different revalidation intervals for different pages based on how frequently the content changes.
Scalability: Reduces the need for full-site rebuilds, making it easier to manage large sites with frequently updated content.
In Next.js, dynamic routing allows you to create pages that are generated based on dynamic data. This is particularly useful for sites with content that is structured in a hierarchical way, such as blogs, e-commerce sites, and portfolios. By combining dynamic routing with static site generation, you can pre-render pages that depend on dynamic data at build time, ensuring faster page loads and better SEO.
To handle dynamic routes with static site generation in Next.js, you use the getStaticPaths function. This function is used to specify which dynamic routes should be pre-rendered at build time. It works in tandem with the getStaticProps function to fetch the necessary data for each dynamic route.
Here's a step-by-step process:
Define the Dynamic Route: Create a new file with a dynamic segment in the pages directory. For example, pages/posts/[id].js where [id] is the dynamic part of the URL.
Implement getStaticPaths: In the file, export an async function called getStaticPaths. This function fetches the list of dynamic paths and returns them.
Implement getStaticProps: Fetch the data for each dynamic route and pass it as props to the page component.
Let's look at a complete example to see how dynamic routing with static site generation is implemented.
1// pages/posts/[id].js 2import { useRouter } from 'next/router'; 3 4export async function getStaticPaths() { 5 const res = await fetch('https://api.example.com/posts'); 6 const posts = await res.json(); 7 8 const paths = posts.map((post) => ({ 9 params: { id: post.id.toString() }, 10 })); 11 12 return { 13 paths, 14 fallback: false, 15 }; 16} 17 18export async function getStaticProps({ params }) { 19 const res = await fetch(`https://api.example.com/posts/${params.id}`); 20 const post = await res.json(); 21 22 return { 23 props: { 24 post, 25 }, 26 }; 27} 28 29export default function PostPage({ post }) { 30 return ( 31 <div> 32 <h1>{post.title}</h1> 33 <p>{post.content}</p> 34 </div> 35 ); 36}
In this example:
• getStaticPaths: This function fetches a list of posts from an API endpoint and generates paths for each post based on its ID. The paths are then returned in the paths array. The fallback: false option means that any paths not returned by getStaticPaths will result in a 404 page.
• getStaticProps: This function fetches the data for each post based on the id parameter. The fetched data is passed to the PostPage component as props.
• PostPage Component: This React component receives the post data as props and renders the post title and content.
If you have a large number of dynamic pages or frequently changing content, you might want to use the fallback option in getStaticPaths to handle paths that weren't generated at build time. Here’s how you can modify the previous example to use fallback: true:
1// pages/posts/[id].js 2import { useRouter } from 'next/router'; 3 4export async function getStaticPaths() { 5 const res = await fetch('https://api.example.com/posts'); 6 const posts = await res.json(); 7 8 const paths = posts.map((post) => ({ 9 params: { id: post.id.toString() }, 10 })); 11 12 return { 13 paths, 14 fallback: true, 15 }; 16} 17 18export async function getStaticProps({ params }) { 19 const res = await fetch(`https://api.example.com/posts/${params.id}`); 20 const post = await res.json(); 21 22 return { 23 props: { 24 post, 25 }, 26 revalidate: 10, // Re-generate the page at most once every 10 seconds 27 }; 28} 29 30export default function PostPage({ post }) { 31 const router = useRouter(); 32 33 // Show a loading state until the page is generated 34 if (router.isFallback) { 35 return <div>Loading...</div>; 36 } 37 38 return ( 39 <div> 40 <h1>{post.title}</h1> 41 <p>{post.content}</p> 42 </div> 43 ); 44}
In this updated example:
• fallback: true: This setting allows Next.js to serve a fallback page until the content is generated. If a user navigates to a path that hasn't been pre-rendered, Next.js will serve a loading state while it fetches the data and generates the page.
• router.isFallback: This property from the useRouter hook is used to check if the page is in a fallback state. If it is, you can display a loading message or spinner until the page is generated.
In Next.js, you can fetch data for static site generation using various methods. The most common approaches include:
Fetching from External APIs: This is a straightforward method where you fetch data from third-party APIs.
Fetching from Local Files: You can read data from local files such as JSON files.
Fetching from Databases: You can connect to a database and fetch data directly.
Using GraphQL: You can fetch data from a GraphQL API.
Combining Multiple Data Sources: Fetch data from multiple sources and combine them as needed.
Fetching data from external APIs is a common use case in Next.js. Here’s how you can do it:
1// pages/posts.js 2export async function getStaticProps() { 3 const res = await fetch('https://api.example.com/posts'); 4 const posts = await res.json(); 5 6 return { 7 props: { 8 posts, 9 }, 10 }; 11} 12 13export default function PostsPage({ posts }) { 14 return ( 15 <div> 16 <h1>Posts</h1> 17 <ul> 18 {posts.map((post) => ( 19 <li key={post.id}>{post.title}</li> 20 ))} 21 </ul> 22 </div> 23 ); 24}
In this example:
• getStaticProps fetches data from an external API endpoint and passes it to the PostsPage component.
You can also fetch data from local files, which is useful for data that doesn’t change often.
1// pages/data.js 2import fs from 'fs'; 3import path from 'path'; 4 5export async function getStaticProps() { 6 const filePath = path.join(process.cwd(), 'data.json'); 7 const jsonData = fs.readFileSync(filePath, 'utf8'); 8 const data = JSON.parse(jsonData); 9 10 return { 11 props: { 12 data, 13 }, 14 }; 15} 16 17export default function DataPage({ data }) { 18 return ( 19 <div> 20 <h1>Data from Local File</h1> 21 <pre>{JSON.stringify(data, null, 2)}</pre> 22 </div> 23 ); 24}
In this example:
• getStaticProps reads data from a local JSON file and passes it to the DataPage component.
To fetch data from a database, you would typically use a database client or ORM (Object-Relational Mapping) tool. Here’s an example using a mock database function:
1// lib/db.js 2import { Client } from 'some-database-client'; 3 4export async function fetchPosts() { 5 const client = new Client(); 6 await client.connect(); 7 const res = await client.query('SELECT * FROM posts'); 8 await client.end(); 9 return res.rows; 10}
1// pages/db-posts.js 2import { fetchPosts } from '../lib/db'; 3 4export async function getStaticProps() { 5 const posts = await fetchPosts(); 6 7 return { 8 props: { 9 posts, 10 }, 11 }; 12} 13 14export default function DbPostsPage({ posts }) { 15 return ( 16 <div> 17 <h1>Posts from Database</h1> 18 <ul> 19 {posts.map((post) => ( 20 <li key={post.id}>{post.title}</li> 21 ))} 22 </ul> 23 </div> 24 ); 25}
In this example:
• fetchPosts is a mock function that simulates fetching data from a database.
• getStaticProps calls fetchPosts and passes the data to the DbPostsPage component.
Fetching data from a GraphQL API is another powerful method, especially if your backend supports GraphQL.
1// lib/graphql.js 2import { GraphQLClient, gql } from 'graphql-request'; 3 4export async function fetchGraphQLPosts() { 5 const endpoint = 'https://api.example.com/graphql'; 6 const graphQLClient = new GraphQLClient(endpoint); 7 const query = gql` 8 { 9 posts { 10 id 11 title 12 } 13 } 14 `; 15 const data = await graphQLClient.request(query); 16 return data.posts; 17}
1// pages/graphql-posts.js 2import { fetchGraphQLPosts } from '../lib/graphql'; 3 4export async function getStaticProps() { 5 const posts = await fetchGraphQLPosts(); 6 7 return { 8 props: { 9 posts, 10 }, 11 }; 12} 13 14export default function GraphQLPostsPage({ posts }) { 15 return ( 16 <div> 17 <h1>Posts from GraphQL</h1> 18 <ul> 19 {posts.map((post) => ( 20 <li key={post.id}>{post.title}</li> 21 ))} 22 </ul> 23 </div> 24 ); 25}
In this example:
• fetchGraphQLPosts fetches data from a GraphQL endpoint.
• getStaticProps calls fetchGraphQLPosts and passes the data to the GraphQLPostsPage component.
Here are some additional examples to illustrate how you can fetch data from different sources in Next.js:
1// pages/multi-source.js 2export async function getStaticProps() { 3 const apiRes = await fetch('https://api.example.com/posts'); 4 const apiPosts = await apiRes.json(); 5 6 const filePath = path.join(process.cwd(), 'data.json'); 7 const fileData = JSON.parse(fs.readFileSync(filePath, 'utf8')); 8 9 return { 10 props: { 11 apiPosts, 12 fileData, 13 }, 14 }; 15} 16 17export default function MultiSourcePage({ apiPosts, fileData }) { 18 return ( 19 <div> 20 <h1>Combined Data</h1> 21 <h2>API Posts</h2> 22 <ul> 23 {apiPosts.map((post) => ( 24 <li key={post.id}>{post.title}</li> 25 ))} 26 </ul> 27 <h2>File Data</h2> 28 <pre>{JSON.stringify(fileData, null, 2)}</pre> 29 </div> 30 ); 31}
In this example:
• getStaticProps fetches data from both an external API and a local file, then combines the data and passes it to the MultiSourcePage component.
Static Site Generation with Next.js offers a powerful blend of speed, scalability, and SEO benefits, making it an excellent choice for developers looking to optimize their web applications. By leveraging `getStaticProps` and `getStaticPaths`, you can pre-render pages with the data they need, directly at build time. With the addition of Incremental Static Regeneration, Next.js also provides a flexible solution for keeping content fresh without sacrificing performance.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.