[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-nextjs-weekly-cache-components-explained":44,"search-suggestions":431,"related-nextjs-weekly-cache-components-explained":472,"code:tsx:true:ivo0bt":605,"code:ts:true:vawbkf":606,"code:ts:true:pukwh1":607,"code:tsx:true:184vco4":608,"code:tsx:true:1tjeene":609,"code:ts:true:10yj1ro":610,"code:tsx:true:9b9jqb":611,"comments-01a0499d-b520-778f-8286-8221ec03b8b5":612},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":74,"playground":76,"body":78,"bodyMd":415,"seo":416,"translationGroupId":418,"series":48,"podcastUrl":48,"verticalId":5,"thread":419,"assessments":421,"translations":427,"quiz":429},"01a0499d-b520-778f-8286-8221ec03b8b5","nextjs-weekly-cache-components-explained","Next.js Cache Components Explained (with Cheat Sheet)",null,"How Next.js Cache Components decide what's static, what's cached, and what streams — the use cache directive, cacheLife, and Suspense explained.","\u002Fmedia\u002Fcovers\u002Fnextjs-weekly-cache-components-explained.png",14,"2026-09-01T11:24:15.087Z",18,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"nextjs","Nextjs",[64,67,70,73],{"slug":65,"name":66,"color":48},"webdev","Webdev",{"slug":68,"name":69,"color":48},"react","React",{"slug":71,"name":72,"color":48},"tutorial","Tutorial",{"slug":61,"name":62,"color":48},{"assessments":75},1,{"slug":46,"title":77},"Next.js Cache Components — interactive playground",{"blocks":79,"version":75},[80,84,87,92,95,104,107,110,113,116,130,133,136,142,145,148,151,154,157,160,163,168,171,175,178,182,185,188,192,195,199,202,205,209,212,216,219,222,226,229,233,236,239,242,245,248,251,254,263,266,274,277,281,284,288,291,294,297,301,304,308,311,314,358,362,365,373,376,379,382,385,388,391,394,397,400,403,406,409],{"id":81,"html":82,"type":83},"b1","\u003Cp>You add one line to a layout — \u003Ccode>const theme = (await cookies()).get(&#39;theme&#39;)?.value\u003C\u002Fcode> — to greet returning visitors by their saved preference. Nothing else changes. Deploy, and your blog&#39;s server load quadruples: every page that used to serve instantly from a CDN edge now renders fresh, on your origin server, for every single visitor, including the 95% of the page that is identical for everyone.\u003C\u002Fp>","paragraph",{"id":85,"html":86,"type":83},"b2","\u003Cp>Nothing you wrote was wrong, exactly. It&#39;s how the App Router&#39;s \u003Cem>previous\u003C\u002Fem> rendering model worked: one dynamic API call anywhere in a route&#39;s tree marked the \u003Cem>entire\u003C\u002Fem> route dynamic. Next.js&#39;s new Cache Components model exists specifically to fix this, and understanding how it decides what&#39;s static, what&#39;s cached, and what streams is the single most valuable thing you can know about the framework right now.\u003C\u002Fp>",{"id":88,"html":89,"text":90,"type":91,"level":31},"b3","What you&#39;ll learn","What you'll learn","heading",{"id":93,"html":94,"type":83},"b4","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":96,"type":97,"items":98,"ordered":18},"b5","list",[99,100,101,102,103],"Explain why a single \u003Ccode>cookies()\u003C\u002Fcode> or \u003Ccode>headers()\u003C\u002Fcode> call used to make a whole Next.js route dynamic, and how Cache Components changes that","Use the \u003Ccode>use cache\u003C\u002Fcode> directive at the function, component, and file level, and know which one to reach for","Set explicit cache lifetimes with \u003Ccode>cacheLife\u003C\u002Fcode> and invalidate on demand with \u003Ccode>cacheTag\u003C\u002Fcode> + \u003Ccode>updateTag\u003C\u002Fcode>","Read a route and predict which parts become the static shell, which get cached, and which stream in behind a \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode> boundary","Avoid the constraints that trip people up first: reading runtime APIs inside a cached scope, and passing uncached promises into one",{"id":105,"html":106,"text":106,"type":91,"level":31},"b6","Who this is for",{"id":108,"html":109,"type":83},"b7","\u003Cp>You&#39;ve built at least a small App Router project — a \u003Ccode>page.tsx\u003C\u002Fcode>, a \u003Ccode>layout.tsx\u003C\u002Fcode>, maybe a \u003Ccode>fetch\u003C\u002Fcode> call inside a Server Component. You don&#39;t need any prior experience with caching APIs; we build the model from nothing.\u003C\u002Fp>",{"id":111,"html":112,"type":83},"b8","\u003Cp>This article is written against \u003Cstrong>Next.js 16.3\u003C\u002Fstrong> (verified against the framework&#39;s own documentation and npm&#39;s \u003Ccode>latest\u003C\u002Fcode> dist-tag in August 2026). Cache Components shipped as an opt-in flag in Next.js 16.0 and is the model this article teaches; where the still-supported previous model (implicit \u003Ccode>fetch\u003C\u002Fcode> caching, route segment configs like \u003Ccode>export const dynamic\u003C\u002Fcode>) differs, it&#39;s called out explicitly rather than left implied.\u003C\u002Fp>",{"id":114,"html":115,"text":115,"type":91,"level":31},"b9","Table of contents",{"id":117,"type":97,"items":118,"ordered":18},"b10",[119,120,121,122,123,124,125,126,127,128,129],"\u003Ca href=\"#the-problem-one-cookie-read-one-fully-dynamic-page\">The problem: one cookie read, one fully dynamic page\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-pieces-decide-not-routes\">The mental model: pieces decide, not routes\u003C\u002Fa>","\u003Ca href=\"#stage-1-turning-it-on\">Stage 1: turning it on\u003C\u002Fa>","\u003Ca href=\"#stage-2-caching-data-with-use-cache\">Stage 2: caching data with \u003Ccode>use cache\u003C\u002Fcode>\u003C\u002Fa>","\u003Ca href=\"#stage-3-caching-a-component-and-reading-cachelife\">Stage 3: caching a component, and reading \u003Ccode>cacheLife\u003C\u002Fcode>\u003C\u002Fa>","\u003Ca href=\"#stage-4-the-part-that-cant-be-cached--suspense\">Stage 4: the part that can&#39;t be cached — Suspense\u003C\u002Fa>","\u003Ca href=\"#stage-5-invalidating-on-demand-with-cachetag\">Stage 5: invalidating on demand with \u003Ccode>cacheTag\u003C\u002Fcode>\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices\">Best practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>",{"id":131,"html":132,"text":132,"type":91,"level":31},"b11","The problem: one cookie read, one fully dynamic page",{"id":134,"html":135,"type":83},"b12","\u003Cp>Here&#39;s a blog layout that reads a saved theme preference so it can render the right class on \u003Ccode>&lt;body&gt;\u003C\u002Fcode>:\u003C\u002Fp>",{"id":137,"code":138,"type":139,"language":140,"highlight":141},"b13","\u002F\u002F app\u002Flayout.tsx — pre-Cache-Components App Router\nimport { cookies } from 'next\u002Fheaders';\n\nexport default async function RootLayout({ children }: { children: React.ReactNode }) {\n  const theme = (await cookies()).get('theme')?.value ?? 'light';\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Cbody className={theme}>{children}\u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}","code","tsx",[],{"id":143,"html":144,"type":83},"b14","\u003Cp>\u003Ccode>cookies()\u003C\u002Fcode> is a \u003Cem>request-time\u003C\u002Fem> API — it can only produce a value once an actual request exists, so there&#39;s no way to know it at build time. In the App Router&#39;s previous rendering model, that fact wasn&#39;t scoped to the component that called it: reading a dynamic API anywhere in a route&#39;s component tree opted the \u003Cstrong>entire route\u003C\u002Fstrong> out of static rendering. The header, the article body, the footer, the &quot;10 related posts&quot; list that&#39;s the same for every visitor — all of it now re-renders on the server, on every request, because one \u003Ccode>&lt;body&gt;\u003C\u002Fcode> class needed to know something about the current user.\u003C\u002Fp>",{"id":146,"html":147,"type":83},"b15","\u003Cp>You can work around this in the previous model (extract the theme read into a small Client Component that reads \u003Ccode>document.cookie\u003C\u002Fcode> after hydration, for instance), but the workaround is the tell: the framework&#39;s default behavior didn&#39;t distinguish &quot;this one value needs live data&quot; from &quot;this route needs live data.&quot; Cache Components draws that line at the component, not the route.\u003C\u002Fp>",{"id":149,"html":150,"text":150,"type":91,"level":31},"b16","The mental model: pieces decide, not routes",{"id":152,"html":153,"type":83},"b17","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> in the previous model, a \u003Cem>route\u003C\u002Fem> got one verdict — static or dynamic — decided by the most demanding thing anywhere in its tree. With Cache Components enabled, that verdict moves down to individual functions and components. Each one is either cached (with an explicit lifetime), streamed behind a \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode> boundary, or — if it does neither and touches something request-specific — flagged by the framework as needing one of those two treatments before the build will pass.\u003C\u002Fp>",{"id":155,"html":156,"type":83},"b18","\u003Cp>Next.js still produces one artifact per route: a \u003Cstrong>static shell\u003C\u002Fstrong>, prerendered at build time, containing every static and cached piece plus fallback UI for anything still streaming. That shell is what a CDN can serve instantly on a direct visit. The pieces behind \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode> fill in afterward, at request time, without dragging the rest of the page down with them.\u003C\u002Fp>",{"id":158,"html":159,"text":159,"type":91,"level":31},"b19","Stage 1: turning it on",{"id":161,"html":162,"type":83},"b20","\u003Cp>Cache Components is an opt-in flag as of Next.js 16.0 — a fresh \u003Ccode>create-next-app\u003C\u002Fcode> project doesn&#39;t enable it by default yet:\u003C\u002Fp>",{"id":164,"code":165,"type":139,"language":166,"highlight":167},"b21","\u002F\u002F next.config.ts\nimport type { NextConfig } from 'next';\n\nconst nextConfig: NextConfig = {\n  cacheComponents: true,\n};\n\nexport default nextConfig;","ts",[],{"id":169,"html":170,"type":83},"b22","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> this one flag replaces three separate experimental flags from Next.js 15 (\u003Ccode>dynamicIO\u003C\u002Fcode>, \u003Ccode>useCache\u003C\u002Fcode>, and \u003Ccode>ppr\u003C\u002Fcode>) with one unified setting, and it requires the Node.js runtime — routes still exporting the deprecated \u003Ccode>runtime = &#39;edge&#39;\u003C\u002Fcode> need to migrate first.\u003C\u002Fp>",{"id":172,"html":173,"text":174,"type":91,"level":31},"b23","Stage 2: caching data with \u003Ccode>use cache\u003C\u002Fcode>","Stage 2: caching data with use cache",{"id":176,"html":177,"type":83},"b24","\u003Cp>The \u003Ccode>use cache\u003C\u002Fcode> directive marks an async function&#39;s or component&#39;s return value as cacheable. Start with a plain data-fetching function:\u003C\u002Fp>",{"id":179,"code":180,"type":139,"language":166,"highlight":181},"b25","\u002F\u002F app\u002Flib\u002Fposts.ts\nimport { cacheLife } from 'next\u002Fcache';\n\nexport async function getRecentPosts() {\n  'use cache';\n  cacheLife('hours');\n  const res = await fetch('https:\u002F\u002Fapi.example.com\u002Fposts');\n  return res.json();\n}",[],{"id":183,"html":184,"type":83},"b26","\u003Cp>The first call with a given set of inputs runs the function and stores the result; every later call with the \u003Cem>same\u003C\u002Fem> inputs — including different requests, from different visitors — reuses it, until the lifetime you set with \u003Ccode>cacheLife\u003C\u002Fcode> expires. Arguments and any variables captured from an outer scope become part of the cache key automatically, so \u003Ccode>getRecentPosts(category)\u003C\u002Fcode> called with two different categories gets two separate cache entries.\u003C\u002Fp>",{"id":186,"html":187,"type":83},"b27","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>use cache\u003C\u002Fcode> caches a \u003Cem>result\u003C\u002Fem>, keyed by its inputs — not a route, not a URL. That&#39;s what lets a component ten levels deep cache independently from everything around it.\u003C\u002Fp>",{"id":189,"html":190,"text":191,"type":91,"level":31},"b28","Stage 3: caching a component, and reading \u003Ccode>cacheLife\u003C\u002Fcode>","Stage 3: caching a component, and reading cacheLife",{"id":193,"html":194,"type":83},"b29","\u003Cp>The same directive works at the component level, caching everything the component renders:\u003C\u002Fp>",{"id":196,"code":197,"type":139,"language":140,"highlight":198},"b30","\u002F\u002F app\u002Fblog\u002Frecent-posts.tsx\nimport { cacheLife, cacheTag } from 'next\u002Fcache';\n\nexport async function RecentPosts() {\n  'use cache';\n  cacheLife('hours');\n  cacheTag('posts');\n\n  const res = await fetch('https:\u002F\u002Fapi.example.com\u002Fposts');\n  const posts: { id: string; title: string }[] = await res.json();\n\n  return (\n    \u003Cul>\n      {posts.map((p) => (\n        \u003Cli key={p.id}>{p.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}",[],{"id":200,"html":201,"type":83},"b31","\u003Cp>\u003Ccode>cacheLife(&#39;hours&#39;)\u003C\u002Fcode> isn&#39;t a made-up duration — it&#39;s one of six built-in profiles, each balancing three numbers: how long the \u003Cem>client\u003C\u002Fem> trusts a cached copy without checking (\u003Ccode>stale\u003C\u002Fcode>), how often the \u003Cem>server\u003C\u002Fem> regenerates it in the background (\u003Ccode>revalidate\u003C\u002Fcode>), and when it&#39;s dropped entirely if nobody&#39;s asked for it (\u003Ccode>expire\u003C\u002Fcode>). Omit \u003Ccode>cacheLife\u003C\u002Fcode> and the \u003Ccode>default\u003C\u002Fcode> profile applies implicitly — which works, but leaves the lifetime invisible at the call site. Naming it explicitly is the recommended habit.\u003C\u002Fp>",{"id":203,"html":204,"type":83},"b32","\u003Cp>If this result is part of what could go into the route&#39;s prerendered static shell (its \u003Ccode>stale\u003C\u002Fcode> window is long enough), it&#39;s filled in at build time and served straight from a CDN on a direct visit — no server round trip at all.\u003C\u002Fp>",{"id":206,"html":207,"text":208,"type":91,"level":31},"b33","Stage 4: the part that can&#39;t be cached — Suspense","Stage 4: the part that can't be cached — Suspense",{"id":210,"html":211,"type":83},"b34","\u003Cp>Back to the theme example. The fix isn&#39;t to avoid \u003Ccode>cookies()\u003C\u002Fcode> — it&#39;s to contain it:\u003C\u002Fp>",{"id":213,"code":214,"type":139,"language":140,"highlight":215},"b35","\u002F\u002F app\u002Flayout.tsx — with Cache Components\nimport { cookies } from 'next\u002Fheaders';\nimport { Suspense } from 'react';\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Cbody>\n        \u003CSuspense fallback={null}>\n          \u003CThemeBody>{children}\u003C\u002FThemeBody>\n        \u003C\u002FSuspense>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n\nasync function ThemeBody({ children }: { children: React.ReactNode }) {\n  const theme = (await cookies()).get('theme')?.value ?? 'light';\n  return \u003Cdiv className={theme}>{children}\u003C\u002Fdiv>;\n}",[],{"id":217,"html":218,"type":83},"b36","\u003Cp>\u003Ccode>RootLayout\u003C\u002Fcode> itself no longer awaits \u003Ccode>cookies()\u003C\u002Fcode>, so it isn&#39;t request-dependent, and it completes during prerendering. \u003Ccode>ThemeBody\u003C\u002Fcode> is the only thing that streams in at request time — the header, the article body, and everything else in \u003Ccode>children\u003C\u002Fcode> that&#39;s cached or static ships in the initial shell exactly as before. One component&#39;s need for live data no longer taxes the whole page.\u003C\u002Fp>",{"id":220,"html":221,"type":83},"b37","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode> doesn&#39;t make a component dynamic — it gives a component that&#39;s \u003Cem>already\u003C\u002Fem> dynamic (because it reads a runtime API, or fetches without caching) somewhere to put its fallback so the rest of the shell doesn&#39;t have to wait for it. A component that only does synchronous work completes during prerendering regardless of whether it&#39;s wrapped.\u003C\u002Fp>",{"id":223,"html":224,"text":225,"type":91,"level":31},"b38","Stage 5: invalidating on demand with \u003Ccode>cacheTag\u003C\u002Fcode>","Stage 5: invalidating on demand with cacheTag",{"id":227,"html":228,"type":83},"b39","\u003Cp>Time-based expiry (\u003Ccode>cacheLife\u003C\u002Fcode>) and on-demand invalidation (\u003Ccode>cacheTag\u003C\u002Fcode>) aren&#39;t alternatives — they&#39;re usually paired. \u003Ccode>RecentPosts\u003C\u002Fcode> above tagged its cache entry \u003Ccode>&#39;posts&#39;\u003C\u002Fcode>. When a new post is published, invalidate every entry with that tag, from anywhere the mutation happens:\u003C\u002Fp>",{"id":230,"code":231,"type":139,"language":166,"highlight":232},"b40","\u002F\u002F app\u002Factions.ts\n'use server';\n\nimport { updateTag } from 'next\u002Fcache';\n\nexport async function publishPost(formData: FormData) {\n  await db.posts.create({ \u002F* ... *\u002F });\n  updateTag('posts'); \u002F\u002F every 'posts'-tagged cache entry is now stale\n}",[],{"id":234,"html":235,"type":83},"b41","\u003Cp>This is the same pattern as the older \u003Ccode>revalidateTag\u003C\u002Fcode>, but \u003Ccode>updateTag\u003C\u002Fcode> is aware of Cache Components&#39; server and client caches together, so tagging and invalidating stays a single mental step regardless of which layer actually stored the result.\u003C\u002Fp>",{"id":237,"html":238,"type":83},"b42","\u003C!-- playground:start -->",{"id":240,"html":241,"text":241,"type":91,"level":31},"b43","🎮 Try it yourself",{"id":243,"html":244,"type":83},"b44","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnextjs-weekly-cache-components-explained\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":246,"html":247,"type":83},"b45","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":249,"html":250,"type":83},"b46","\u003C!-- playground:end -->",{"id":252,"html":253,"text":253,"type":91,"level":31},"b47","Edge cases and gotchas",{"id":255,"type":97,"items":256,"ordered":18},"b48",[257,258,259,260,261,262],"\u003Cstrong>A cached scope can&#39;t read runtime APIs at all.\u003C\u002Fstrong> Calling \u003Ccode>cookies()\u003C\u002Fcode>, \u003Ccode>headers()\u003C\u002Fcode>, or reading \u003Ccode>searchParams\u003C\u002Fcode> directly inside a \u003Ccode>use cache\u003C\u002Fcode> function — or inside anything it calls — throws. Read the value in an uncached component first, then pass it as an argument to the cached function; the argument becomes part of the cache key.","\u003Cstrong>Passing an uncached promise into a cached function hangs the build.\u003C\u002Fstrong> If a \u003Ccode>use cache\u003C\u002Fcode> function awaits a promise that resolves to request-specific or otherwise-uncached data (received as a prop, from a closure, or from shared storage like a \u003Ccode>Map\u003C\u002Fcode>), the build waits for data that can never resolve during prerendering and times out after 50 seconds. Await the value outside the cached scope and pass the resolved value in.","\u003Cstrong>\u003Ccode>Math.random()\u003C\u002Fcode>, \u003Ccode>Date.now()\u003C\u002Fcode>, and \u003Ccode>crypto.randomUUID()\u003C\u002Fcode> need an explicit choice.\u003C\u002Fstrong> They produce a different value every call, so Cache Components requires you to say what you mean: call \u003Ccode>connection()\u003C\u002Fcode> before them and wrap in \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode> to get a genuinely unique value per request, or wrap them in \u003Ccode>use cache\u003C\u002Fcode> so every visitor sees the same value until it revalidates.","\u003Cstrong>Serialization has real limits.\u003C\u002Fstrong> Arguments and cached return values must be serializable — primitives, plain objects, arrays, \u003Ccode>Date\u003C\u002Fcode>\u002F\u003Ccode>Map\u003C\u002Fcode>\u002F\u003Ccode>Set\u003C\u002Fcode>, and (for return values only) JSX. Class instances, functions, and \u003Ccode>URL\u003C\u002Fcode> instances aren&#39;t allowed, except as opaque pass-through props like \u003Ccode>children\u003C\u002Fcode>.","\u003Cstrong>Draft Mode bypasses the cache entirely.\u003C\u002Fstrong> With Draft Mode enabled, every cached function re-executes on every request and nothing is written to the cache — by design, so preview content is never stale.","\u003Cstrong>Bots and crawlers skip the shell.\u003C\u002Fstrong> Because they need a complete document, Next.js detects them by user agent and renders the whole page dynamically at request time instead of serving the static shell. If any part of your shell depends on build-time-only data, make sure the same data is reachable at request time too, or a page that renders for a person can fail for a crawler.",{"id":264,"html":265,"text":265,"type":91,"level":31},"b49","Best practices",{"id":267,"type":97,"items":268,"ordered":18},"b50",[269,270,271,272,273],"\u003Cstrong>Push runtime API reads as deep into the tree as they&#39;ll go.\u003C\u002Fstrong> A \u003Ccode>params\u003C\u002Fcode> or \u003Ccode>cookies()\u003C\u002Fcode> read at the top of a layout blocks everything below it from being static; the same read three components down blocks only that subtree. The deeper the dynamic work sits, the more of the page prerenders.","\u003Cstrong>Pair every \u003Ccode>use cache\u003C\u002Fcode> with an explicit \u003Ccode>cacheLife\u003C\u002Fcode>.\u003C\u002Fstrong> The implicit \u003Ccode>default\u003C\u002Fcode> profile (5-minute stale, 15-minute revalidate, never expires) works, but naming the profile you actually mean documents the decision at the call site.","\u003Cstrong>Reach for \u003Ccode>cacheTag\u003C\u002Fcode> + \u003Ccode>updateTag\u003C\u002Fcode> for anything invalidated by a mutation\u003C\u002Fstrong>, and a longer \u003Ccode>cacheLife\u003C\u002Fcode> (\u003Ccode>days\u003C\u002Fcode>, \u003Ccode>weeks\u003C\u002Fcode>, or \u003Ccode>max\u003C\u002Fcode>) for content that only changes when someone edits it — the two together mean you rarely need a short polling-style lifetime.","\u003Cstrong>Don&#39;t cache what should stream.\u003C\u002Fstrong> A component that genuinely needs the current request — a cart total from a session cookie, a personalized recommendation — belongs behind \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode>, not squeezed into a cache with an artificially short lifetime.","\u003Cstrong>\u003Ccode>use cache: private\u003C\u002Fcode> is the exception, not the default.\u003C\u002Fstrong> It exists for cases where you can&#39;t refactor to pass runtime data as arguments; reach for it rarely, since regular \u003Ccode>use cache\u003C\u002Fcode> plus an extracted argument covers most real cases and stays easier to reason about.",{"id":275,"html":276,"text":276,"type":91,"level":31},"b51","FAQ",{"id":278,"html":279,"text":280,"type":91,"level":43},"b52","Do I have to enable \u003Ccode>cacheComponents\u003C\u002Fcode> to use Next.js 16?","Do I have to enable cacheComponents to use Next.js 16?",{"id":282,"html":283,"type":83},"b53","\u003Cp>No. It&#39;s an opt-in flag. Without it, your app uses the previous rendering model — \u003Ccode>fetch\u003C\u002Fcode> requests are uncached by default (a change from Next.js 14), and route segment configs like \u003Ccode>export const dynamic\u003C\u002Fcode> and \u003Ccode>revalidate\u003C\u002Fcode> still work exactly as before.\u003C\u002Fp>",{"id":285,"html":286,"text":287,"type":91,"level":43},"b54","Does Cache Components replace \u003Ccode>revalidatePath\u003C\u002Fcode>?","Does Cache Components replace revalidatePath?",{"id":289,"html":290,"type":83},"b55","\u003Cp>Not entirely — \u003Ccode>revalidatePath\u003C\u002Fcode> still exists for the previous model&#39;s route-level cache. Inside Cache Components, prefer \u003Ccode>cacheTag\u003C\u002Fcode> plus \u003Ccode>updateTag\u003C\u002Fcode> (or \u003Ccode>revalidateTag\u003C\u002Fcode>), which target specific cached results by tag rather than an entire route.\u003C\u002Fp>",{"id":292,"html":293,"text":293,"type":91,"level":43},"b56","Is Cache Components the same thing as Partial Prerendering (PPR)?",{"id":295,"html":296,"type":83},"b57","\u003Cp>Related, not identical. Partial Prerendering — a static shell plus streaming holes — is the \u003Cem>rendering\u003C\u002Fem> behavior Cache Components implements by default. \u003Ccode>cacheComponents: true\u003C\u002Fcode> is the single flag that turns PPR on along with \u003Ccode>use cache\u003C\u002Fcode> and the removal of implicit dynamic-API-triggers-whole-route behavior; you no longer set an experimental PPR flag separately.\u003C\u002Fp>",{"id":298,"html":299,"text":300,"type":91,"level":43},"b58","Does \u003Ccode>use cache\u003C\u002Fcode> persist across deployments?","Does use cache persist across deployments?",{"id":302,"html":303,"type":83},"b59","\u003Cp>No. Every cache key includes the build ID (or your configured \u003Ccode>deploymentId\u003C\u002Fcode>), so a new deploy starts with an empty cache, even for the durable \u003Ccode>use cache: remote\u003C\u002Fcode> variant. That&#39;s deliberate — it guarantees a deploy never serves output built from stale code.\u003C\u002Fp>",{"id":305,"html":306,"text":307,"type":91,"level":43},"b60","What happens if I forget \u003Ccode>cacheLife\u003C\u002Fcode> on a \u003Ccode>use cache\u003C\u002Fcode> function?","What happens if I forget cacheLife on a use cache function?",{"id":309,"html":310,"type":83},"b61","\u003Cp>Nothing breaks — the \u003Ccode>default\u003C\u002Fcode> profile applies (5-minute client-side stale window, 15-minute server-side revalidate, no time-based expiry). The framework recommends setting it explicitly anyway, since the alternative is a lifetime that&#39;s easy to lose track of.\u003C\u002Fp>",{"id":312,"html":313,"text":313,"type":91,"level":31},"b62","Cheat sheet",{"id":315,"head":316,"rows":320,"type":357},"b63",[317,318,319],"Task","Code","Notes",[321,325,329,333,337,341,345,349,353],[322,323,324],"Enable Cache Components","\u003Ccode>cacheComponents: true\u003C\u002Fcode> in \u003Ccode>next.config.ts\u003C\u002Fcode>","Requires Node.js runtime",[326,327,328],"Cache a data function","\u003Ccode>&#39;use cache&#39;\u003C\u002Fcode> at the top of an async function","Result keyed by arguments + captured closures",[330,331,332],"Cache a whole component","\u003Ccode>&#39;use cache&#39;\u003C\u002Fcode> at the top of an async component","Composed \u003Ccode>children\u003C\u002Fcode>\u002Fslots pass through uncached",[334,335,336],"Cache every export in a file","\u003Ccode>&#39;use cache&#39;\u003C\u002Fcode> at the top of the file","Every exported function must be async",[338,339,340],"Set an explicit lifetime","\u003Ccode>cacheLife(&#39;hours&#39;)\u003C\u002Fcode>","One call per function invocation, inside the cached scope",[342,343,344],"Tag a cache entry","\u003Ccode>cacheTag(&#39;posts&#39;)\u003C\u002Fcode>","Pairs with \u003Ccode>updateTag\u003C\u002Fcode>\u002F\u003Ccode>revalidateTag\u003C\u002Fcode>",[346,347,348],"Invalidate on demand","\u003Ccode>updateTag(&#39;posts&#39;)\u003C\u002Fcode> inside a Server Action","Invalidates every entry with that tag",[350,351,352],"Stream request-specific data","Wrap in \u003Ccode>&lt;Suspense fallback={...}&gt;\u003C\u002Fcode>, read \u003Ccode>cookies()\u003C\u002Fcode>\u002F\u003Ccode>headers()\u003C\u002Fcode> inside","Fallback ships in the static shell; content streams at request time",[354,355,356],"Get a unique value per request","\u003Ccode>await connection()\u003C\u002Fcode> then \u003Ccode>Math.random()\u003C\u002Fcode>\u002F\u003Ccode>Date.now()\u003C\u002Fcode>, inside \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode>","Forces request-time evaluation instead of a cached build-time value","table",{"id":359,"code":360,"type":139,"language":140,"highlight":361},"b64","\u002F\u002F The pattern in one page: static, cached, and streaming together\nimport { Suspense } from 'react';\nimport { cookies } from 'next\u002Fheaders';\nimport { cacheLife, cacheTag } from 'next\u002Fcache';\n\nexport default function BlogPage() {\n  return (\n    \u003C>\n      \u003Cheader>Static — prerendered automatically\u003C\u002Fheader>\n      \u003CRecentPosts \u002F>{\u002F* cached, joins the static shell *\u002F}\n      \u003CSuspense fallback={\u003Cp>Loading your preferences…\u003C\u002Fp>}>\n        \u003CUserPreferences \u002F>{\u002F* streams in at request time *\u002F}\n      \u003C\u002FSuspense>\n    \u003C\u002F>\n  );\n}\n\nasync function RecentPosts() {\n  'use cache';\n  cacheLife('hours');\n  cacheTag('posts');\n  const posts = await fetch('https:\u002F\u002Fapi.example.com\u002Fposts').then((r) => r.json());\n  return \u003Cul>{posts.map((p: any) => \u003Cli key={p.id}>{p.title}\u003C\u002Fli>)}\u003C\u002Ful>;\n}\n\nasync function UserPreferences() {\n  const theme = (await cookies()).get('theme')?.value ?? 'light';\n  return \u003Caside>Theme: {theme}\u003C\u002Faside>;\n}",[],{"id":363,"html":364,"text":364,"type":91,"level":31},"b65","Key takeaways",{"id":366,"type":97,"items":367,"ordered":18},"b66",[368,369,370,371,372],"The previous App Router model gave each \u003Cem>route\u003C\u002Fem> one verdict — static or dynamic — decided by its most demanding component. Cache Components moves that decision down to individual functions and components.","\u003Ccode>use cache\u003C\u002Fcode> caches a result by its inputs; pair it with an explicit \u003Ccode>cacheLife\u003C\u002Fcode> so the lifetime is visible at the call site, not implicit.","\u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode> is how a genuinely request-specific piece streams in without dragging the rest of the page&#39;s caching down with it — it doesn&#39;t make a component dynamic, it gives one that already is somewhere to put its fallback.","Time-based (\u003Ccode>cacheLife\u003C\u002Fcode>) and on-demand (\u003Ccode>cacheTag\u003C\u002Fcode> + \u003Ccode>updateTag\u003C\u002Fcode>) revalidation are complementary, not competing — most real content wants both.","The line between &quot;this needs live data&quot; and &quot;this route needs live data&quot; is now drawn at the component, and that&#39;s the whole point.",{"id":374,"html":375,"text":375,"type":91,"level":31},"b67","Back to that layout",{"id":377,"html":378,"type":83},"b68","\u003Cp>The fix for the theme cookie wasn&#39;t to stop reading it — it was to stop letting one read decide the fate of everything around it. Move the read into its own component, wrap it in \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode>, and the header, the article, and the &quot;related posts&quot; list go back to shipping from the edge, instantly, while the one thing that actually needed to know who&#39;s visiting still gets to ask.\u003C\u002Fp>",{"id":380,"html":381,"type":83},"b69","\u003Cp>If you want the background this article assumes — what a Server Component actually is and why &quot;it&#39;s just SSR&quot; is the wrong mental model — \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fserver-components-without-the-hype-a-mental-model-that-sticks-4k5e\">Server Components Without the Hype\u003C\u002Fa> covers that half, and this article picks up from there.\u003C\u002Fp>",{"id":383,"html":384,"type":83},"b70","\u003Cp>What&#39;s the last route you had to manually pull out of &quot;fully dynamic&quot; — and did you know at the time which single line caused it?\u003C\u002Fp>",{"id":386,"html":387,"type":83},"b71","\u003C!-- quiz:start -->",{"id":389,"html":390,"text":390,"type":91,"level":31},"b72","🧠 Test yourself",{"id":392,"html":393,"type":83},"b73","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnextjs-weekly-cache-components-explained\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":395,"html":396,"type":83},"b74","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":398,"html":399,"type":83},"b75","\u003C!-- quiz:end -->",{"id":401,"type":402},"b76","divider",{"id":404,"html":405,"type":83},"b77","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":407,"html":408,"type":83},"b78","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":410,"type":97,"items":411,"ordered":18},"b79",[412,413,414],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","You add one line to a layout — `const theme = (await cookies()).get('theme')?.value` — to greet returning visitors by their saved preference. Nothing else changes. Deploy, and your blog's server load quadruples: every page that used to serve instantly from a CDN edge now renders fresh, on your origin server, for every single visitor, including the 95% of the page that is identical for everyone.\n\nNothing you wrote was wrong, exactly. It's how the App Router's *previous* rendering model worked: one dynamic API call anywhere in a route's tree marked the *entire* route dynamic. Next.js's new Cache Components model exists specifically to fix this, and understanding how it decides what's static, what's cached, and what streams is the single most valuable thing you can know about the framework right now.\n\n## What you'll learn\n\nBy the end of this article you'll be able to:\n\n- Explain why a single `cookies()` or `headers()` call used to make a whole Next.js route dynamic, and how Cache Components changes that\n- Use the `use cache` directive at the function, component, and file level, and know which one to reach for\n- Set explicit cache lifetimes with `cacheLife` and invalidate on demand with `cacheTag` + `updateTag`\n- Read a route and predict which parts become the static shell, which get cached, and which stream in behind a `\u003CSuspense>` boundary\n- Avoid the constraints that trip people up first: reading runtime APIs inside a cached scope, and passing uncached promises into one\n\n## Who this is for\n\nYou've built at least a small App Router project — a `page.tsx`, a `layout.tsx`, maybe a `fetch` call inside a Server Component. You don't need any prior experience with caching APIs; we build the model from nothing.\n\nThis article is written against **Next.js 16.3** (verified against the framework's own documentation and npm's `latest` dist-tag in August 2026). Cache Components shipped as an opt-in flag in Next.js 16.0 and is the model this article teaches; where the still-supported previous model (implicit `fetch` caching, route segment configs like `export const dynamic`) differs, it's called out explicitly rather than left implied.\n\n## Table of contents\n\n- [The problem: one cookie read, one fully dynamic page](#the-problem-one-cookie-read-one-fully-dynamic-page)\n- [The mental model: pieces decide, not routes](#the-mental-model-pieces-decide-not-routes)\n- [Stage 1: turning it on](#stage-1-turning-it-on)\n- [Stage 2: caching data with `use cache`](#stage-2-caching-data-with-use-cache)\n- [Stage 3: caching a component, and reading `cacheLife`](#stage-3-caching-a-component-and-reading-cachelife)\n- [Stage 4: the part that can't be cached — Suspense](#stage-4-the-part-that-cant-be-cached--suspense)\n- [Stage 5: invalidating on demand with `cacheTag`](#stage-5-invalidating-on-demand-with-cachetag)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices](#best-practices)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n\n## The problem: one cookie read, one fully dynamic page\n\nHere's a blog layout that reads a saved theme preference so it can render the right class on `\u003Cbody>`:\n\n```tsx\n\u002F\u002F app\u002Flayout.tsx — pre-Cache-Components App Router\nimport { cookies } from 'next\u002Fheaders';\n\nexport default async function RootLayout({ children }: { children: React.ReactNode }) {\n  const theme = (await cookies()).get('theme')?.value ?? 'light';\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Cbody className={theme}>{children}\u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n`cookies()` is a *request-time* API — it can only produce a value once an actual request exists, so there's no way to know it at build time. In the App Router's previous rendering model, that fact wasn't scoped to the component that called it: reading a dynamic API anywhere in a route's component tree opted the **entire route** out of static rendering. The header, the article body, the footer, the \"10 related posts\" list that's the same for every visitor — all of it now re-renders on the server, on every request, because one `\u003Cbody>` class needed to know something about the current user.\n\nYou can work around this in the previous model (extract the theme read into a small Client Component that reads `document.cookie` after hydration, for instance), but the workaround is the tell: the framework's default behavior didn't distinguish \"this one value needs live data\" from \"this route needs live data.\" Cache Components draws that line at the component, not the route.\n\n## The mental model: pieces decide, not routes\n\n**The mental model:** in the previous model, a *route* got one verdict — static or dynamic — decided by the most demanding thing anywhere in its tree. With Cache Components enabled, that verdict moves down to individual functions and components. Each one is either cached (with an explicit lifetime), streamed behind a `\u003CSuspense>` boundary, or — if it does neither and touches something request-specific — flagged by the framework as needing one of those two treatments before the build will pass.\n\nNext.js still produces one artifact per route: a **static shell**, prerendered at build time, containing every static and cached piece plus fallback UI for anything still streaming. That shell is what a CDN can serve instantly on a direct visit. The pieces behind `\u003CSuspense>` fill in afterward, at request time, without dragging the rest of the page down with them.\n\n## Stage 1: turning it on\n\nCache Components is an opt-in flag as of Next.js 16.0 — a fresh `create-next-app` project doesn't enable it by default yet:\n\n```ts\n\u002F\u002F next.config.ts\nimport type { NextConfig } from 'next';\n\nconst nextConfig: NextConfig = {\n  cacheComponents: true,\n};\n\nexport default nextConfig;\n```\n\n**Key concept:** this one flag replaces three separate experimental flags from Next.js 15 (`dynamicIO`, `useCache`, and `ppr`) with one unified setting, and it requires the Node.js runtime — routes still exporting the deprecated `runtime = 'edge'` need to migrate first.\n\n## Stage 2: caching data with `use cache`\n\nThe `use cache` directive marks an async function's or component's return value as cacheable. Start with a plain data-fetching function:\n\n```ts\n\u002F\u002F app\u002Flib\u002Fposts.ts\nimport { cacheLife } from 'next\u002Fcache';\n\nexport async function getRecentPosts() {\n  'use cache';\n  cacheLife('hours');\n  const res = await fetch('https:\u002F\u002Fapi.example.com\u002Fposts');\n  return res.json();\n}\n```\n\nThe first call with a given set of inputs runs the function and stores the result; every later call with the *same* inputs — including different requests, from different visitors — reuses it, until the lifetime you set with `cacheLife` expires. Arguments and any variables captured from an outer scope become part of the cache key automatically, so `getRecentPosts(category)` called with two different categories gets two separate cache entries.\n\n**Key concept:** `use cache` caches a *result*, keyed by its inputs — not a route, not a URL. That's what lets a component ten levels deep cache independently from everything around it.\n\n## Stage 3: caching a component, and reading `cacheLife`\n\nThe same directive works at the component level, caching everything the component renders:\n\n```tsx\n\u002F\u002F app\u002Fblog\u002Frecent-posts.tsx\nimport { cacheLife, cacheTag } from 'next\u002Fcache';\n\nexport async function RecentPosts() {\n  'use cache';\n  cacheLife('hours');\n  cacheTag('posts');\n\n  const res = await fetch('https:\u002F\u002Fapi.example.com\u002Fposts');\n  const posts: { id: string; title: string }[] = await res.json();\n\n  return (\n    \u003Cul>\n      {posts.map((p) => (\n        \u003Cli key={p.id}>{p.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n`cacheLife('hours')` isn't a made-up duration — it's one of six built-in profiles, each balancing three numbers: how long the *client* trusts a cached copy without checking (`stale`), how often the *server* regenerates it in the background (`revalidate`), and when it's dropped entirely if nobody's asked for it (`expire`). Omit `cacheLife` and the `default` profile applies implicitly — which works, but leaves the lifetime invisible at the call site. Naming it explicitly is the recommended habit.\n\nIf this result is part of what could go into the route's prerendered static shell (its `stale` window is long enough), it's filled in at build time and served straight from a CDN on a direct visit — no server round trip at all.\n\n## Stage 4: the part that can't be cached — Suspense\n\nBack to the theme example. The fix isn't to avoid `cookies()` — it's to contain it:\n\n```tsx\n\u002F\u002F app\u002Flayout.tsx — with Cache Components\nimport { cookies } from 'next\u002Fheaders';\nimport { Suspense } from 'react';\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Cbody>\n        \u003CSuspense fallback={null}>\n          \u003CThemeBody>{children}\u003C\u002FThemeBody>\n        \u003C\u002FSuspense>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n\nasync function ThemeBody({ children }: { children: React.ReactNode }) {\n  const theme = (await cookies()).get('theme')?.value ?? 'light';\n  return \u003Cdiv className={theme}>{children}\u003C\u002Fdiv>;\n}\n```\n\n`RootLayout` itself no longer awaits `cookies()`, so it isn't request-dependent, and it completes during prerendering. `ThemeBody` is the only thing that streams in at request time — the header, the article body, and everything else in `children` that's cached or static ships in the initial shell exactly as before. One component's need for live data no longer taxes the whole page.\n\n**Key concept:** `\u003CSuspense>` doesn't make a component dynamic — it gives a component that's *already* dynamic (because it reads a runtime API, or fetches without caching) somewhere to put its fallback so the rest of the shell doesn't have to wait for it. A component that only does synchronous work completes during prerendering regardless of whether it's wrapped.\n\n## Stage 5: invalidating on demand with `cacheTag`\n\nTime-based expiry (`cacheLife`) and on-demand invalidation (`cacheTag`) aren't alternatives — they're usually paired. `RecentPosts` above tagged its cache entry `'posts'`. When a new post is published, invalidate every entry with that tag, from anywhere the mutation happens:\n\n```ts\n\u002F\u002F app\u002Factions.ts\n'use server';\n\nimport { updateTag } from 'next\u002Fcache';\n\nexport async function publishPost(formData: FormData) {\n  await db.posts.create({ \u002F* ... *\u002F });\n  updateTag('posts'); \u002F\u002F every 'posts'-tagged cache entry is now stale\n}\n```\n\nThis is the same pattern as the older `revalidateTag`, but `updateTag` is aware of Cache Components' server and client caches together, so tagging and invalidating stays a single mental step regardless of which layer actually stored the result.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnextjs-weekly-cache-components-explained\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Edge cases and gotchas\n\n- **A cached scope can't read runtime APIs at all.** Calling `cookies()`, `headers()`, or reading `searchParams` directly inside a `use cache` function — or inside anything it calls — throws. Read the value in an uncached component first, then pass it as an argument to the cached function; the argument becomes part of the cache key.\n- **Passing an uncached promise into a cached function hangs the build.** If a `use cache` function awaits a promise that resolves to request-specific or otherwise-uncached data (received as a prop, from a closure, or from shared storage like a `Map`), the build waits for data that can never resolve during prerendering and times out after 50 seconds. Await the value outside the cached scope and pass the resolved value in.\n- **`Math.random()`, `Date.now()`, and `crypto.randomUUID()` need an explicit choice.** They produce a different value every call, so Cache Components requires you to say what you mean: call `connection()` before them and wrap in `\u003CSuspense>` to get a genuinely unique value per request, or wrap them in `use cache` so every visitor sees the same value until it revalidates.\n- **Serialization has real limits.** Arguments and cached return values must be serializable — primitives, plain objects, arrays, `Date`\u002F`Map`\u002F`Set`, and (for return values only) JSX. Class instances, functions, and `URL` instances aren't allowed, except as opaque pass-through props like `children`.\n- **Draft Mode bypasses the cache entirely.** With Draft Mode enabled, every cached function re-executes on every request and nothing is written to the cache — by design, so preview content is never stale.\n- **Bots and crawlers skip the shell.** Because they need a complete document, Next.js detects them by user agent and renders the whole page dynamically at request time instead of serving the static shell. If any part of your shell depends on build-time-only data, make sure the same data is reachable at request time too, or a page that renders for a person can fail for a crawler.\n\n## Best practices\n\n- **Push runtime API reads as deep into the tree as they'll go.** A `params` or `cookies()` read at the top of a layout blocks everything below it from being static; the same read three components down blocks only that subtree. The deeper the dynamic work sits, the more of the page prerenders.\n- **Pair every `use cache` with an explicit `cacheLife`.** The implicit `default` profile (5-minute stale, 15-minute revalidate, never expires) works, but naming the profile you actually mean documents the decision at the call site.\n- **Reach for `cacheTag` + `updateTag` for anything invalidated by a mutation**, and a longer `cacheLife` (`days`, `weeks`, or `max`) for content that only changes when someone edits it — the two together mean you rarely need a short polling-style lifetime.\n- **Don't cache what should stream.** A component that genuinely needs the current request — a cart total from a session cookie, a personalized recommendation — belongs behind `\u003CSuspense>`, not squeezed into a cache with an artificially short lifetime.\n- **`use cache: private` is the exception, not the default.** It exists for cases where you can't refactor to pass runtime data as arguments; reach for it rarely, since regular `use cache` plus an extracted argument covers most real cases and stays easier to reason about.\n\n## FAQ\n\n### Do I have to enable `cacheComponents` to use Next.js 16?\n\nNo. It's an opt-in flag. Without it, your app uses the previous rendering model — `fetch` requests are uncached by default (a change from Next.js 14), and route segment configs like `export const dynamic` and `revalidate` still work exactly as before.\n\n### Does Cache Components replace `revalidatePath`?\n\nNot entirely — `revalidatePath` still exists for the previous model's route-level cache. Inside Cache Components, prefer `cacheTag` plus `updateTag` (or `revalidateTag`), which target specific cached results by tag rather than an entire route.\n\n### Is Cache Components the same thing as Partial Prerendering (PPR)?\n\nRelated, not identical. Partial Prerendering — a static shell plus streaming holes — is the *rendering* behavior Cache Components implements by default. `cacheComponents: true` is the single flag that turns PPR on along with `use cache` and the removal of implicit dynamic-API-triggers-whole-route behavior; you no longer set an experimental PPR flag separately.\n\n### Does `use cache` persist across deployments?\n\nNo. Every cache key includes the build ID (or your configured `deploymentId`), so a new deploy starts with an empty cache, even for the durable `use cache: remote` variant. That's deliberate — it guarantees a deploy never serves output built from stale code.\n\n### What happens if I forget `cacheLife` on a `use cache` function?\n\nNothing breaks — the `default` profile applies (5-minute client-side stale window, 15-minute server-side revalidate, no time-based expiry). The framework recommends setting it explicitly anyway, since the alternative is a lifetime that's easy to lose track of.\n\n## Cheat sheet\n\n| Task | Code | Notes |\n| --- | --- | --- |\n| Enable Cache Components | `cacheComponents: true` in `next.config.ts` | Requires Node.js runtime |\n| Cache a data function | `'use cache'` at the top of an async function | Result keyed by arguments + captured closures |\n| Cache a whole component | `'use cache'` at the top of an async component | Composed `children`\u002Fslots pass through uncached |\n| Cache every export in a file | `'use cache'` at the top of the file | Every exported function must be async |\n| Set an explicit lifetime | `cacheLife('hours')` | One call per function invocation, inside the cached scope |\n| Tag a cache entry | `cacheTag('posts')` | Pairs with `updateTag`\u002F`revalidateTag` |\n| Invalidate on demand | `updateTag('posts')` inside a Server Action | Invalidates every entry with that tag |\n| Stream request-specific data | Wrap in `\u003CSuspense fallback={...}>`, read `cookies()`\u002F`headers()` inside | Fallback ships in the static shell; content streams at request time |\n| Get a unique value per request | `await connection()` then `Math.random()`\u002F`Date.now()`, inside `\u003CSuspense>` | Forces request-time evaluation instead of a cached build-time value |\n\n```tsx\n\u002F\u002F The pattern in one page: static, cached, and streaming together\nimport { Suspense } from 'react';\nimport { cookies } from 'next\u002Fheaders';\nimport { cacheLife, cacheTag } from 'next\u002Fcache';\n\nexport default function BlogPage() {\n  return (\n    \u003C>\n      \u003Cheader>Static — prerendered automatically\u003C\u002Fheader>\n      \u003CRecentPosts \u002F>{\u002F* cached, joins the static shell *\u002F}\n      \u003CSuspense fallback={\u003Cp>Loading your preferences…\u003C\u002Fp>}>\n        \u003CUserPreferences \u002F>{\u002F* streams in at request time *\u002F}\n      \u003C\u002FSuspense>\n    \u003C\u002F>\n  );\n}\n\nasync function RecentPosts() {\n  'use cache';\n  cacheLife('hours');\n  cacheTag('posts');\n  const posts = await fetch('https:\u002F\u002Fapi.example.com\u002Fposts').then((r) => r.json());\n  return \u003Cul>{posts.map((p: any) => \u003Cli key={p.id}>{p.title}\u003C\u002Fli>)}\u003C\u002Ful>;\n}\n\nasync function UserPreferences() {\n  const theme = (await cookies()).get('theme')?.value ?? 'light';\n  return \u003Caside>Theme: {theme}\u003C\u002Faside>;\n}\n```\n\n## Key takeaways\n\n- The previous App Router model gave each *route* one verdict — static or dynamic — decided by its most demanding component. Cache Components moves that decision down to individual functions and components.\n- `use cache` caches a result by its inputs; pair it with an explicit `cacheLife` so the lifetime is visible at the call site, not implicit.\n- `\u003CSuspense>` is how a genuinely request-specific piece streams in without dragging the rest of the page's caching down with it — it doesn't make a component dynamic, it gives one that already is somewhere to put its fallback.\n- Time-based (`cacheLife`) and on-demand (`cacheTag` + `updateTag`) revalidation are complementary, not competing — most real content wants both.\n- The line between \"this needs live data\" and \"this route needs live data\" is now drawn at the component, and that's the whole point.\n\n## Back to that layout\n\nThe fix for the theme cookie wasn't to stop reading it — it was to stop letting one read decide the fate of everything around it. Move the read into its own component, wrap it in `\u003CSuspense>`, and the header, the article, and the \"related posts\" list go back to shipping from the edge, instantly, while the one thing that actually needed to know who's visiting still gets to ask.\n\nIf you want the background this article assumes — what a Server Component actually is and why \"it's just SSR\" is the wrong mental model — [Server Components Without the Hype](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fserver-components-without-the-hype-a-mental-model-that-sticks-4k5e) covers that half, and this article picks up from there.\n\nWhat's the last route you had to manually pull out of \"fully dynamic\" — and did you know at the time which single line caused it?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnextjs-weekly-cache-components-explained\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":47,"canonical":417,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnextjs-weekly-cache-components-explained","01a0499d-b521-7458-aa67-20ea2a9b14b8",{"id":420,"locked":18},"01a0499d-b5af-737a-8dc6-eede043320c7",[422],{"id":423,"slug":46,"title":424,"_count":425},"01a0499d-b5d2-76da-8c1c-672084f98cf3","Next.js Cache Components",{"questions":426},8,[428],{"locale":13,"slug":46},{"id":423,"slug":46,"title":424,"_count":430,"questionCount":426},{"questions":426},[432,434,438,442,446,448,452,456,457,461,465,469],{"slug":65,"name":66,"articles":433},77,{"slug":435,"name":436,"articles":437},"frontend","Frontend",65,{"slug":439,"name":440,"articles":441},"javascript","Javascript",64,{"slug":443,"name":444,"articles":445},"css","Css",29,{"slug":71,"name":72,"articles":447},16,{"slug":449,"name":450,"articles":451},"typescript","Typescript",11,{"slug":453,"name":454,"articles":455},"performance","Performance",10,{"slug":68,"name":69,"articles":426},{"slug":458,"name":459,"articles":460},"grammar","Grammar",6,{"slug":462,"name":463,"articles":464},"node","Node",5,{"slug":466,"name":467,"articles":468},"programming","Programming",4,{"slug":470,"name":471,"articles":468},"html","Html",{"items":473,"meta":603},[474,486,507,525,545,564,583],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":475,"likeCount":19,"commentCount":19,"author":476,"vertical":477,"topic":478,"tags":479,"_count":484,"playground":485,"hasQuiz":17,"hasPlayground":17},19,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[480,481,482,483],{"slug":61,"name":62,"color":48},{"slug":68,"name":69,"color":48},{"slug":65,"name":66,"color":48},{"slug":71,"name":72,"color":48},{"assessments":75},{"slug":46},{"id":487,"slug":488,"title":489,"subtitle":48,"excerpt":490,"coverUrl":491,"locale":13,"readingMinutes":464,"publishedAt":492,"viewCount":493,"likeCount":19,"commentCount":19,"author":494,"vertical":495,"topic":496,"tags":498,"_count":505,"playground":506,"hasQuiz":17,"hasPlayground":17},"01a04446-4fc4-73e1-b763-eda8c9cf631f","navigation-api-intercept-navigation","Your Router Doesn't Intercept Navigation. It Reacts To It.","Double-click a link during a slow route change and most SPA routers render two pages and settle on the wrong one — because they're listening for navigation after it already happened. The Navigation API lets you stop it before it starts.","\u002Fmedia\u002Fcovers\u002Fnavigation-api-intercept-navigation.png","2026-09-01T11:23:43.875Z",30,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":439,"name":497},"JavaScript",[499,500,501,504],{"slug":439,"name":440,"color":48},{"slug":65,"name":66,"color":48},{"slug":502,"name":503,"color":48},"browser","Browser",{"slug":435,"name":436,"color":48},{"assessments":75},{"slug":488},{"id":508,"slug":509,"title":510,"subtitle":48,"excerpt":511,"coverUrl":512,"locale":13,"readingMinutes":51,"publishedAt":513,"viewCount":514,"likeCount":19,"commentCount":19,"author":515,"vertical":516,"topic":517,"tags":518,"_count":523,"playground":524,"hasQuiz":17,"hasPlayground":17},"019ff73a-fb5b-73ce-b725-2912167a7dde","weekly-debounce-and-throttle","Debounce and Throttle in JavaScript: The Complete Guide","Learn debounce and throttle in JavaScript step by step, with worked examples, edge cases, and a copy-paste cheat sheet for search, scroll, and resize.","\u002Fmedia\u002Fcovers\u002Fweekly-debounce-and-throttle.png","2026-08-31T13:12:14.139Z",79,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":439,"name":497},[519,520,521,522],{"slug":439,"name":440,"color":48},{"slug":65,"name":66,"color":48},{"slug":71,"name":72,"color":48},{"slug":453,"name":454,"color":48},{"assessments":75},{"slug":509},{"id":526,"slug":527,"title":528,"subtitle":48,"excerpt":529,"coverUrl":530,"locale":13,"readingMinutes":460,"publishedAt":531,"viewCount":532,"likeCount":19,"commentCount":19,"author":533,"vertical":534,"topic":535,"tags":536,"_count":543,"playground":544,"hasQuiz":17,"hasPlayground":17},"01a03cdb-3743-73ef-ab9d-21f03158ca35","server-sent-events-eventsource-live-updates","You Don't Need a WebSocket for That Live Feed","A live notification badge, a progress bar, a dashboard counter — you reached for socket.io and started hand-rolling reconnect logic. The browser already ships a simpler API that does it for you.","\u002Fmedia\u002Fcovers\u002Fserver-sent-events-eventsource-live-updates.png","2026-08-31T13:11:42.385Z",47,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":439,"name":497},[537,538,539,540],{"slug":439,"name":440,"color":48},{"slug":65,"name":66,"color":48},{"slug":71,"name":72,"color":48},{"slug":541,"name":542,"color":48},"apis","Apis",{"assessments":75},{"slug":527},{"id":546,"slug":547,"title":548,"subtitle":48,"excerpt":549,"coverUrl":550,"locale":13,"readingMinutes":460,"publishedAt":551,"viewCount":552,"likeCount":19,"commentCount":19,"author":553,"vertical":554,"topic":555,"tags":557,"_count":562,"playground":563,"hasQuiz":17,"hasPlayground":17},"01a037b3-f71a-755b-9820-9f2489daaebd","css-aspect-ratio-replace-padding-hack","Delete Your padding-bottom Aspect-Ratio Hack","For a decade the only way to keep a responsive box's shape was a padding percentage and an absolutely positioned wrapper. One CSS property replaces it — with one gotcha on real images nobody warns you about.","\u002Fmedia\u002Fcovers\u002Fcss-aspect-ratio-replace-padding-hack.png","2026-08-30T11:36:40.014Z",85,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":443,"name":556},"CSS",[558,559,560,561],{"slug":443,"name":444,"color":48},{"slug":65,"name":66,"color":48},{"slug":435,"name":436,"color":48},{"slug":71,"name":72,"color":48},{"assessments":75},{"slug":547},{"id":565,"slug":566,"title":567,"subtitle":48,"excerpt":568,"coverUrl":569,"locale":13,"readingMinutes":570,"publishedAt":571,"viewCount":572,"likeCount":19,"commentCount":19,"author":573,"vertical":574,"topic":575,"tags":576,"_count":581,"playground":582,"hasQuiz":17,"hasPlayground":17},"01a04446-373a-775b-8aaf-b72c8952123d","react-weekly-rerender-vs-remount","React Re-render vs Remount: What Actually Triggers Each","A practical guide to React re-render vs remount: what type, position, and key decide, why state resets unexpectedly, and how to force a remount.","\u002Fmedia\u002Fcovers\u002Freact-weekly-rerender-vs-remount.png",13,"2026-08-29T12:32:41.200Z",164,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":68,"name":69},[577,578,579,580],{"slug":68,"name":69,"color":48},{"slug":439,"name":440,"color":48},{"slug":71,"name":72,"color":48},{"slug":65,"name":66,"color":48},{"assessments":75},{"slug":566},{"id":584,"slug":585,"title":586,"subtitle":48,"excerpt":587,"coverUrl":588,"locale":13,"readingMinutes":468,"publishedAt":589,"viewCount":590,"likeCount":19,"commentCount":19,"author":591,"vertical":592,"topic":593,"tags":594,"_count":601,"playground":602,"hasQuiz":17,"hasPlayground":17},"01a03297-a2d3-7318-bffe-5edb58824c5d","css-focus-visible-keyboard-focus-rings","The outline: none Debate Has Been Over Since 2020","A designer flags the ugly blue focus ring in review. An engineer deletes it with outline: none. Nobody notices the keyboard-only user who now can't tell where they are on the page.","\u002Fmedia\u002Fcovers\u002Fcss-focus-visible-keyboard-focus-rings.png","2026-08-29T12:32:09.792Z",134,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":443,"name":556},[595,596,599,600],{"slug":443,"name":444,"color":48},{"slug":597,"name":598,"color":48},"accessibility","Accessibility",{"slug":65,"name":66,"color":48},{"slug":435,"name":436,"color":48},{"assessments":75},{"slug":585},{"page":75,"perPage":604,"total":433,"totalPages":451},7,"\u003Cdiv class=\"shj shj-lang-tsx shj-multiline\" data-lang=\"tsx\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F app\u002Flayout.tsx — pre-Cache-Components App Router\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> cookies \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'next\u002Fheaders'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">default\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">RootLayout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> children \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> children\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">React\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-class\">ReactNode\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> theme \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">cookies\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">get\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'theme'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">?.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">??\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'light'\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">html\u003C\u002Fspan> \u003Cspan class=\"shj-class\">lang\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"en\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">body\u003C\u002Fspan> \u003Cspan class=\"shj-class\">className\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>theme\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>children\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">body\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">html\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F next.config.ts\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-class\">NextConfig\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'next'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> nextConfig\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">NextConfig\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  cacheComponents\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">default\u003C\u002Fspan> nextConfig;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F app\u002Flib\u002Fposts.ts\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> cacheLife \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'next\u002Fcache'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">getRecentPosts\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-str\">'use cache'\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">cacheLife\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'hours'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> res \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'https:\u002F\u002Fapi.example.com\u002Fposts'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> res\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">json\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-tsx shj-multiline\" data-lang=\"tsx\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F app\u002Fblog\u002Frecent-posts.tsx\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> cacheLife\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> cacheTag \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'next\u002Fcache'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">RecentPosts\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-str\">'use cache'\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">cacheLife\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'hours'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">cacheTag\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'posts'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> res \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'https:\u002F\u002Fapi.example.com\u002Fposts'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> posts\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> id\u003Cspan class=\"shj-type\">: string\u003C\u002Fspan>; title\u003Cspan class=\"shj-type\">: string\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> res\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">json\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">ul\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>posts\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">map\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>p\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n        \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">li\u003C\u002Fspan> \u003Cspan class=\"shj-class\">key\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>p\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>id\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>p\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>title\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">li\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">ul\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-tsx shj-multiline\" data-lang=\"tsx\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F app\u002Flayout.tsx — with Cache Components\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> cookies \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'next\u002Fheaders'\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Suspense\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'react'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">default\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">RootLayout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> children \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> children\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">React\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-class\">ReactNode\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">html\u003C\u002Fspan> \u003Cspan class=\"shj-class\">lang\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"en\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">body\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">Suspense\u003C\u002Fspan> \u003Cspan class=\"shj-class\">fallback\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>\u003Cspan class=\"shj-num\">null\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n          \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">ThemeBody\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>children\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">ThemeBody\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">Suspense\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">body\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">html\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ThemeBody\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> children \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> children\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">React\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-class\">ReactNode\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> theme \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">cookies\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">get\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'theme'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">?.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">??\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'light'\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">div\u003C\u002Fspan> \u003Cspan class=\"shj-class\">className\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>theme\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>children\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">div\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F app\u002Factions.ts\n\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'use server'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> updateTag \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'next\u002Fcache'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">publishPost\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>formData\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">FormData\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> db\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>posts\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">create\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F* ... *\u002F\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">updateTag\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'posts'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F every 'posts'-tagged cache entry is now stale\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-tsx shj-multiline\" data-lang=\"tsx\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003Cdiv>21\u003C\u002Fdiv>\u003Cdiv>22\u003C\u002Fdiv>\u003Cdiv>23\u003C\u002Fdiv>\u003Cdiv>24\u003C\u002Fdiv>\u003Cdiv>25\u003C\u002Fdiv>\u003Cdiv>26\u003C\u002Fdiv>\u003Cdiv>27\u003C\u002Fdiv>\u003Cdiv>28\u003C\u002Fdiv>\u003Cdiv>29\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F The pattern in one page: static, cached, and streaming together\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Suspense\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'react'\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> cookies \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'next\u002Fheaders'\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> cacheLife\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> cacheTag \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'next\u002Fcache'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">default\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">BlogPage\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">header\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Static — prerendered automatically\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">header\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">RecentPosts\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F* cached, joins the static shell *\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">Suspense\u003C\u002Fspan> \u003Cspan class=\"shj-class\">fallback\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">p\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Loading your preferences…\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">p\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">UserPreferences\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F* streams in at request time *\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\n      \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">Suspense\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">RecentPosts\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-str\">'use cache'\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">cacheLife\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'hours'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">cacheTag\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'posts'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> posts \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'https:\u002F\u002Fapi.example.com\u002Fposts'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>r\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> r\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">json\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">ul\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>posts\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">map\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>p\u003Cspan class=\"shj-type\">: any\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">li\u003C\u002Fspan> \u003Cspan class=\"shj-class\">key\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>p\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>id\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>p\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>title\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">li\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">ul\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">UserPreferences\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> theme \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">cookies\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">get\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'theme'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">?.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">??\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'light'\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">aside\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Theme: \u003Cspan class=\"shj-oper\">{\u003C\u002Fspan>theme\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">aside\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":613},[]]