
Just a guy who loves to write code and watch anime.
Search for a command to run...

Just a guy who loves to write code and watch anime.
good article, love it.
one thing I don't like it, if I use a function that has "use cache" in the layout page, I need to wrap the layout page with suspense but on one upper level, why? page/component having the "use cache" function have to be wrapped with suspense boundary before it renders something like that and default loading page doesnt cover that for layout page.
faced it in the current canary version,
Qualities that make outstanding builders.

Bipedal vs quadrupedal: how many legs This is about the number of legs the creature walks on. Bipedal. Two legs. Humans, ostriches, T-rex, kangaroos, most fantasy humanoids. Quadrupedal. Four legs. Do

Intro You hear "lerp" and "smoothstep" everywhere in game dev. They sound like math jargon. They're not. Both are small tools that do the same job: smoothly move from one value to another. The problem

What is Kinematics Kinematics is the math field. It's the study of how things move without worrying about forces (which would be dynamics). FK and IK are the two branches: forward kinematics and inver

Intro Textures are usually the biggest cost in a 3D scene. Memory, bandwidth, and load time all get eaten by them. Resizing them is the obvious lever. There's more. This post is about the less obvious

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.
// 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
},
},
},
};
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();
}
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
}
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();
}
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>;
}
export async function CachedWrapper({ children }) {
"use cache";
const header = await fetchHeader();
return (
<div>
<h1>{header}</h1>
{children} {/* Dynamic content passed through */}
</div>
);
}
export async function ProductPage({ id }) {
"use cache";
cacheTag(["products", `product-${id}`, "featured"]);
// Now can invalidate with any of these tags
}
"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>
);
}
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; // ...
}