The missing fetch() cache
for Next.js middleware on Vercel.
A Next.js fetch wrapper for edge middleware that uses Vercel Runtime Cache as its caching backend. Drop-in replacement with SWR support, GraphQL compatibility, and zero configuration.
SWR Caching
Stale-while-revalidate strategy with background refresh using waitUntil()
Drop-in Replacement
Works exactly like Next.js fetch with cache, next.revalidate, and next.tags
Performance Insights
Cache status headers with HIT/MISS/STALE indicators and timing data
Everything you need for middleware caching
Built specifically for Next.js edge middleware with all the features you expect from modern caching solutions.
Get started in seconds
Install the package and start caching your middleware requests immediately.
npm install cached-middleware-fetch-next
On Vercel Edge
Uses Runtime Cache with SWR
Local Development
Falls back to native fetch
Quick Start Guide
Get up and running with cached middleware fetch in just a few lines of code.
import { cachedFetch } from 'cached-middleware-fetch-next';
import { NextRequest, NextResponse } from 'next/server';
export async function middleware(request: NextRequest) {
// This will be cached using Vercel Runtime Cache
const response = await cachedFetch('https://api.example.com/data');
const data = await response.json();
// Use the data in your middleware logic
return NextResponse.next();
}
import { cachedFetch } from 'cached-middleware-fetch-next';
// Cache for 5 minutes with SWR behavior
const response = await cachedFetch('https://api.example.com/data', {
next: {
revalidate: 300, // Consider stale after 5 minutes
expires: 3600 // Keep serving stale data for up to 1 hour
}
});
// Check cache status
const cacheStatus = response.headers.get('X-Cache-Status'); // 'HIT' | 'MISS' | 'STALE'
const cacheAge = response.headers.get('X-Cache-Age'); // Age in seconds
import { NextRequest, NextResponse } from 'next/server';
import { cachedFetch } from 'cached-middleware-fetch-next';
export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
// Cache route resolution for 30 minutes
const routeResponse = await cachedFetch(
`https://api.example.com/routes?path=${pathname}`,
{
next: {
revalidate: 1800, // 30 minutes
tags: ['routes']
}
}
);
const route = await routeResponse.json();
if (route.redirect) {
return NextResponse.redirect(new URL(route.redirect, request.url));
}
if (route.rewrite) {
return NextResponse.rewrite(new URL(route.rewrite, request.url));
}
return NextResponse.next();
}
Real-world Examples
See how to use cached-middleware-fetch-next in common scenarios and advanced use cases.
// Each unique query gets its own cache entry
const response = await cachedFetch('https://api.example.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
query GetProducts($category: String!) {
products(category: $category) {
id
name
price
}
}
`,
variables: { category: 'electronics' }
}),
next: {
revalidate: 3600, // Cache for 1 hour
tags: ['products', 'electronics']
}
});
// Different variables = different cache key
const clothingResponse = await cachedFetch('https://api.example.com/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `query GetProducts($category: String!) { ... }`,
variables: { category: 'clothing' } // This gets cached separately
}),
next: { revalidate: 3600 }
});
API Reference
Complete reference for all functions, options, and return values.
import { fetch } from 'cached-middleware-fetch-next';
// Now you can use it exactly like native fetch
const response = await fetch('https://api.example.com/data', {
next: { revalidate: 300 }
});