ISR Dynamic Routes — Using ISR with Dynamic Route Parameters
In this tutorial, you will learn about ISR Dynamic Routes. We cover key concepts, practical examples, and best practices to help you master this topic.
ISR with dynamic routes combines parameterized URLs with background revalidation, updating individual parameterized pages without full rebuilds.
What You'll Learn
By the end of this tutorial, you'll understand how ISR works with dynamic routes, how to combine getStaticPaths with ISR, handle many parameter combinations, and optimize dynamic ISR for large datasets.
Why It Matters
Dynamic routes like /products/[category]/[id] create a combinatorial explosion of possible paths. ISR lets you pre-build popular routes and generate others on demand, keeping build times manageable while serving all possible URLs.
Real-World Use
A travel site has routes like /destinations/[country]/[city]/[hotel]. With 100 countries, 1000 cities, and 10,000 hotels, pre-building all combinations is impossible. ISR pre-builds top hotels and generates others via fallback.
Dynamic ISR Architecture
graph TD
A[getStaticPaths] --> B[Pre-build popular
parameter combinations]
B --> C[Static HTML
for popular routes]
A --> D[fallback: blocking
for unknown routes]
D --> E[Request arrives
with new params]
E --> F[Server renders page
with these params]
F --> G[Cache result
with ISR revalidate]
G --> H[Future requests
serve cached page]
C --> I[All routes eventually
cached via ISR]
H --> I
style A fill:#4a90d9,color:#fff
style D fill:#e67e22,color:#fff
style I fill:#27ae60,color:#fff
Multiple Dynamic Parameters
// pages/[category]/[product].js — Multi-param dynamic ISR
export default function ProductPage({ product, category, params }) {
return (
<div>
<nav className="breadcrumb">
<a href="/">Home</a> /
<a href={`/${category}`}>{category}</a> /
<span>{product.name}</span>
</nav>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p className="price">${product.price}</p>
<small>
Route: /{category}/{product.slug}
<br />
Generated: {new Date(params.generatedAt).toLocaleString()}
</small>
</div>
);
}
export async function getStaticPaths() {
// Pre-build top products from each category
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());
// Only pre-build top 5 per category
products.slice(0, 5).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}`
);
if (!res.ok) return { notFound: true };
const productData = await res.json();
return {
props: {
product: productData,
category,
params: { generatedAt: Date.now() }
},
revalidate: 60
};
}
Catch-All Dynamic Routes
// pages/docs/[...slug].js — Catch-all dynamic ISR
export default function DocPage({ doc, params }) {
return (
<article>
<h1>{doc.title}</h1>
<div className="breadcrumbs">
{params.slug.map((part, i) => (
<span key={i}>
{i > 0 && ' / '}
<a href={`/docs/${params.slug.slice(0, i + 1).join('/')}`}>
{part}
</a>
</span>
))}
</div>
<div>{doc.content}</div>
</article>
);
}
export async function getStaticPaths() {
// Fetch all doc paths from the API
const paths = await fetch('https://api.example.com/docs/paths')
.then(r => r.json());
return {
paths: paths.map(p => ({
params: { slug: p.split('/') }
})),
fallback: 'blocking'
};
}
export async function getStaticProps({ params }) {
const slugPath = params.slug.join('/');
const res = await fetch(`https://api.example.com/docs/${slugPath}`);
if (!res.ok) return { notFound: true };
const doc = await res.json();
return {
props: { doc, params },
revalidate: 300
};
}
Optimized Path Generation for Large Datasets
// lib/optimized-paths.js — Batch path generation
const BATCH_SIZE = 100;
const CONCURRENCY = 5;
async function* generateAllPaths(totalCount) {
for (let offset = 0; offset < totalCount; offset += BATCH_SIZE) {
const batch = await fetchPathsBatch(offset, BATCH_SIZE);
yield batch;
}
}
async function fetchPathsBatch(offset, limit) {
const res = await fetch(
`https://api.example.com/products?offset=${offset}&limit=${limit}&fields=slug,category`
);
const data = await res.json();
return data.items.map(item => ({
params: {
category: item.category.slug,
product: item.slug
}
}));
}
// In getStaticPaths
export async function getStaticPaths() {
// Get total count
const { total } = await fetch('https://api.example.com/products/count')
.then(r => r.json());
const paths = [];
const generator = generateAllPaths(total);
for await (const batch of generator) {
paths.push(...batch);
// Yield to event loop every 500 paths
if (paths.length % 500 === 0) {
console.log(`Generated ${paths.length}/${total} paths...`);
await new Promise(r => setTimeout(r, 0));
}
}
console.log(`Total paths generated: ${paths.length}`);
return {
paths,
fallback: 'blocking'
};
}
Parameter Validation
// lib/validate-params.js — Dynamic route validation
const validCategories = new Set(['electronics', 'clothing', 'food', 'books']);
const MAX_SLUG_LENGTH = 100;
function validateProductParams(params) {
const errors = [];
// Validate category
if (!params.category || typeof params.category !== 'string') {
errors.push('Category is required and must be a string');
} else if (!validCategories.has(params.category)) {
errors.push(`Invalid category: ${params.category}`);
}
// Validate product slug
if (!params.product || typeof params.product !== 'string') {
errors.push('Product slug is required');
} else if (params.product.length > MAX_SLUG_LENGTH) {
errors.push('Product slug too long');
} else if (!/^[a-z0-9-]+$/.test(params.product)) {
errors.push('Product slug contains invalid characters');
}
return {
valid: errors.length === 0,
errors
};
}
// Usage in getStaticProps
export async function getStaticProps({ params }) {
const validation = validateProductParams(params);
if (!validation.valid) {
console.warn('Invalid params:', validation.errors);
return { notFound: true };
}
// Proceed with data fetching
// ...
}
Common Mistakes
- Pre-building too many dynamic route combinations. If you have 3 parameter types with 100 options each, that's 1M paths. Pre-build a subset and use fallback for the rest.
- Not validating dynamic parameters. Users can craft any URL. Validate parameters in getStaticProps to prevent unnecessary API calls and potential errors.
- Forgetting to include all required parameters. getStaticPaths must return params matching the dynamic segments. A missing parameter causes build errors.
- Using fallback: false with dynamic content. If new parameter combinations are added after deployment, fallback: false makes them inaccessible. Use 'blocking' for dynamic sets.
- Not Caching API responses in getStaticPaths. During development, every build re-fetches all paths. Cache the path list locally for faster iteration.
Practice Questions
- How do you combine ISR with dynamic route parameters?
- What is the challenge of using ISR with multiple dynamic parameters?
- How do you optimize path generation for large parameter spaces?
- How do you validate dynamic route parameters in getStaticProps?
- How does fallback: 'blocking' help with dynamic ISR routes?
Challenge: Build a multi-level dynamic ISR site with 3 parameter levels (category/subcategory/product), pre-build only the top 10% of combinations, use fallback: 'blocking' for the rest, implement parameter validation, and benchmark build time vs total routes available.
FAQ
Mini Project
Create a multi-level directory site with ISR: implement routes like /[country]/[state]/[city], pre-build top 5 cities per state for popular countries, use fallback: 'blocking' for unknown combinations, validate geolocation parameters, and benchmark the caching behavior.
What's Next
Dynamic ISR is mastered. Now learn how to use ISR with Databases to fetch and cache database content in static pages.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro