npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

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
  1. Your Next.js app fetches data with a cache tag (e.g., "products")
  2. You cache aggressively (even up to 1 year — it doesn't matter!)
  3. When data changes on your backend, it sends a POST request to your Next.js webhook
  4. next-revalidator-js verifies the secret, extracts the tag, and calls revalidateTag()
  5. The next visitor gets fresh data — instantly

📦 Installation

npm install next-revalidator-js
yarn add next-revalidator-js
pnpm 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 price

Laravel Side

.env

NEXTJS_URL=http://your-nextjs-ip:3000
NEXTJS_REVALIDATION_SECRET=my_super_secret_password_123

app/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_123

app/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-secret doesn't fit your setup
  • Fallback header — Also checks x-revalidation-secret as a fallback for flexibility
  • Environment variables — Never hardcode secrets; always use .env files

🤝 Contributing

Contributions, issues, and feature requests are welcome! Feel free to open an issue or submit a pull request.

📄 License

ISC © next-revalidator-js