Skip to main content

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:

  1. You're using getStaticProps (static generation)
  2. 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:

javascript
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:

javascript
// 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:

  1. getStaticPaths returns a list of possible values for slug
  2. Next.js will pre-render pages for each path returned
  3. getStaticProps uses the slug 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:

javascript
// 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:

javascript
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:

javascript
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:
javascript
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':

javascript
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:

javascript
// 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:

  1. Pre-rendering only popular product pages to optimize build time
  2. Using fallback: 'blocking' to generate other product pages on-demand
  3. Using revalidate for Incremental Static Regeneration (ISR) to keep product data fresh
  4. 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:

javascript
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:

javascript
// 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:

  1. Only pre-render important pages and use fallback: true or 'blocking' for the rest
  2. Use separate deployments for different sections of your site
  3. Implement pagination to limit the number of items per page
javascript
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:

javascript
// ❌ 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:

javascript
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:

  1. Defining which paths to pre-render at build time
  2. Working in conjunction with getStaticProps to fetch data for each path
  3. 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

  1. Create a blog with markdown files and use getStaticPaths to generate pages for each post
  2. Implement a product catalog with pagination using getStaticPaths
  3. Build a documentation site with nested routes (/docs/[category]/[topic]) using multiple dynamic parameters
  4. 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! :)