Introduction
Type-safe cache tag management for Next.js 16
Stop Wrestling with String-Based Cache Tags
next-cool-cache brings compile-time safety to Next.js cache management. No more typos, no more silent failures, no more debugging mysterious stale data issues.
The Problem
In Next.js 16, cache tags are plain strings. A simple typo can break your entire caching strategy:
// Somewhere in your codebase...
revalidateTag("user/byId:123"); // typo: "user" instead of "users"
// The actual tag is "users/byId:123"
// Result: Silent failure. Cache never invalidates. Users see stale data.This bug is nearly impossible to catch:
- No compile-time error
- No runtime error
- No warning in logs
- Just stale data and confused users
The Solution
With next-cool-cache, your IDE catches these errors instantly:
import { createCache } from 'next-cool-cache';
const schema = {
users: {
list: {},
byId: { _params: ['id'] as const },
},
} as const;
const scopes = ['admin', 'public'] as const;
export const cache = createCache(schema, scopes);
// TypeScript error: Property 'user' does not exist
cache.admin.user.byId.revalidateTag({ id: '123' });
// ^^^^
// Did you mean 'users'?Key Features
Type-Safe Tags
Full autocomplete and compile-time verification. Rename a resource and TypeScript guides you to every location that needs updating.
Hierarchical Invalidation
Invalidate a parent to cascade to all children. Perfect for bulk operations.
// Invalidate all user data in admin scope
cache.admin.users.revalidateTag();
// Invalidate just one user
cache.admin.users.byId.revalidateTag({ id: '123' });Multi-Scope Support
Different cache strategies for different audiences:
// Admins see changes immediately
cache.admin.posts.byId.updateTag({ id: '456' });
// Public users get stale-while-revalidate (no loading screens)
cache.public.posts.byId.revalidateTag({ id: '456' });Cross-Scope Operations
Invalidate across all scopes when needed:
// User changed their profile - invalidate everywhere
cache.users.byId.revalidateTag({ id: '123' });Zero Runtime Overhead
Pure TypeScript types with minimal runtime. Tags are simple strings under the hood.
Quick Example
// cache.ts
import { createCache } from 'next-cool-cache';
const schema = {
blog: {
posts: {
list: {},
byId: { _params: ['id'] as const },
bySlug: { _params: ['slug'] as const },
},
categories: {
list: {},
},
},
users: {
byId: { _params: ['id'] as const },
},
} as const;
const scopes = ['admin', 'public'] as const;
export const cache = createCache(schema, scopes);// In a cached function
'use cache: remote';
cache.public.blog.posts.byId.cacheTag({ id: postId });
const post = await fetchPost(postId);// In a server action
'use server';
await updatePost(postId, data);
cache.admin.blog.posts.byId.updateTag({ id: postId });
cache.public.blog.posts.byId.revalidateTag({ id: postId });