Fresh by default
In the current Next.js model, a plain fetch in a server component is not cached between requests. Every visitor triggers a real request, so data is always fresh, at the cost of speed and load on the data source.
When the data changes rarely (a blog post, a product list), you can opt into caching and tell Next.js how long the copy stays valid:
// re-fetch at most once every 60 seconds const res = await fetch(url, { next: { revalidate: 60 } });
Between refreshes, every visitor is served the saved result instantly. This one option is the seed of the rendering strategies in Unit 7.
Three layers, one table
| Layer | Lifetime | What it saves you |
|---|---|---|
| Request memoization | one render | duplicate fetches to the same URL in one page render |
Data cache (revalidate) | seconds to days, you choose | re-fetching the same data for every visitor |
| Full-page prerender | until rebuilt or revalidated | re-rendering the whole page (Unit 7) |
Mental shortcut: memoization dedupes within a request, caching reuses across requests. When something looks stale, ask which layer is holding the old copy.
Reading a revalidate window
Take a product page that fetches prices with { next: { revalidate: 3600 } }, and suppose the price changes in the database 5 minutes after the last fetch. For the rest of that hour visitors see the old cached price.
revalidate: 3600 tells Next.js that cached data is acceptable for up to an hour. Visitors get the stored copy instantly, stale or not, until the window expires and a fresh fetch replaces it. The database change does not reach back into the cache to invalidate it.
Trading freshness for speed is the entire point of caching. The number you choose is a statement about how stale the data is allowed to be, so pick it per data source: an hour is fine for a blog index and far too long for a stock level.
latestTitles: reshaping API data before rendering
Server components often reshape API data before rendering it, and that reshaping is ordinary JavaScript with no JSX involved. latestTitles(posts, n) returns the titles of the n most recent posts, newest first.
The dates here are "YYYY-MM-DD" strings, which sort correctly with localeCompare because that format puts the most significant part first. That is a trick worth remembering from Advanced JavaScript. The function also leaves its input untouched, which matters when the same array is rendered elsewhere on the page.
function latestTitles(posts, n) { return posts .slice() .sort((a, b) => b.date.localeCompare(a.date)) .slice(0, n) .map((p) => p.title); } const posts = [ { title: "Hello Next.js", date: "2024-01-10" }, { title: "Caching explained", date: "2024-03-02" }, { title: "Server actions", date: "2024-02-14" }, { title: "Deploying", date: "2024-01-28" }, ]; console.log(latestTitles(posts, 2).join(", ")); console.log(latestTitles(posts, 3).join(", "));
Output
Caching explained, Server actions Caching explained, Server actions, Deploying
Walking the chain
posts.slice()copies the array first, becausesortmutates in place and a server component should not be quietly rearranging shared data.b.date.localeCompare(a.date)puts newest first. Reversing the operands toa.date.localeCompare(b.date)would give oldest first..slice(0, n)takes the topn, and.map((p) => p.title)reduces each post object to just its title.