Next.js getStaticPaths
Introduction
When building websites with Next.js, one of the most powerful features is Static Site Generation (SSG), which allows you to pre-render pages at build time. This makes your website incredibly fast because pages are generated ahead of time and served as static HTML.
But what happens when you have dynamic routes in your application? For example, if you're building a blog with URLs like /posts/[slug]
or an e-commerce site with /products/[id]
?
That's where getStaticPaths
comes in. This function tells Next.js which dynamic routes to pre-render during the build process. It works hand-in-hand with getStaticProps
to enable static generation for pages with dynamic routes.
Understanding getStaticPaths
When do you need getStaticPaths?
You need getStaticPaths
when:
- You're using
getStaticProps
(static generation) - Your page has dynamic routes (uses
[parameter]
syntax in the filename)
For example, if you have a file called pages/posts/[slug].js
, you'll need to define getStaticPaths
to tell Next.js which slug
values to pre-render.
Basic Structure
Here's the basic structure of getStaticPaths
:
export async function getStaticPaths() {
return {
paths: [
{ params: { /* parameters for the dynamic route */ } },
// More objects with params...
],
fallback: true | false | 'blocking'
}
}
Implementing getStaticPaths
Let's create a simple blog where each post has its own page. We'll start with a file structure like this:
pages/
posts/
[slug].js
Basic Example
Here's how we would implement getStaticPaths
in our [slug].js
file:
// pages/posts/[slug].js
import React from 'react'
import { getAllPostSlugs, getPostData } from '../../lib/posts'
export default function Post({ post }) {
return (
<div>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</div>
)
}
export async function getStaticPaths() {
// Get all possible slugs for our posts
const slugs = getAllPostSlugs()
// Map the slugs to the format expected by Next.js
const paths = slugs.map((slug) => ({
params: { slug },
}))
// Return the paths and set fallback
return {
paths,
fallback: false,
}
}
export async function getStaticProps({ params }) {
// Get the data for a specific post
const post = await getPostData(params.slug)
return {
props: {
post,
},
}
}
In this example:
getStaticPaths
returns a list of possible values forslug
- Next.js will pre-render pages for each path returned
getStaticProps
uses theslug
parameter to fetch data for that specific post
The getAllPostSlugs function
For the example to work, we need to implement the getAllPostSlugs
function. Here's a simple implementation assuming our posts are stored as markdown files:
// lib/posts.js
import fs from 'fs'
import path from 'path'
const postsDirectory = path.join(process.cwd(), 'posts')
export function getAllPostSlugs() {
const fileNames = fs.readdirSync(postsDirectory)
// Returns an array of slugs (filenames without .md extension)
return fileNames.map((fileName) => {
return fileName.replace(/\.md$/, '')
})
}
export async function getPostData(slug) {
// Here you would parse the markdown file and return the data
// This is simplified for the example
const fullPath = path.join(postsDirectory, `${slug}.md`)
const fileContents = fs.readFileSync(fullPath, 'utf8')
// In a real app, you'd parse the markdown here
// For this example, we'll just return a simple object
return {
slug,
title: 'Sample Post Title',
content: '<p>This is sample content.</p>'
}
}
Understanding the Fallback Option
The fallback
property in getStaticPaths
is a powerful feature that determines how Next.js handles routes that weren't pre-rendered at build time.
fallback: false
When fallback
is set to false
:
export async function getStaticPaths() {
return {
paths: [
{ params: { slug: 'hello-world' } },
{ params: { slug: 'learn-nextjs' } }
],
fallback: false // <-- This is the important part
}
}
This means:
- Only the paths specified will be pre-rendered
- Any other path will result in a 404 page
- Good for small sites where all routes are known ahead of time
fallback: true
When fallback
is set to true
:
export async function getStaticPaths() {
return {
paths: [
{ params: { slug: 'hello-world' } },
{ params: { slug: 'learn-nextjs' } }
],
fallback: true // <-- Using true here
}
}
This means:
- The specified paths will be pre-rendered at build time
- For paths not generated at build time:
- Next.js will serve a "fallback" version of the page (usually a loading state)
- In the background, Next.js will generate the requested page
- When generation completes, the page will be cached for future requests
- You must handle the loading state in your component:
import { useRouter } from 'next/router'
export default function Post({ post }) {
const router = useRouter()
// If the page is still being generated, show a loading indicator
if (router.isFallback) {
return <div>Loading...</div>
}
return (
<div>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</div>
)
}
fallback: 'blocking'
When fallback
is set to 'blocking'
:
export async function getStaticPaths() {
return {
paths: [
{ params: { slug: 'hello-world' } },
{ params: { slug: 'learn-nextjs' } }
],
fallback: 'blocking' // <-- Using 'blocking' here
}
}
This means:
- Similar to
true
, but there's no fallback state - The first request to an ungenerated page will be rendered on the server (SSR)
- Once rendered, the page will be cached and served statically for future requests
- The user will see the fully rendered page on first load (no loading state needed)
Real-World Example: E-commerce Product Page
Here's a more practical example of using getStaticPaths
for an e-commerce website with product pages:
// pages/products/[id].js
import React from 'react'
import { getAllProductIds, getProductData } from '../../lib/products'
export default function Product({ product }) {
if (!product) {
return <div>Product not found</div>
}
return (
<div className="product-page">
<h1>{product.name}</h1>
<div className="product-image">
<img src={product.image} alt={product.name} />
</div>
<div className="product-price">${product.price.toFixed(2)}</div>
<div className="product-description">{product.description}</div>
<button className="add-to-cart">Add to Cart</button>
</div>
)
}
export async function getStaticPaths() {
// For this example, we'll only pre-render the most popular products
// and generate the rest on-demand
const popularProductIds = await getPopularProductIds()
const paths = popularProductIds.map((id) => ({
params: { id: id.toString() },
}))
return {
paths,
fallback: 'blocking', // Generate other products on-demand
}
}
export async function getStaticProps({ params }) {
try {
const product = await getProductData(params.id)
return {
props: {
product,
},
// Re-generate the page at most once every hour
revalidate: 3600,
}
} catch (error) {
// Handle the case where the product doesn't exist
return {
notFound: true,
}
}
}
This example demonstrates:
- Pre-rendering only popular product pages to optimize build time
- Using
fallback: 'blocking'
to generate other product pages on-demand - Using
revalidate
for Incremental Static Regeneration (ISR) to keep product data fresh - Handling non-existent products with
notFound: true
Advanced Techniques
Dynamic Path Generation with API Data
Often, you'll need to fetch paths from an API. Here's how you can do that:
export async function getStaticPaths() {
// Fetch data from an external API
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id.toString() },
}))
return { paths, fallback: 'blocking' }
}
Multiple Dynamic Parameters
For routes with multiple parameters, like /posts/[category]/[slug]
, your params
object should include all parameters:
// pages/posts/[category]/[slug].js
export async function getStaticPaths() {
return {
paths: [
{ params: { category: 'react', slug: 'getting-started' } },
{ params: { category: 'nextjs', slug: 'data-fetching' } },
],
fallback: false,
}
}
Generating a Large Number of Pages
When your site has thousands of pages, pre-rendering all of them can make build times very long. Here are some strategies:
- Only pre-render important pages and use
fallback: true
or'blocking'
for the rest - Use separate deployments for different sections of your site
- Implement pagination to limit the number of items per page
export async function getStaticPaths() {
// Only pre-render the first 100 posts at build time
const posts = await getAllPosts()
const popularPosts = posts.slice(0, 100)
const paths = popularPosts.map((post) => ({
params: { slug: post.slug },
}))
return {
paths,
fallback: 'blocking', // Generate other posts on-demand
}
}
Common Pitfalls and Best Practices
1. Parameter Type Issues
Always convert numeric IDs to strings in your paths:
// ❌ Wrong
params: { id: 123 }
// ✅ Correct
params: { id: '123' }
2. Build Time Considerations
Be mindful of build time when pre-rendering many pages. Consider:
- Pre-render only the most important pages at build time
- Use
fallback: true
or'blocking'
for less important pages - Implement Incremental Static Regeneration (ISR) with
revalidate
3. Catching 404s
For pages that might not exist, handle them properly:
export async function getStaticProps({ params }) {
const post = await getPostData(params.slug)
// If the post doesn't exist, return notFound
if (!post) {
return {
notFound: true, // This will return a 404 page
}
}
return {
props: { post },
}
}
Summary
getStaticPaths
is a powerful Next.js function that enables static generation for dynamic routes. It works by:
- Defining which paths to pre-render at build time
- Working in conjunction with
getStaticProps
to fetch data for each path - Providing fallback strategies for paths not pre-rendered at build time
Understanding when and how to use getStaticPaths
will help you build performant Next.js applications that scale well, with the right balance between build-time generation and on-demand rendering.
Additional Resources
Exercises
- Create a blog with markdown files and use
getStaticPaths
to generate pages for each post - Implement a product catalog with pagination using
getStaticPaths
- Build a documentation site with nested routes (
/docs/[category]/[topic]
) using multiple dynamic parameters - Create an e-commerce site that pre-renders only the top 20 products and uses
fallback: true
for the rest
By mastering getStaticPaths
, you'll be able to build highly performant Next.js applications that scale well, even with thousands of dynamic pages.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)