Page Not Reflecting Updated Data After Mutation? Here's How to Fix It
Table of contents
- Understanding the Issue
- Why Does the Cache Persist?
- Solution: Using Revalidation in Server Actions
- Example: Adding a To-Do and Revalidating the Cache
- Be Careful With Revalidation
- Example: Adding a Product and Revalidating a Specific Page
- What If You Don’t Revalidate?
- Using revalidatePath for Multiple Routes
- Example: Using Revalidation with Client-Side Components
- Conclusion
When working with data mutations in Next.js, one common problem is when the page doesn’t update after adding or editing data. Imagine you’re adding a new to-do item via a server action, and even though the data is updated in the database, the changes don’t reflect on the page. This can be confusing and frustrating.
Let’s explore this issue, break down why it happens, and how to fix it by using revalidation in Next.js.
Understanding the Issue
In Next.js, server components are responsible for rendering data on the server and sending it to the client. When you add new data (e.g., a to-do item), the page won’t automatically update because the data is cached in a RSC Payload (React Server Component Payload), which is stored on the client side. The cache ensures efficient page loading, but it can become stale after data mutation, meaning the page won't reflect the new data unless manually re-validated.
Why Does the Cache Persist?
When Next.js sends the server-rendered component to the client, it caches the rendered output. This improves performance but can cause the issue of showing stale data. After a mutation (like adding a new product or to-do), the page doesn’t automatically re-fetch the updated data because the cache is still valid.
Solution: Using Revalidation in Server Actions
To resolve this, we need to invalidate the cache when we make a data mutation, such as adding or updating a to-do or product. Next.js provides a solution through the revalidate function, which allows you to manually revalidate specific routes after the mutation.
Here’s how it works:
Example: Adding a To-Do and Revalidating the Cache
"use server";
import { revalidatePath } from 'next/cache';
import { prisma } from '@/lib/prisma'; // Assuming you're using Prisma as ORM
export async function addTodoAction(todo) {
// Add the new to-do item to the database
await prisma.todo.create({
data: {
title: todo.title,
description: todo.description,
}
});
// Revalidate the specific path for updated data
revalidatePath('/todos');
}
What’s happening here?
Data Mutation: A new to-do item is added to the database using
prisma.todo.create()
.Revalidation: After the mutation, the
revalidatePath('/todos')
function is called. This tells Next.js to revalidate the cache for the/todos
route, ensuring that the page fetches the updated data.
Now, after the to-do item is added, the /todos
page will be revalidated, the cache for that route will be cleared, and fresh data will be fetched when the page is rendered.
Be Careful With Revalidation
While revalidation solves the problem, you must be cautious when specifying which paths to revalidate. Accidentally revalidating the wrong path (like the homepage /
) can result in clearing the cache for your entire app, which would lead to unnecessary cache busting and performance issues.
Here’s an example of a mistake to avoid:
// Be careful when revalidating the homepage!
revalidatePath('/');
This code will clear the cache for the entire app, affecting all routes that use server-side rendering.
Example: Adding a Product and Revalidating a Specific Page
Let’s say we’re working with an e-commerce site, and you need to add a new product and then revalidate the product page. Here’s how you can do that:
"use server";
import { revalidatePath } from 'next/cache';
import { prisma } from '@/lib/prisma';
export async function addProductAction(product) {
// Add new product to the database
await prisma.product.create({
data: {
name: product.name,
price: product.price,
}
});
// Revalidate the specific product list page to reflect the new product
revalidatePath('/products');
}
Explanation:
Add Product: The
prisma.product.create()
method adds the new product to the database.Revalidate Product Page: The
revalidatePath('/products')
function clears the cache for the/products
route, so the newly added product is fetched when the user visits or reloads the page.
What If You Don’t Revalidate?
If you omit the revalidation step, the page will continue to show stale data because the cache won’t be updated automatically. You’ll have to rely on the user manually refreshing the page to see the new data, which isn’t a good user experience.
Using revalidatePath
for Multiple Routes
If your data mutation affects multiple routes, you can revalidate multiple paths. For example, if you add a product that appears both on the /products
page and the /featured
page, you’ll want to revalidate both paths.
"use server";
import { revalidatePath } from 'next/cache';
import { prisma } from '@/lib/prisma';
export async function addProductAction(product) {
await prisma.product.create({
data: {
name: product.name,
price: product.price,
}
});
// Revalidate both the products page and the featured products page
revalidatePath('/products');
revalidatePath('/featured');
}
Explanation:
In this case, both the /products
and /featured
routes are revalidated, ensuring that any page displaying product data will show the latest updates.
Example: Using Revalidation with Client-Side Components
You can also combine server actions with client-side components to enhance user interaction. Let’s see how you can trigger the addTodoAction
function from a client-side component:
'use client';
import { addTodoAction } from '@/server-actions';
export default function AddTodoForm() {
const handleSubmit = async (event) => {
event.preventDefault();
const formData = new FormData(event.target);
// Collect the input data from the form
const todo = {
title: formData.get('title'),
description: formData.get('description'),
};
// Call the server action to add the todo
await addTodoAction(todo);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="title" placeholder="Title" required />
<input type="text" name="description" placeholder="Description" required />
<button type="submit">Add To-Do</button>
</form>
);
}
What’s happening here?
Client-Side Interaction: The user fills out the form and submits it.
Server Action: The client component calls the
addTodoAction
function, which updates the database and triggers the cache revalidation for the/todos
route.
This ensures that after the to-do is added, the updated data will reflect immediately on the page without needing a full refresh.
Conclusion
When working with data mutations in Next.js, it’s essential to handle caching and revalidation properly to ensure that your application reflects the latest data after changes. The revalidatePath function is a powerful tool to achieve this.
Here are the key takeaways:
Server actions like adding or updating data can cause stale cache issues, resulting in pages not reflecting updated data.
Use revalidatePath to manually revalidate the cache for specific routes after a data mutation.
Be careful when specifying the paths to revalidate—accidentally clearing the cache for the entire app can impact performance.
Always revalidate when your data mutation affects multiple routes.
By incorporating revalidation into your server actions, you can ensure that your Next.js application displays up-to-date data and provides a seamless user experience!
Subscribe to my newsletter
Read articles from Rishi Bakshi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Rishi Bakshi
Rishi Bakshi
Full Stack Developer with experience in building end-to-end encrypted chat services. Currently dedicated in improving my DSA skills to become a better problem solver and deliver more efficient, scalable solutions.