next-revalidator-js
v5.0.0
Published
<p align="center"> <img src="https://img.shields.io/npm/v/next-revalidator-js?style=flat-square&color=0070f3" alt="npm version" /> <img src="https://img.shields.io/npm/dm/next-revalidator-js?style=flat-square&color=22c55e" alt="npm downloads" /> <im
Downloads
34
Maintainers
Readme
next-revalidator-js
On-demand cache revalidation for Next.js via webhooks.
A tiny, zero-config npm package that creates a secure webhook endpoint in your Next.js App Router. When your backend (Laravel, Rails, Django, Express, Strapi, or any CMS) updates data, it calls this webhook with a cache tag — and next-revalidator-js instantly purges and refreshes the stale cache on your production server.
No more waiting for timed revalidation. No more deploying to clear the cache. Just instant, surgical cache updates.
🤔 The Problem
When you fetch data in Next.js Server Components using fetch() with tags, the response is cached. That's great for performance — but what happens when the data changes on your backend?
| Approach | Downside |
|---|---|
| revalidate: 60 (time-based) | Users see stale data for up to 60 seconds |
| revalidate: 0 (no cache) | Every request hits your API — defeats the purpose of caching |
| revalidate: 31536000 (1 year) | Great performance, but cache is stuck until you redeploy |
The real solution: Keep the aggressive 1-year cache and bust it instantly when data actually changes. That's exactly what this package does.
✨ How It Works
┌──────────────┐ data changes ┌──────────────────┐
│ │ ──────────────────► │ │
│ Backend │ POST /api/revalidate │ Next.js App │
│ (Laravel, │ { tag: "products" } │ │
│ Express, │ Header: secret │ revalidateTag() │
│ Strapi…) │ ──────────────────► │ busts the cache │
│ │ │ │
└──────────────┘ └──────────────────┘
│
▼
Next request fetches
fresh data from API- Your Next.js app fetches data with a cache tag (e.g.,
"products") - You cache aggressively (even up to 1 year — it doesn't matter!)
- When data changes on your backend, it sends a POST request to your Next.js webhook
next-revalidator-jsverifies the secret, extracts the tag, and callsrevalidateTag()- The next visitor gets fresh data — instantly
📦 Installation
npm install next-revalidator-jsyarn add next-revalidator-jspnpm add next-revalidator-js🚀 Quick Start
1. Create the Webhook Route
Create a new API route in your Next.js App Router:
app/api/revalidate/route.ts
import { createRevalidationHandler } from 'next-revalidator-js';
// Next.js 13–15
export const POST = createRevalidationHandler({
secret: process.env.REVALIDATION_SECRET || 'fallback-secret',
});
// Next.js 16+ (with cacheProfile support)
export const POST = createRevalidationHandler({
secret: process.env.REVALIDATION_SECRET || 'fallback-secret',
cacheProfile: { expire: 0 }, // Immediate invalidation
});That's it. One file, one export.
2. Add the Secret to Your Environment
.env.local
REVALIDATION_SECRET=your-super-secret-token-here💡 Tip: Generate a strong secret with
openssl rand -hex 32
3. Tag Your Fetch Calls
In your Server Components or data-fetching functions, add tags to your fetch() calls:
// app/page.tsx (Server Component)
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: {
tags: ['products'],
revalidate: 31536000, // 1 year — cache as long as you want!
},
});
return res.json();
}
export default async function HomePage() {
const products = await getProducts();
return (
<div>
{products.map((product) => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}4. Call the Webhook from Your Backend
When data changes on your backend, send a POST request to the webhook:
cURL
curl -X POST https://your-nextjs-app.com/api/revalidate \
-H "Content-Type: application/json" \
-H "x-revalidate-secret: your-super-secret-token-here" \
-d '{"tag": "products"}'Laravel (PHP)
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
// Call this after updating data in your admin panel
Log::info('Attempting to send webhook to Next.js...');
$response = Http::timeout(5)
->withHeaders([
'x-revalidate-secret' => env('NEXTJS_REVALIDATION_SECRET'),
])
->post(env('NEXTJS_URL') . '/api/revalidate', [
'tag' => 'products',
]);
Log::info('Next.js Response Status: ' . $response->status());
Log::info('Next.js Response Body: ' . $response->body());Node.js / Express
await fetch('https://your-nextjs-app.com/api/revalidate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-revalidate-secret': process.env.NEXTJS_REVALIDATION_SECRET,
},
body: JSON.stringify({ tag: 'products' }),
});Python / Django
import requests
requests.post(
'https://your-nextjs-app.com/api/revalidate',
json={'tag': 'products'},
headers={'x-revalidate-secret': 'your-super-secret-token-here'}
)⚙️ Configuration
createRevalidationHandler({
// Required: Secret token to authenticate incoming webhook requests
secret: string,
// Optional: Custom header name for the secret (default: 'x-revalidate-secret')
headerName?: string,
// Optional (Next.js 16+ only): Cache profile for revalidation
cacheProfile?: string | { expire?: number },
});Config Options
| Option | Type | Default | Description |
|---|---|---|---|
| secret | string | (required) | The secret token to verify incoming webhook requests. Must match the header sent by your backend. |
| headerName | string | 'x-revalidate-secret' | The HTTP header key to check for the secret token. |
| cacheProfile | string \| { expire?: number } | undefined | (Next.js 16+ only) The cache life profile to apply during revalidation. |
🔄 Next.js Version Compatibility
This package works seamlessly across all modern Next.js versions:
| Next.js Version | Support | Notes |
|---|---|---|
| 13 | ✅ | App Router with revalidateTag() |
| 14 | ✅ | Stable App Router |
| 15 | ✅ | Full support |
| 16 | ✅ | Supports new cacheProfile parameter |
Next.js 16+ Configuration
Next.js 16 introduced a second argument to revalidateTag(). You can use it like this:
const handler = createRevalidationHandler({
secret: process.env.REVALIDATION_SECRET!,
cacheProfile: { expire: 0 }, // Immediate invalidation
});For Next.js 13–15, simply omit the cacheProfile option — the package handles it automatically.
🏗️ Real-World Example: Laravel + Next.js
Here's a complete example of how this works in a production setup:
The Flow
┌─────────────────────┐
│ Admin Panel │
│ (Laravel Backend) │
│ │
│ Admin updates a │
│ product price │
│ │ │
│ ▼ │
│ ProductObserver │
│ fires webhook ─────────────► POST /api/revalidate
│ │ { tag: "products" }
└─────────────────────┘ │
▼
┌──────────────────┐
│ Next.js App │
│ │
│ revalidateTag │
│ ("products") │
│ │
│ Cache busted! ✓ │
└──────────────────┘
│
▼
Next visitor sees
the updated priceLaravel Side
.env
NEXTJS_URL=http://your-nextjs-ip:3000
NEXTJS_REVALIDATION_SECRET=my_super_secret_password_123app/Observers/ProductObserver.php
<?php
namespace App\Observers;
use App\Models\Product;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class ProductObserver
{
public function updated(Product $product)
{
$this->revalidateNextCache('products');
}
public function created(Product $product)
{
$this->revalidateNextCache('products');
}
public function deleted(Product $product)
{
$this->revalidateNextCache('products');
}
private function revalidateNextCache(string $tag): void
{
try {
Log::info('Attempting to send webhook to Next.js...');
$response = Http::timeout(5)
->withHeaders([
'x-revalidate-secret' => env('NEXTJS_REVALIDATION_SECRET'),
])
->post(env('NEXTJS_URL') . '/api/revalidate', [
'tag' => $tag,
]);
Log::info('Next.js Response Status: ' . $response->status());
Log::info('Next.js Response Body: ' . $response->body());
} catch (\Exception $e) {
Log::error('Next.js revalidation failed: ' . $e->getMessage());
}
}
}Next.js Side
.env
NEXT_PUBLIC_API_URL=http://192.168.1.33:8080/api/
REVALIDATION_SECRET=my_super_secret_password_123app/api/revalidate/route.ts
import { createRevalidationHandler } from 'next-revalidator-js';
export const POST = createRevalidationHandler({
secret: process.env.REVALIDATION_SECRET || 'fallback-secret',
cacheProfile: { expire: 0 }, // Next.js 16+
});app/products/page.tsx
async function getProducts() {
const res = await fetch(`${process.env.API_URL}/api/products`, {
next: {
tags: ['products'],
revalidate: 31536000, // 1 year cache
},
});
return res.json();
}
export default async function ProductsPage() {
const { data: products } = await getProducts();
return (
<section>
<h1>Our Products</h1>
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</section>
);
}📡 Webhook Request & Response
Request Format
POST /api/revalidate HTTP/1.1
Host: your-nextjs-app.com
Content-Type: application/json
x-revalidate-secret: your-secret-token
{
"tag": "products"
}Success Response
{
"revalidated": true,
"tag": "products",
"timestamp": 1714200000000
}Error Responses
| Status | Body | Reason |
|---|---|---|
| 401 | { "message": "Unauthorized" } | Missing or invalid secret |
| 400 | { "message": "Missing tag parameter" } | No tag in request body |
| 500 | { "message": "Internal Server Error" } | Unexpected server error |
🏷️ Using Multiple Tags
You can use different tags for different data types and revalidate them independently:
// Fetch products with 'products' tag
const products = await fetch('/api/products', {
next: { tags: ['products'], revalidate: 31536000 },
});
// Fetch categories with 'categories' tag
const categories = await fetch('/api/categories', {
next: { tags: ['categories'], revalidate: 31536000 },
});
// Fetch settings with 'settings' tag
const settings = await fetch('/api/settings', {
next: { tags: ['settings'], revalidate: 31536000 },
});Then revalidate only what changed:
# Only bust the products cache
curl -X POST https://your-app.com/api/revalidate \
-H "x-revalidate-secret: your-secret" \
-H "Content-Type: application/json" \
-d '{"tag": "products"}'🔒 Security
- Secret verification — Every incoming request is verified against your secret token
- Custom header support — Use a custom header name if
x-revalidate-secretdoesn't fit your setup - Fallback header — Also checks
x-revalidation-secretas a fallback for flexibility - Environment variables — Never hardcode secrets; always use
.envfiles
🤝 Contributing
Contributions, issues, and feature requests are welcome! Feel free to open an issue or submit a pull request.
📄 License
ISC © next-revalidator-js
