Enabling Incremental Static Regeneration (ISR) on Dynamic Pages with Next.js 14 App Router
Next.js 14's App Router revolutionized the way we build dynamic web applications. However, one common question arises: how do we leverage the power of Incremental Static Regeneration (ISR) on pages with dynamic routes?
This article will demystify ISR in Next.js 14's App Router, providing a clear understanding and practical examples for seamless implementation.
The Challenge of Dynamic Pages and ISR
Traditionally, ISR in Next.js was straightforward for static pages, generating pre-rendered HTML at build time and refreshing it on a schedule. Dynamic routes, however, introduce complexities, as their content is dependent on variables or user input.
Let's consider a scenario where we build a blog application. Each blog post has a unique slug (e.g., /blog/my-first-post
) and needs to be generated individually. If we solely rely on static generation, every post would be pre-rendered during build, potentially leading to inefficient resource usage and outdated content.
Here's a code snippet illustrating the problem with a simple Next.js 13 app:
// pages/blog/[slug].js
import { getStaticProps } from 'next';
export default function BlogPost({ post }) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
export const getStaticProps = async ({ params }) => {
const { slug } = params;
const post = await fetch(`https://api.example.com/posts/${slug}`).then(res => res.json());
return {
props: {
post
},
revalidate: 10 // Update every 10 seconds
};
};
This code uses getStaticProps
for static generation with revalidate
, but every post will be fetched and re-rendered every 10 seconds, even if its content hasn't changed. This is where ISR shines.
The Solution: ISR with App Router
Next.js 14's App Router introduces a powerful mechanism for enabling ISR on dynamic pages. This is achieved through the use of fetch
within loader
functions:
// app/blog/[slug]/page.js
import { fetchPost } from '../../lib/posts';
export const loader = async ({ params }) => {
const { slug } = params;
const post = await fetchPost(slug);
return { post };
};
export default function BlogPost({ data }) {
const { post } = data;
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
This code defines a loader
function that fetches the post based on the slug
parameter. Crucially, the loader
function is called both at build time and on every request, ensuring that the data is fresh and dynamically generated.
Key Benefits of ISR with App Router:
- Improved Performance: Only the dynamic content is refreshed, minimizing unnecessary re-renders.
- Dynamic Data Handling: Fetching data within the
loader
seamlessly integrates dynamic content. - Simplified Development: Concise and focused code thanks to
fetch
inloader
.
Example: Building a Dynamic Product Page
Let's create a simple e-commerce product page using Next.js 14's App Router and ISR:
// app/products/[productId]/page.js
import { fetchProduct } from '../../lib/products';
export const loader = async ({ params }) => {
const { productId } = params;
const product = await fetchProduct(productId);
return { product };
};
export default function ProductPage({ data }) {
const { product } = data;
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<img src={product.image} alt={product.name} />
</div>
);
}
This code utilizes fetchProduct
to retrieve product data dynamically. Each product page will be generated at build time and re-rendered only when its information changes, ensuring a faster and more efficient experience for users.
Conclusion
Next.js 14's App Router provides a seamless and efficient way to enable Incremental Static Regeneration on dynamic pages. By leveraging the loader
function and fetch
within it, developers can easily integrate dynamic content while still enjoying the benefits of ISR. This unlocks improved performance, simplified development, and a more engaging user experience.