Fetching, Caching, and Revalidating Data in Next.js Static Site Generation

Static site generation (SSG) is a powerful feature in Next.js that allows developers to create fast, scalable websites by pre-rendering pages at build time. These pages are cached and served directly to users, resulting in excellent performance and reduced server load. However, one challenge with SSG is keeping content fresh, especially for sites with dynamic data like blogs, e-commerce platforms, or news websites. Next.js addresses this with Incremental Static Regeneration (ISR), a feature that enables static pages to be updated dynamically without requiring a full site rebuild.
In this article, we’ll explore how to fetch, cache, and revalidate data in Next.js using ISR. We’ll provide detailed explanations, dummy API code snippets, and practical examples to demonstrate how to implement ISR using Next.js’s built-in functions and other methods. The goal is to make these concepts accessible and easy to apply, whether you’re new to Next.js or an experienced developer.
Understanding Static Site Generation and ISR
Before diving into the implementation, let’s clarify what SSG and ISR are and how they fit into Next.js’s data-fetching strategies.
Static Site Generation (SSG): Pages are generated at build time using
getStaticProps
orgetStaticPaths
. The resulting HTML is cached and served directly, making it fast and SEO-friendly. However, static pages don’t update automatically when the underlying data changes, which can be a limitation for dynamic content.Server-Side Rendering (SSR): Pages are rendered on each request using
getServerSideProps
. This ensures fresh data but can be slower and more resource-intensive due to server-side processing.Incremental Static Regeneration (ISR): ISR combines the speed of SSG with the ability to update content dynamically. Pages are generated at build time, but you can specify a revalidation time after which the page is regenerated in the background when requested. ISR also supports on-demand revalidation, allowing manual updates via API calls.
ISR is particularly useful for scenarios where content changes periodically, such as blog posts, product listings, or news articles. It offers a balance between performance and freshness, making it a game-changer for modern web development.
Fetching Data with getStaticProps
The foundation of SSG and ISR in Next.js is the getStaticProps
function, which fetches data at build time and passes it as props to a page component. Let’s start with a simple example of a blog page that fetches posts from a dummy API.
Example: Fetching Blog Posts
Suppose we want to create a blog page that displays a list of posts fetched from JSONPlaceholder, a free API for testing. We’ll create a page at pages/blog.js
.
Here’s the code for the page component and getStaticProps
:
import React from 'react';
const Blog = ({ posts }) => {
return (
<div>
<h1>Blog Posts</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export async function getStaticProps() {
// Fetch data from external API
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await res.json();
return {
props: {
posts: posts.slice(0, 10), // Limit to first 10 posts
},
};
}
export default Blog;
In this example:
The
getStaticProps
function fetches posts from the API at build time.The fetched data is passed as the
posts
prop to theBlog
component.The page is generated as static HTML and cached by Next.js.
When you run next build
, Next.js generates the /blog
page with the fetched posts. This page is served to users until the data needs to be updated.
Caching in Next.js SSG
Caching is an integral part of SSG. When you use getStaticProps
, Next.js serializes the props and stores them as part of the static HTML in the .next
directory. This cached page is served to users, ensuring fast load times. The cache persists until the page is regenerated, either through a full rebuild or, with ISR, through revalidation.
In a production environment, the cache is typically managed by the Next.js server or a CDN. The cache is invalidated based on the revalidation settings we’ll discuss next.
Adding ISR with Revalidation
To keep the blog page up-to-date, we can enable ISR by adding a revalidate
option to getStaticProps
. This tells Next.js how often to regenerate the page.
Updated Example with ISR
Let’s modify the getStaticProps
function to include a revalidation time:
export async function getStaticProps() {
// Fetch data from external API
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await res.json();
return {
props: {
posts: posts.slice(0, 10),
},
revalidate: 60, // Revalidate every 60 seconds
};
}
Here’s what happens:
The page is generated at build time with the initial data.
For the first 60 seconds, the cached static page is served to all users.
After 60 seconds, when a user requests the page, Next.js regenerates it in the background by re-running
getStaticProps
.The regenerated page is cached and served to subsequent requests until the next revalidation.
The first user after the revalidation period may see the stale page, but subsequent users will see the updated version. This process ensures minimal latency while keeping content fresh.
How Caching Works with ISR
With ISR, the caching mechanism remains the same as SSG, but the cache is invalidated based on the revalidate
time. The regenerated page replaces the old one in the cache, ensuring users eventually see the latest content. In a production environment with multiple servers, you may need a shared cache (e.g., a network drive) to ensure consistency across servers, as each server maintains its own .next
directory.
On-Demand Revalidation
While periodic revalidation is great for predictable updates, sometimes you need to update a page immediately, such as when a new blog post is published in a CMS. Next.js supports on-demand revalidation through API routes, allowing you to trigger revalidation manually.
Example: Setting Up On-Demand Revalidation
Let’s create an API route at pages/api/revalidate.js
to trigger revalidation of the /blog
page:
export default async function handler(req, res) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' });
}
try {
await res.revalidate('/blog');
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send('Error revalidating');
}
}
To use this API route:
Set an environment variable
MY_SECRET_TOKEN
in your.env
file (e.g.,MY_SECRET_TOKEN=your-secure-token
).Make a request to
/api/revalidate?secret=your-secure-token
, for example, via a CMS webhook or a manual HTTP request.
When the request is made:
The API route verifies the secret token to prevent unauthorized access.
If valid,
res.revalidate('/blog')
triggers the regeneration of the/blog
page.The regenerated page is cached and served to subsequent requests.
If an error occurs during revalidation, Next.js continues to serve the last successfully generated page and retries on the next request.
Use Case for On-Demand Revalidation
On-demand revalidation is ideal for scenarios where content changes unpredictably. For example:
A CMS like WordPress updates a blog post, triggering a webhook to the
/api/revalidate
endpoint.An e-commerce platform adds a new product, requiring immediate updates to product listings.
Handling Dynamic Routes with ISR
For dynamic routes (e.g., /blog/[id]
), ISR works similarly, but you need to use getStaticPaths
to specify which pages to pre-render at build time. You can also use the fallback
option to handle pages that aren’t pre-rendered.
Example: Dynamic Blog Post Pages
Suppose we want individual blog post pages at /blog/[id]
. Here’s how to set it up:
import React from 'react';
const Post = ({ post }) => {
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
};
export async function getStaticProps({ params }) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
const post = await res.json();
return {
props: {
post,
},
revalidate: 60, // Revalidate every 60 seconds
};
}
export async function getStaticPaths() {
return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } },
// Pre-render only a few posts
],
fallback: 'blocking', // Generate other pages on demand
};
}
export default Post;
In this example:
getStaticPaths
pre-renders pages for posts with IDs 1 and 2.The
fallback: 'blocking'
option means that for other IDs, the page is generated on the first request and cached for future requests.ISR ensures that each post page is revalidated every 60 seconds.
The fallback
option can also be set to true
, which shows a loading state while the page is generated, but blocking
is often preferred for faster data fetching.
Best Practices for Using ISR
To make the most of ISR, consider these best practices:
Set Appropriate Revalidation Times:
Choose a
revalidate
time based on how often your data changes. For example:News sites: 60 seconds for frequent updates.
Product pages: 3600 seconds (1 hour) for less frequent changes.
Balance freshness with server load, as shorter revalidation times increase regeneration frequency.
Optimize Build-Time Generation:
For large sites, pre-render only the most popular pages at build time to reduce build times. For example, generate the top 1,000 product pages instead of all 10,000.
Use ISR for less popular pages to generate them on demand.
Use Fallback Options Wisely:
For dynamic routes, use
fallback: 'blocking'
for fast data fetching orfallback: true
for slow data fetching with a loading state.Ensure data fetching is optimized to minimize delays for
fallback: 'blocking'
.
Test ISR Locally:
Run
next build
followed bynext start
to test ISR behavior in a production-like environment.Add
NEXT_LOGGER=true
to your.env
file to log ISR cache hits and misses for debugging.
Secure On-Demand Revalidation:
Use a strong, random secret token for API routes to prevent abuse.
Store the token in environment variables and avoid hardcoding it.
Handle Errors Gracefully:
If
getStaticProps
fails during revalidation, Next.js serves the last successful page and retries on the next request.Implement fallback UI or error handling in your components to manage data issues.
Consider Hosting Environment:
In production with multiple servers, use a shared cache (e.g., a network drive) to ensure consistency across servers.
ISR is supported only with the Node.js runtime, not with Static Export.
Trade-Offs of ISR
While ISR is powerful, it’s not a one-size-fits-all solution. Here are some trade-offs to consider:
ISR vs. SSR: ISR guarantees a static first request, which is faster, but SSR allows per-request customization. Use ISR for content that doesn’t need real-time personalization.
ISR vs. SSG: ISR is better when content updates frequently, as SSG requires full rebuilds for updates.
ISR vs. Client-Side Rendering (CSR): ISR is superior for SEO and performance, as CSR relies on JavaScript and lacks pre-rendered content.
For highly dynamic content (e.g., live sports scores), SSR or CSR may be more appropriate, as ISR’s revalidation period introduces a slight delay.
Example Scenarios for ISR
ISR shines in various use cases:
Blogs: Update blog posts periodically or when new posts are published.
E-commerce: Refresh product listings or prices without rebuilding the entire site.
News Sites: Keep articles current with minimal server load.
Marketing Pages: Update promotional content dynamically.
Conclusion
Incremental Static Regeneration (ISR) in Next.js is a game-changer for building fast, scalable websites with dynamic content. By combining the performance of static site generation with the ability to update pages dynamically, ISR offers a flexible solution for modern web development. Whether you’re using periodic revalidation with getStaticProps
or on-demand revalidation via API routes, ISR makes it easy to keep your content fresh without sacrificing speed.
Start experimenting with ISR in your Next.js projects today. Test it locally, fine-tune your revalidation times, and explore on-demand revalidation for real-time updates. With ISR, you can build websites that are both blazing fast and always up-to-date.
Subscribe to my newsletter
Read articles from Som palkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
