ISR Fallback — Fallback Strategies for Uncached Pages
In this tutorial, you will learn about ISR Fallback. We cover key concepts, practical examples, and best practices to help you master this topic.
ISR fallback controls what happens when a page isn't pre-rendered: show a loading state, block until ready, or return 404.
What You'll Learn
By the end of this tutorial, you'll understand the three fallback strategies in Next.js ISR, how each affects user experience and performance, and how to choose the right one for your use case.
Why It Matters
Not every page can be pre-built at deployment time. For large catalogs or user-generated content, you need to handle pages that haven't been cached yet. The right fallback Strategy balances UX, server load, and SEO.
Real-World Use
An e-commerce site with 100,000 products pre-builds only the top 1,000 popular products. When a user visits an uncached product, fallback: 'blocking' generates the page server-side and caches it. The user waits slightly longer, but subsequent visitors get a cached page.
Fallback Options
graph TD
A[Request for
uncached page] --> B{fallback option?}
B -->|false| C[Return 404]
B -->|true| D[Show loading
skeleton]
B -->|'blocking'| E[Wait for SSR
generation]
D --> F[Generate page
in background]
F --> G[Cache HTML
for future]
E --> G
G --> H[Serve cached
on next request]
style C fill:#e74c3c,color:#fff
style D fill:#f39c12,color:#fff
style E fill:#3498db,color:#fff
style G fill:#27ae60,color:#fff
Fallback: false (Strict)
// pages/products/[id].js — Strict: only pre-built paths
export async function getStaticPaths() {
// Must return ALL possible paths
const products = await fetchAllProducts();
const paths = products.map(p => ({
params: { id: String(p.id) }
}));
return {
paths, // Only these paths work
fallback: false // Everything else = 404
};
}
export async function getStaticProps({ params }) {
const product = await fetchProduct(params.id);
if (!product) {
return { notFound: true };
}
return {
props: { product },
revalidate: 60
};
}
// Usage: Every product must be known at build time.
// Unknown paths return 404 immediately.
// Best for: Small, predictable content sets.
Fallback: true (Lazy Generation)
// pages/blog/[slug].js — Lazy: generate on demand
export async function getStaticPaths() {
// Pre-build only popular posts
const popularPosts = await fetchPopularPosts(10);
const paths = popularPosts.map(p => ({
params: { slug: p.slug }
}));
return {
paths, // Pre-built popular posts
fallback: true // Generate unknown paths on demand
};
}
// Component must handle fallback state
export default function BlogPost({ post, isFallback }) {
if (isFallback) {
// Show loading skeleton while page generates
return (
<div className="skeleton">
<div className="skeleton-title" />
<div className="skeleton-content">
<div className="skeleton-line" />
<div className="skeleton-line" />
<div className="skeleton-line short" />
</div>
</div>
);
}
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
export async function getStaticProps({ params }) {
const post = await fetchPost(params.slug);
if (!post) {
return { notFound: true };
}
return {
props: { post },
revalidate: 300
};
}
Fallback: 'blocking' (SSR Generation)
// pages/products/[id].js — Blocking: wait for generation
export async function getStaticPaths() {
// Pre-build only featured products
const featured = await fetchFeaturedProducts();
const paths = featured.map(p => ({
params: { id: String(p.id) }
}));
return {
paths, // Pre-built featured products
fallback: 'blocking' // Server-render unknown paths
};
}
// No loading state needed — request waits for render
export default function ProductPage({ product }) {
return (
<div>
<h1>{product.name}</h1>
<p className="price">${product.price}</p>
<p>{product.description}</p>
<small>SKU: {product.sku}</small>
</div>
);
}
export async function getStaticProps({ params }) {
const product = await fetchProduct(params.id);
if (!product) {
return { notFound: true };
}
return {
props: { product },
revalidate: 60
};
}
Choosing the Right Fallback
// Decision helper for fallback choice
function chooseFallback(siteType, pageCount, trafficPattern) {
const configs = {
blog: {
recommendation: 'blocking',
reason: 'SEO-critical, user waits once, cached forever',
preBuildRatio: 0.1 // Pre-build top 10% of posts
},
ecommerce: {
recommendation: 'blocking',
reason: 'Product pages must be indexable, one-time wait',
preBuildRatio: 0.05 // Pre-build top 5% of products
},
dashboard: {
recommendation: true,
reason: 'Loading state is acceptable, SEO not critical',
preBuildRatio: 0
},
docs: {
recommendation: false,
reason: 'All pages known at build time, no unknown paths',
preBuildRatio: 1.0 // Pre-build all pages
}
};
return configs[siteType] || configs.blog;
}
// Example usage
const config = chooseFallback('blog', 10000, 'high');
console.log(`Use fallback: ${config.recommendation}`);
console.log(`Pre-build ratio: ${config.preBuildRatio * 100}%`);
Fallback Performance Comparison
// Benchmark different fallback strategies
const fallbackBenchmark = {
'fallback: false': {
pros: ['Fastest response', 'No server load', 'Predictable'],
cons: ['404 for unknown paths', 'All paths must be known at build'],
ttfb: '~50ms (from CDN)',
useWhen: 'All content is known at build time'
},
'fallback: true': {
pros: ['Immediate UI response', 'Pages cached after generation'],
cons: ['Loading state needed', 'SEO concerns', 'Complex UI'],
ttfb: '~50ms (skeleton) + ~500ms (render)',
useWhen: 'SEO not critical, loading state acceptable'
},
'fallback: blocking': {
pros: ['Best for SEO', 'Simple component code', 'Cached after first visit'],
cons: ['First user waits for render', 'Potential timeout for slow fetches'],
ttfb: '~500-2000ms (first visit)',
useWhen: 'SEO is critical, content must be indexable'
}
};
Common Mistakes
- Using fallback: true for SEO-critical pages. Search engines don't execute JavaScript well during crawling. True fallback shows an empty skeleton to crawlers. Use 'blocking' for SEO.
- Not checking isFallback in the component. With fallback: true, the component renders before the page is ready. Check isFallback to show a loading state.
- Pre-rendering too many paths unnecessarily. If only 10% of products get traffic, pre-building all 100K wastes build time. Pre-build the popular ones, use fallback for the rest.
- Using fallback: false for dynamic content. If new content is created after deployment, fallback: false makes it inaccessible until the next build. Use 'blocking' for dynamic sites.
- Setting fallback when getStaticPaths returns empty array. With empty paths and fallback: false, every request returns 404. With fallback: true or 'blocking', pages still generate.
Practice Questions
- What are the three fallback options available in Next.js getStaticPaths?
- How does fallback: 'blocking' differ from fallback: true in terms of user experience?
- When should you use fallback: false?
- Why is fallback: 'blocking' better for SEO than fallback: true?
- How does fallback interact with ISR revalidation?
Challenge: Build a product catalog with mixed fallback strategies: pre-build the top 10 products (popular), use fallback: 'blocking' for the remaining 90, implement a fallback: true page for comparison, and benchmark the TTFB for each approach.
FAQ
Mini Project
Create a comparison site that demonstrates all three fallback strategies: 50 pages using fallback: false, 50 using fallback: true (with loading skeletons), and 50 using fallback: 'blocking'. Measure TTFB, server load, and SEO indexability for each.
What's Next
You've mastered ISR fallback. Now learn about ISR Dynamic Routes to combine ISR with complex URL patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro