I like the future of Next.js
It's actually getting super cool.

Just a guy who loves to write code and watch anime.
Introduction
I'm vibing with the future of Next.js.
Not sure if they're gonna change the story around server actions. I’ve not been the biggest fan. I’m not alone though, plenty of people aren’t too happy about them (see Twitter/X). I love how Remix does it with useFetcher .
React 19 makes the experience better for sure with useOptimistic and useFormStatus. But I believe a final piece is missing here for the optimal DX.
But I love PPR and the new granular cache system.
It's lit.
The Big Picture
// next.config.js
const config = {
experimental: {
// 1. Enable new caching system
// You can now use `use cache` in your code
dynamicIO: true,
// 2. Optional: Define cache profiles
cacheLife: {
blog: {
stale: 3600, // Client cache: 1 hour
revalidate: 900, // Server refresh: 15 mins
expire: 86400, // Max life: 1 day
},
},
},
};
Basic Usage with use cache
// 1. File-level caching
"use cache";
export default function Page() {
return <div>Cached Page</div>;
}
// 2. Component-level caching
export async function PriceDisplay() {
"use cache";
const price = await fetchPrice();
return <div>${price}</div>;
}
// 3. Function-level caching
export async function getData() {
"use cache";
return await db.query();
}
Smart Caching with Tags
import { unstable_cacheTag as cacheTag } from 'next/cache'
import { revalidateTag } from 'next/cache'
// Tag cached data
export async function ProductList() {
'use cache'
cacheTag('products') // Tag this cache entry
const products = await fetchProducts()
return <div>{products}</div>
}
// Invalidate when needed
export async function addProduct() {
'use server'
await db.products.add(...)
revalidateTag('products') // Clear cached data
}
Custom Cache Profiles
import { unstable_cacheLife as cacheLife } from "next/cache";
export async function BlogPosts() {
"use cache";
cacheLife("blog"); // Use the blog profile we defined
return await fetchPosts();
}
Non-Obvious But Important Points
Cache Keys
Props and arguments automatically become part of the cache key.
Non-serializable values, like functions, become "unmodifiable references."
Example:
export async function UserCard({ id, onDelete }) {
"use cache";
// id becomes part of cache key
// onDelete is passed through but doesn't affect caching
const user = await fetchUser(id);
return <div onClick={onDelete}>{user.name}</div>;
}
Interleaving Dynamic & Cached Content
export async function CachedWrapper({ children }) {
"use cache";
const header = await fetchHeader();
return (
<div>
<h1>{header}</h1>
{children} {/* Dynamic content passed through */}
</div>
);
}
Multiple Cache Tags
export async function ProductPage({ id }) {
"use cache";
cacheTag(["products", `product-${id}`, "featured"]);
// Now can invalidate with any of these tags
}
Caching Hierarchy
"use cache";
export default async function Page() {
// This whole page is cached EXCEPT dynamic part:
return (
<div>
<CachedHeader />
<div>
{/* Dynamic section within cached page */}
{/* Marked with Suspense */}
<Suspense fallback={<Loading />}>
<DynamicFeed />
</Suspense>
</div>
</div>
);
}
Type safety
I would probably have a constant for cache keys and life.
For cache life, I'd probably do something like this:
// You can then use this in your code
// Keeping it all type safe and avoiding magic strings
export const CACHE_LIFE_KEYS = {
blog: "blog",
} as const;
const config = {
experimental: {
cacheLife: {
[CACHE_LIFE_KEYS.blog]: {
stale: 3600, // Client cache: 1 hour
revalidate: 900, // Server refresh: 15 mins
expire: 86400, // Max life: 1 day
},
},
},
};
For tags, I'd go with something like the factory pattern when working with React Query:
export const CACHE_TAGS = {
blog: {
all: ["blog"] as const,
list: () => [...CACHE_TAGS.blog.all, "list"] as const,
post: (id: string) => [...CACHE_TAGS.blog.all, "post", id] as const,
comments: (postId: string) =>
[...CACHE_TAGS.blog.all, "post", postId, "comments"] as const,
},
} as const;
function tagCache(tags: string[]) {
// We need to spread because tags is an array and cacheTag expects
// multiple arguments as plain strings
cacheTag(...tags);
}
// Then use it like this:
export async function BlogList() {
"use cache"; // Still need this
tagCache(CACHE_TAGS.blog.list()); // This works for the spreading
return; // ...
}






