getStaticPaths — Generating Dynamic Routes at Build Time in Next.js
In this tutorial, you will learn about getstaticpaths. We cover key concepts, practical examples, and best practices to help you master this topic.
getStaticPaths defines which dynamic routes Next.js pre-renders at build time, generating static pages for each path in a dynamic segment.
What You'll Learn
By the end of this tutorial, you'll understand how getStaticPaths works with dynamic routes, how to use fallback strategies, generate paths from data sources, and optimize path generation for large sites.
Why It Matters
Dynamic routes like /posts/[slug] or /products/[id] are essential for content-driven sites. getStaticPaths tells Next.js which specific paths to pre-build, enabling SSG for dynamic-looking URLs.
Real-World Use
An e-commerce site has 10,000 products, each with a URL like /product/smartphone-x. getStaticPaths fetches all product slugs from the API during build and generates static pages for each, giving every product page the speed of a static site.
Dynamic Routing Flow
graph TD
A[Page: [slug].js] --> B[getStaticPaths]
B --> C{Fetch all paths
from data source}
C --> D[Return paths array]
D --> E{fallback option?}
E -->|false| F[Only pre-built
paths work]
E -->|true| G[Pre-built paths +
lazy generate]
E -->|blocking| H[Pre-built paths +
SSR fallback]
F --> I[200 for known paths
404 for unknown]
G --> J[200 for known paths
Generate + cache unknown]
H --> K[200 for known paths
SSR + cache unknown]
I --> L[getStaticProps executes
for each known path]
J --> L
K --> L
L --> M[Static HTML files]
style B fill:#4a90d9,color:#fff
style F fill:#e74c3c,color:#fff
style G fill:#f39c12,color:#fff
style H fill:#27ae60,color:#fff
Basic Dynamic Routes
// pages/posts/[id].js
export default function Post({ post }) {
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
<small>Post ID: {post.id}</small>
</article>
);
}
// Define which paths to pre-render
export async function getStaticPaths() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await res.json();
const paths = posts.map(post => ({
params: { id: String(post.id) }
}));
return {
paths,
fallback: false // 404 for unknown paths
};
}
export async function getStaticProps({ params }) {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.id}`
);
const post = await res.json();
return { props: { post } };
}
Fallback Strategies
// pages/products/[slug].js
// Fallback: false — only pre-built paths (strict)
export async function getStaticPathsStrict() {
const products = await fetchProducts(); // 50 products
const paths = products.map(p => ({
params: { slug: p.slug }
}));
return { paths, fallback: false };
}
// Fallback: true — lazy generation
export async function getStaticPathsLazy() {
// Pre-build only popular products (top 100)
const popularProducts = await fetchPopularProducts();
const paths = popularProducts.map(p => ({
params: { slug: p.slug }
}));
return { paths, fallback: true };
}
// With fallback: true, the component must handle loading state
export default function ProductPage({ product, isFallback }) {
if (isFallback) {
return <div className="skeleton">Loading product...</div>;
}
return (
<div>
<h1>{product.name}</h1>
<p className="price">${product.price}</p>
<p>{product.description}</p>
</div>
);
}
// Fallback: 'blocking' — SSR unknown paths
export async function getStaticPathsBlocking() {
const paths = await fetchAllPaths();
return { paths, fallback: 'blocking' };
}
// With 'blocking', no loading state needed.
// Requests wait for server-side render, result is cached.
Multi-Parameter Dynamic Routes
// pages/[category]/[product].js
export default function ProductPage({ category, product }) {
return (
<div>
<nav>
<a href={`/${category}`}>{category}</a> / {product.name}
</nav>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
// Generate paths for category + product combinations
export async function getStaticPaths() {
const categories = await fetch('https://api.example.com/categories')
.then(r => r.json());
const paths = [];
for (const category of categories) {
const products = await fetch(
`https://api.example.com/${category.slug}/products`
).then(r => r.json());
products.forEach(product => {
paths.push({
params: {
category: category.slug,
product: product.slug
}
});
});
}
return {
paths,
fallback: 'blocking'
};
}
export async function getStaticProps({ params }) {
const { category, product } = params;
const res = await fetch(
`https://api.example.com/${category}/${product}`
);
const data = await res.json();
return {
props: { category, product: data }
};
}
Common Mistakes
- Returning strings instead of string arrays for params.
params: { id: 1 }should beparams: { id: '1' }. Route params must always be strings. - Not using fallback for large datasets. Generating 100K paths at build time can take hours. Use
fallback: 'blocking'for infrequently accessed paths. - Returning all possible paths on every build. For large datasets, paginate or prioritize paths. Only pre-build high-traffic pages.
- Missing catch-all routes for nested content. Use
[...slug].jsfor multi-level paths like/docs/guides/getting-started. - Not handling empty paths array. If your data source returns nothing, verify the path generation logic. Empty paths mean no pages.
Practice Questions
- What does getStaticPaths return and how does it control routing?
- What are the three fallback options and when should each be used?
- How does getStaticPaths communicate with getStaticProps?
- Can getStaticPaths handle nested dynamic routes?
- How does fallback: true affect the user experience?
Challenge: Build a product catalog with 3 categories, each containing 5 products. Use multi-parameter dynamic routes ([category]/[product].js), fetch all paths from an API, implement fallback: 'blocking', and display proper loading or error states.
FAQ
Mini Project
Build a recipe site with dynamic routes: implement [cuisine]/[recipe].js, use getStaticPaths to fetch all cuisine-recipe combinations, pre-build popular recipes with fallback: 'blocking', and show a fallback skeleton for uncached pages.
What's Next
You understand dynamic routing with getStaticPaths. Now explore Incremental Static Regeneration to update static content without full rebuilds.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro