Dynamic routes can be pre-generated at build time for incredible performance! Use generateStaticParams to tell Next.js which dynamic route parameters to generate as static HTML pages. Pre-generate blog posts, product pages, documentation routes—anything with predictable paths. Combine with ISR for static speed with periodic updates, or on-demand generation for infinite scalability. Master static generation and build lightning-fast sites!
Basic generateStaticParams
Simple Dynamic Route
// Generate static params at build time
export async function generateStaticParams() {
// Fetch all blog posts
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
// Return array of params objects
return posts.map((post) => ({
slug: post.slug,
}));
}
// This page will be generated for each slug
export default async function BlogPostPage({
params,
}: {
params: { slug: string };
}) {
// Fetch the specific post
const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(r => r.json());
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// At build time, Next.js:
// 1. Calls generateStaticParams()
// 2. Gets array of slugs: ['post-1', 'post-2', 'post-3']
// 3. Pre-generates: /blog/post-1, /blog/post-2, /blog/post-3
// 4. Each page is static HTML
// ✅ All pages generated at build
// ✅ Served from CDN
// ✅ Lightning-fast load timesReturn Value Format
// generateStaticParams must return array of objects
export async function generateStaticParams() {
return [
{ slug: 'first-post' }, // /blog/first-post
{ slug: 'second-post' }, // /blog/second-post
{ slug: 'third-post' }, // /blog/third-post
];
}
// ✅ Array of objects
// ✅ Keys match dynamic segment names
// ✅ Values are strings
// For [id] route:
return [
{ id: '1' },
{ id: '2' },
{ id: '3' },
];
// For [slug] route:
return [
{ slug: 'about' },
{ slug: 'contact' },
];With Database
import { db } from '@/lib/db';
export async function generateStaticParams() {
// Fetch from database
const products = await db.products.findMany({
select: { id: true },
});
return products.map((product) => ({
id: product.id,
}));
}
export default async function ProductPage({
params,
}: {
params: { id: string };
}) {
const product = await db.products.findUnique({
where: { id: params.id },
});
return (
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
</div>
);
}
// ✅ Fetch IDs from database
// ✅ Pre-generate all product pages
// ✅ Static HTML for each productNested Dynamic Routes
Multiple Dynamic Segments
// Generate params for nested routes
export async function generateStaticParams() {
const categories = await fetch('https://api.example.com/categories').then(r => r.json());
// Generate all category/product combinations
const params = [];
for (const category of categories) {
const products = await fetch(`https://api.example.com/products?category=${category.slug}`)
.then(r => r.json());
for (const product of products) {
params.push({
category: category.slug,
id: product.id,
});
}
}
return params;
}
export default async function ProductPage({
params,
}: {
params: { category: string; id: string };
}) {
const product = await fetch(
`https://api.example.com/products/${params.id}`
).then(r => r.json());
return (
<div>
<p>Category: {params.category}</p>
<h1>{product.name}</h1>
</div>
);
}
// Generated routes:
// /products/electronics/laptop-1
// /products/electronics/phone-2
// /products/clothing/shirt-3
// /products/clothing/pants-4
// ✅ All combinations pre-generated
// ✅ Static nested routesParent-Child Generation
// Generate child params based on parent
export async function generateStaticParams() {
// First, get all categories
const categories = await fetch('https://api.example.com/categories').then(r => r.json());
// Then, get posts for each category
const allParams = await Promise.all(
categories.map(async (category) => {
const posts = await fetch(
`https://api.example.com/posts?category=${category.slug}`
).then(r => r.json());
return posts.map((post) => ({
category: category.slug,
slug: post.slug,
}));
})
);
// Flatten array of arrays
return allParams.flat();
}
export default async function BlogPostPage({
params,
}: {
params: { category: string; slug: string };
}) {
const post = await fetch(
`https://api.example.com/posts/${params.slug}`
).then(r => r.json());
return (
<article>
<p className="text-sm text-gray-600">Category: {params.category}</p>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// ✅ Hierarchical generation
// ✅ Promise.all for parallel fetching
// ✅ Flat array of paramsCatch-All Routes
Catch-All Segments
// Generate params for catch-all routes
export async function generateStaticParams() {
const docs = await fetch('https://api.example.com/docs').then(r => r.json());
return docs.map((doc) => ({
// slug is an array for catch-all routes
slug: doc.path.split('/'), // "getting-started/installation" → ["getting-started", "installation"]
}));
}
export default async function DocsPage({
params,
}: {
params: { slug: string[] };
}) {
// Join slug array back to path
const path = params.slug.join('/');
const doc = await fetch(`https://api.example.com/docs/${path}`).then(r => r.json());
return (
<article>
<h1>{doc.title}</h1>
<div dangerouslySetInnerHTML={{ __html: doc.content }} />
</article>
);
}
// Generated routes:
// /docs/getting-started/installation
// /docs/getting-started/configuration
// /docs/guides/deployment
// /docs/guides/optimization
// ✅ slug is an array for catch-all
// ✅ Split paths into arrays
// ✅ Supports nested documentationOptional Catch-All
// Optional catch-all: matches / and /...
export async function generateStaticParams() {
const categories = await fetch('https://api.example.com/categories').then(r => r.json());
return [
// Root page (no slug)
{ slug: [] }, // Matches /shop
// Category pages
...categories.map((cat) => ({
slug: [cat.slug], // Matches /shop/electronics
})),
// Subcategory pages
...categories.flatMap((cat) =>
cat.subcategories.map((sub) => ({
slug: [cat.slug, sub.slug], // Matches /shop/electronics/laptops
}))
),
];
}
export default async function ShopPage({
params,
}: {
params: { slug?: string[] };
}) {
// Handle different levels
if (!params.slug || params.slug.length === 0) {
return <div>Shop Home</div>;
}
if (params.slug.length === 1) {
return <div>Category: {params.slug[0]}</div>;
}
return <div>Subcategory: {params.slug.join(' > ')}</div>;
}
// Matches:
// /shop (slug is undefined or [])
// /shop/electronics (slug is ["electronics"])
// /shop/electronics/laptops (slug is ["electronics", "laptops"])
// ✅ Optional catch-all
// ✅ Include empty array for root
// ✅ Handle all path depthsdynamicParams Configuration
Default Behavior (dynamicParams: true)
// Default: generate unknown params on-demand
export const dynamicParams = true; // Default, can omit
export async function generateStaticParams() {
return [
{ slug: 'first-post' },
{ slug: 'second-post' },
];
}
export default function BlogPostPage({ params }: { params: { slug: string } }) {
return <div>Post: {params.slug}</div>;
}
// Pre-generated at build:
// ✅ /blog/first-post
// ✅ /blog/second-post
// First visit to new post:
// ✅ /blog/third-post → Generated on-demand, then cached
// ✅ Flexible for new content
// ✅ No 404 for missing params
// ✅ Generated once, cached foreverStrict Mode (dynamicParams: false)
// Strict: only pre-generated params exist
export const dynamicParams = false; // Disable on-demand generation
export async function generateStaticParams() {
return [
{ id: '1' },
{ id: '2' },
{ id: '3' },
];
}
export default function ProductPage({ params }: { params: { id: string } }) {
return <div>Product: {params.id}</div>;
}
// Pre-generated at build:
// ✅ /products/1
// ✅ /products/2
// ✅ /products/3
// Visit to unknown product:
// ❌ /products/4 → 404 Not Found
// ❌ /products/5 → 404 Not Found
// ✅ Controlled set of routes
// ✅ No surprise pages
// ✅ Build time validationUse Cases for dynamicParams: false
// Use dynamicParams: false when:
// 1. Fixed set of routes (documentation)
export const dynamicParams = false;
export async function generateStaticParams() {
return [
{ slug: 'getting-started' },
{ slug: 'api-reference' },
{ slug: 'deployment' },
];
}
// 2. Security/privacy (only show specific content)
export const dynamicParams = false;
export async function generateStaticParams() {
// Only public user profiles
const publicUsers = await db.users.findMany({
where: { isPublic: true },
select: { id: true },
});
return publicUsers.map(u => ({ id: u.id }));
}
// 3. Build-time validation (catch missing data)
export const dynamicParams = false;
export async function generateStaticParams() {
const products = await db.products.findMany();
if (products.length === 0) {
throw new Error('No products found!');
}
return products.map(p => ({ id: p.id }));
}
// ✅ Controlled routes
// ✅ Build-time validation
// ✅ Security enforcementgenerateStaticParams File Structure
Dynamic routes with static generation
Select a file or folder to see details
Combining with ISR
Static Generation + Revalidation
// Pre-generate at build, revalidate periodically
export const revalidate = 3600; // 1 hour
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return posts.map((post) => ({
slug: post.slug,
}));
}
export default async function BlogPostPage({
params,
}: {
params: { slug: string };
}) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(r => r.json());
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// Build time:
// ✅ Pre-generate all posts
// After 1 hour:
// ✅ First visit triggers revalidation
// ✅ Updated content served to next visitors
// ✅ Background regeneration
// ✅ Fast initial load (static)
// ✅ Fresh content (ISR)
// ✅ Best of both worldsOn-Demand Revalidation
import { revalidatePath } from 'next/cache';
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const { path } = await request.json();
try {
// Revalidate specific path
revalidatePath(path);
return NextResponse.json({
revalidated: true,
path,
now: Date.now(),
});
} catch (err) {
return NextResponse.json(
{ revalidated: false },
{ status: 500 }
);
}
}
// Trigger from CMS webhook:
// POST /api/revalidate
// { "path": "/blog/my-post" }
// ✅ Instant content updates
// ✅ No waiting for revalidation period
// ✅ Cache cleared immediately// app/api/cms-webhook/route.ts
export async function POST(request: Request) {
const data = await request.json();
// Validate webhook (check secret, signature, etc.)
if (data.event === 'post.published') {
// Revalidate the specific post
await fetch(`${process.env.NEXT_PUBLIC_URL}/api/revalidate`, {
method: 'POST',
body: JSON.stringify({
path: `/blog/${data.post.slug}`,
}),
});
}
return new Response('OK');
}
// When content published in CMS:
// 1. CMS calls webhook
// 2. Webhook calls revalidate API
// 3. Post page updated immediately
// ✅ Instant updates
// ✅ Still served from cache
// ✅ No build neededAdvanced Patterns
Parallel Generation
export async function generateStaticParams() {
// Fetch data in parallel for faster builds
const [posts, authors, categories] = await Promise.all([
fetch('https://api.example.com/posts').then(r => r.json()),
fetch('https://api.example.com/authors').then(r => r.json()),
fetch('https://api.example.com/categories').then(r => r.json()),
]);
// Filter valid posts (have author and category)
const validPosts = posts.filter(post => {
const hasAuthor = authors.some(a => a.id === post.authorId);
const hasCategory = categories.some(c => c.id === post.categoryId);
return hasAuthor && hasCategory;
});
return validPosts.map((post) => ({
id: post.id,
}));
}
// ✅ Parallel fetching
// ✅ Data validation
// ✅ Faster buildsConditional Generation
export async function generateStaticParams() {
const products = await fetch('https://api.example.com/products').then(r => r.json());
// Only generate published products
const publishedProducts = products.filter(p => p.status === 'published');
// Limit generation in development
if (process.env.NODE_ENV === 'development') {
return publishedProducts.slice(0, 5).map(p => ({ id: p.id }));
}
// Generate all in production
return publishedProducts.map(p => ({ id: p.id }));
}
// Development: Only 5 pages for faster builds
// Production: All pages for complete site
// ✅ Faster dev builds
// ✅ Complete production builds
// ✅ Conditional logicPaginated Generation
export async function generateStaticParams() {
const { total } = await fetch('https://api.example.com/posts/count').then(r => r.json());
const postsPerPage = 10;
const totalPages = Math.ceil(total / postsPerPage);
// Generate page numbers
return Array.from({ length: totalPages }, (_, i) => ({
page: String(i + 1),
}));
}
export default async function BlogPagePage({
params,
}: {
params: { page: string };
}) {
const page = parseInt(params.page, 10);
const posts = await fetch(
`https://api.example.com/posts?page=${page}&limit=10`
).then(r => r.json());
return (
<div>
<h1>Blog - Page {page}</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
);
}
// Generated:
// /blog/page/1
// /blog/page/2
// /blog/page/3
// ...
// ✅ Paginated content
// ✅ All pages static
// ✅ Fast navigationLocalized Routes
export async function generateStaticParams() {
const locales = ['en', 'es', 'fr', 'de'];
// Get posts for each locale
const allParams = await Promise.all(
locales.map(async (locale) => {
const posts = await fetch(
`https://api.example.com/posts?locale=${locale}`
).then(r => r.json());
return posts.map((post) => ({
locale,
slug: post.slug,
}));
})
);
return allParams.flat();
}
export default async function LocalizedBlogPost({
params,
}: {
params: { locale: string; slug: string };
}) {
const post = await fetch(
`https://api.example.com/posts/${params.slug}?locale=${params.locale}`
).then(r => r.json());
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// Generated:
// /en/blog/getting-started
// /es/blog/getting-started
// /fr/blog/getting-started
// /de/blog/getting-started
// ✅ Multi-language support
// ✅ All translations static
// ✅ SEO-friendlygenerateStaticParams Best Practices
1. Fetch Efficiently
// ✅ GOOD: Fetch only IDs
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts?fields=id,slug').then(r => r.json());
return posts.map(p => ({ slug: p.slug }));
}
// ❌ BAD: Fetch full data
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
// Fetches all content, images, etc. - wasteful!
return posts.map(p => ({ slug: p.slug }));
}
// Only fetch what you need for params2. Use Parallel Fetching
// ✅ GOOD: Parallel requests
export async function generateStaticParams() {
const [posts, products] = await Promise.all([
fetch('/api/posts').then(r => r.json()),
fetch('/api/products').then(r => r.json()),
]);
return [...posts, ...products];
}
// ❌ BAD: Sequential requests
export async function generateStaticParams() {
const posts = await fetch('/api/posts').then(r => r.json());
const products = await fetch('/api/products').then(r => r.json());
return [...posts, ...products];
}
// Parallel is much faster3. Limit Development Generation
// ✅ GOOD: Limit in development
export async function generateStaticParams() {
const posts = await fetch('/api/posts').then(r => r.json());
if (process.env.NODE_ENV === 'development') {
return posts.slice(0, 10).map(p => ({ slug: p.slug }));
}
return posts.map(p => ({ slug: p.slug }));
}
// Development: Only 10 pages
// Production: All pages
// Faster dev builds, complete prod builds4. Handle Errors Gracefully
// ✅ GOOD: Error handling
export async function generateStaticParams() {
try {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return posts.map(p => ({ slug: p.slug }));
} catch (error) {
console.error('Failed to generate static params:', error);
// Return empty array or fallback
return [];
// Or throw to fail the build
// throw new Error('Cannot build without posts');
}
}
// Handle fetch failures properly5. Validate Params
// ✅ GOOD: Validate params
export async function generateStaticParams() {
const posts = await fetch('/api/posts').then(r => r.json());
// Filter invalid slugs
const validPosts = posts.filter(post => {
return post.slug &&
typeof post.slug === 'string' &&
post.slug.length > 0 &&
/^[a-z0-9-]+$/.test(post.slug); // Valid slug format
});
if (validPosts.length === 0) {
throw new Error('No valid posts found!');
}
return validPosts.map(p => ({ slug: p.slug }));
}
// Validate data before generatingKey Takeaways
- generateStaticParams - pre-generate dynamic routes at build
- Return array of params - objects with keys matching segments
- dynamicParams: true - generate unknown params on-demand (default)
- dynamicParams: false - only pre-generated params exist
- Combine with ISR - static + periodic revalidation
- Nested routes - return all combinations
- Catch-all routes - slug is an array
- Optimize builds - fetch only IDs, use parallel requests
What's Next?
You've mastered generateStaticParams! Next, we'll explore Build and Production Optimization—analyzing bundle sizes, optimizing images and fonts, tree shaking, code splitting, lazy loading, and preparing your application for production. You'll learn to build the fastest possible Next.js apps!
We'll cover bundle analysis, performance optimization, lazy loading, and production best practices.
⚡ Build Optimization Tip
Use generateStaticParams for known routes (blog posts, products, docs) to pre-generate at build time. Set dynamicParams: true for infinite scalability (new content on-demand), or false for controlled route sets. Combine with ISR for static speed + fresh content!