Fetching data fresh from the database on every request is correct, but it's slow and can get expensive. On the other hand, caching forever and never refreshing means users see stale data. Caching strategy is really about weighing speed against freshness.
The mental model
To use Next.js 16's Cache Components model, enable cacheComponents in next.config.ts. Add use cache inside the function you want cached, and set a lifetime with cacheLife. cacheTag names a data group, so after a mutation you can precisely refresh it with updateTag or revalidateTag. Don't cache per-user secret data, or anything that always needs to be perfectly fresh, without thinking it through first.
Let's build it together
// next.config.ts
import type { NextConfig } from "next";
export default { cacheComponents: true } satisfies NextConfig;
// app/lib/notes.ts
import { cacheLife, cacheTag } from "next/cache";
export async function getPublicNotes() {
"use cache";
cacheLife("hours");
cacheTag("notes");
return db.note.findMany({ where: { published: true } });
}How the code works
getPublicNotes caches with a one-hour profile and is tagged notes. After a new note is added, the Server Action calls updateTag('notes'), so the user sees the change right away.
The public notes query reuses the cache and only refetches at the set time or when the tag is invalidated.5-Minute Try-It
Cache a categories list function with a days profile and tag it categories. Write down when you'd invalidate it.
Next.js — Revalidating — Next.js