This error occurs when trying to call revalidateTag() outside of server environments in Next.js. The revalidateTag function is restricted to Server Actions and Route Handlers and cannot be used in Client Components or browser contexts.
This error appears when you attempt to use Next.js's revalidateTag() function in an invalid context. The revalidateTag function is part of Next.js's data revalidation API and is designed to purge cached data associated with specific cache tags. However, it can only be executed in server-side contexts - specifically Server Actions and Route Handlers. The restriction exists because cache revalidation requires server-side access to Next.js's caching layer. Client Components run in the browser and don't have direct access to the server's cache management system. When you try to call revalidateTag directly in a Client Component, Next.js throws this error to prevent runtime failures. This is a common issue when developers try to trigger cache invalidation from interactive UI elements like buttons or forms in Client Components. The solution involves creating a Server Action that wraps the revalidateTag call, then invoking that Server Action from your client code.
Create a new file (e.g., actions/revalidate.ts) with the "use server" directive at the top. This marks all functions in the file as Server Actions:
// actions/revalidate.ts
'use server';
import { revalidateTag } from 'next/cache';
export async function revalidateProductData() {
revalidateTag('products');
}
export async function revalidateByTag(tag: string) {
revalidateTag(tag);
}This creates server-only functions that can safely call revalidateTag.
In your Client Component, import the Server Action and call it from event handlers or effects:
// components/ProductForm.tsx
'use client';
import { revalidateProductData } from '@/actions/revalidate';
import { useState } from 'react';
export function ProductForm() {
const [loading, setLoading] = useState(false);
async function handleSubmit(formData: FormData) {
setLoading(true);
try {
// Your API mutation logic here
await fetch('/api/products', { method: 'POST', body: formData });
// Now revalidate using the Server Action
await revalidateProductData();
} finally {
setLoading(false);
}
}
return (
<form action={handleSubmit}>
{/* form fields */}
<button type="submit" disabled={loading}>Submit</button>
</form>
);
}The Server Action acts as a bridge between client code and server-side cache management.
If you prefer an API endpoint approach, create a Route Handler that performs the revalidation:
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const { tag } = await request.json();
if (!tag) {
return NextResponse.json({ error: 'Tag required' }, { status: 400 });
}
revalidateTag(tag);
return NextResponse.json({ revalidated: true, tag });
}Then call it from your client code:
await fetch('/api/revalidate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tag: 'products' })
});Ensure you're importing revalidateTag from the correct Next.js module:
import { revalidateTag } from 'next/cache';Not from any other package or custom implementation. This import should only appear in Server Actions or Route Handlers, never in files with the "use client" directive.
For better user experience, use the profile="max" parameter to enable stale-while-revalidate semantics:
'use server';
import { revalidateTag } from 'next/cache';
export async function revalidateWithSWR(tag: string) {
revalidateTag(tag, { profile: 'max' });
}This marks the tag as stale and serves cached content while fetching fresh data in the background, preventing blocking updates. Without this parameter, the single-argument syntax is deprecated and will expire the cache immediately.
If you need immediate cache expiration within a Server Action (for example, when users should see their own changes immediately), use updateTag instead:
'use server';
import { updateTag } from 'next/cache';
export async function updateProductAction(productId: string, data: any) {
// Update the database
await db.products.update({ where: { id: productId }, data });
// Immediately expire cache so next read gets fresh data
updateTag('products');
}Note: updateTag can ONLY be used in Server Actions, not Route Handlers. It's specifically designed for scenarios where users need to see their own writes immediately.
Verify your revalidation works by checking the following:
1. Add console.log statements in your Server Action to confirm it's being called
2. Check that cached data updates after triggering the revalidation
3. Monitor network requests in DevTools to see fresh data being fetched
4. Test with different cache tags to ensure proper isolation
Example test setup:
'use server';
import { revalidateTag } from 'next/cache';
export async function revalidateProductData() {
console.log('Server Action: revalidating products tag');
revalidateTag('products', { profile: 'max' });
console.log('Server Action: revalidation complete');
return { success: true };
}The logs should only appear in server console/terminal, not browser console, confirming server-side execution.
Server Actions vs Route Handlers: While both can use revalidateTag, Server Actions are generally preferred for form submissions and mutations because they integrate better with React's form actions and don't require manual fetch calls. Route Handlers are better when you need RESTful API endpoints or are calling from non-React contexts.
updateTag vs revalidateTag: The newer updateTag function (Next.js 15+) is specifically designed for Server Actions and provides immediate cache expiration, whereas revalidateTag supports both Server Actions and Route Handlers and uses stale-while-revalidate by default. For most CRUD operations within Server Actions, updateTag is now recommended.
Full page rerender issue: Some developers report that calling revalidateTag in a Server Action causes the entire page to rerender, which can close modals or lose client state. If you encounter this, consider using a Route Handler instead of a Server Action for the revalidation call, as suggested in GitHub discussions.
Security considerations: When creating revalidation endpoints, always validate permissions and input. Don't allow arbitrary tag revalidation from untrusted sources. Consider adding authentication checks:
'use server';
import { revalidateTag } from 'next/cache';
import { auth } from '@/lib/auth';
export async function revalidateAdminData(tag: string) {
const session = await auth();
if (!session?.user?.isAdmin) {
throw new Error('Unauthorized');
}
revalidateTag(tag);
}Debugging cache issues: If revalidation appears to work but data doesn't refresh, verify that your fetch requests are properly tagged with the same cache tags you're revalidating. The tags must match exactly.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite