[{"data":1,"prerenderedAt":4938},["ShallowReactive",2],{"page-\u002Freact\u002F22-server-components-and-ssr":3},{"id":4,"title":5,"body":6,"description":4931,"extension":4932,"meta":4933,"navigation":76,"path":4934,"seo":4935,"stem":4936,"__hash__":4937},"content\u002Freact\u002F22-server-components-and-ssr.md","22 — Server Components and SSR",{"type":7,"value":8,"toc":4909},"minimark",[9,13,18,196,200,429,433,749,753,947,952,1103,1243,1247,1418,1422,1690,1694,1931,1935,1998,2219,2223,2346,2350,2617,2621,2786,2790,3566,3570,3800,3804,3971,3975,4166,4170,4173,4409,4744,4748,4905],[10,11,5],"h1",{"id":12},"_22-server-components-and-ssr",[14,15,17],"h2",{"id":16},"csr-vs-ssr-vs-rsc-three-rendering-models","CSR vs SSR vs RSC — Three Rendering Models",[19,20,23],"code-wrapper",{"filename":21,"language":22},"rendering_models.js","javascript",[24,25,29],"pre",{"className":26,"code":27,"language":22,"meta":28,"style":28},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F CSR (Client-Side Rendering) — the default for pre-RSC React SPAs:\n\u002F\u002F 1. Browser downloads empty HTML + JS bundle\n\u002F\u002F 2. React renders the entire tree in the browser\n\u002F\u002F 3. Data fetching happens in useEffect after mount (waterfall)\n\u002F\u002F Pros: simple, no server needed, rich interactivity\n\u002F\u002F Cons: slow first paint, poor SEO, empty HTML on initial load\n\n\u002F\u002F SSR (Server-Side Rendering) — traditional React SSR:\n\u002F\u002F 1. Server renders components to HTML string\n\u002F\u002F 2. Browser receives HTML (visible immediately)\n\u002F\u002F 3. Browser downloads JS, hydrates (reattaches event listeners)\n\u002F\u002F 4. SAME component code ships to browser — it must hydrate\n\u002F\u002F Pros: fast first paint, good SEO\n\u002F\u002F Cons: ALL component code still ships to client bundle\n\n\u002F\u002F RSC (React Server Components):\n\u002F\u002F 1. Server components render on server, output serialized UI description\n\u002F\u002F 2. Server component code NEVER ships to browser (zero client bundle)\n\u002F\u002F 3. Only client components (marked 'use client') ship to browser\n\u002F\u002F 4. Can be async, fetch data directly, access server resources\n\u002F\u002F Pros: smaller bundles, no data waterfall, server-only deps stay server-side\n\u002F\u002F Cons: no useState\u002FuseEffect\u002Fevent handlers in server components\n\n\u002F\u002F KEY DISTINCTION:\n\u002F\u002F SSR = WHEN rendering first happens (server first, then hydrate)\n\u002F\u002F RSC = WHERE a component's code permanently lives (some never leave server)\n\u002F\u002F A framework can use BOTH — which is what Next.js App Router does.\n","",[30,31,32,41,47,53,59,65,71,78,84,90,96,102,108,114,120,125,131,137,143,149,155,161,167,172,178,184,190],"code",{"__ignoreMap":28},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sdCPZ","\u002F\u002F CSR (Client-Side Rendering) — the default for pre-RSC React SPAs:\n",[33,42,44],{"class":35,"line":43},2,[33,45,46],{"class":39},"\u002F\u002F 1. Browser downloads empty HTML + JS bundle\n",[33,48,50],{"class":35,"line":49},3,[33,51,52],{"class":39},"\u002F\u002F 2. React renders the entire tree in the browser\n",[33,54,56],{"class":35,"line":55},4,[33,57,58],{"class":39},"\u002F\u002F 3. Data fetching happens in useEffect after mount (waterfall)\n",[33,60,62],{"class":35,"line":61},5,[33,63,64],{"class":39},"\u002F\u002F Pros: simple, no server needed, rich interactivity\n",[33,66,68],{"class":35,"line":67},6,[33,69,70],{"class":39},"\u002F\u002F Cons: slow first paint, poor SEO, empty HTML on initial load\n",[33,72,74],{"class":35,"line":73},7,[33,75,77],{"emptyLinePlaceholder":76},true,"\n",[33,79,81],{"class":35,"line":80},8,[33,82,83],{"class":39},"\u002F\u002F SSR (Server-Side Rendering) — traditional React SSR:\n",[33,85,87],{"class":35,"line":86},9,[33,88,89],{"class":39},"\u002F\u002F 1. Server renders components to HTML string\n",[33,91,93],{"class":35,"line":92},10,[33,94,95],{"class":39},"\u002F\u002F 2. Browser receives HTML (visible immediately)\n",[33,97,99],{"class":35,"line":98},11,[33,100,101],{"class":39},"\u002F\u002F 3. Browser downloads JS, hydrates (reattaches event listeners)\n",[33,103,105],{"class":35,"line":104},12,[33,106,107],{"class":39},"\u002F\u002F 4. SAME component code ships to browser — it must hydrate\n",[33,109,111],{"class":35,"line":110},13,[33,112,113],{"class":39},"\u002F\u002F Pros: fast first paint, good SEO\n",[33,115,117],{"class":35,"line":116},14,[33,118,119],{"class":39},"\u002F\u002F Cons: ALL component code still ships to client bundle\n",[33,121,123],{"class":35,"line":122},15,[33,124,77],{"emptyLinePlaceholder":76},[33,126,128],{"class":35,"line":127},16,[33,129,130],{"class":39},"\u002F\u002F RSC (React Server Components):\n",[33,132,134],{"class":35,"line":133},17,[33,135,136],{"class":39},"\u002F\u002F 1. Server components render on server, output serialized UI description\n",[33,138,140],{"class":35,"line":139},18,[33,141,142],{"class":39},"\u002F\u002F 2. Server component code NEVER ships to browser (zero client bundle)\n",[33,144,146],{"class":35,"line":145},19,[33,147,148],{"class":39},"\u002F\u002F 3. Only client components (marked 'use client') ship to browser\n",[33,150,152],{"class":35,"line":151},20,[33,153,154],{"class":39},"\u002F\u002F 4. Can be async, fetch data directly, access server resources\n",[33,156,158],{"class":35,"line":157},21,[33,159,160],{"class":39},"\u002F\u002F Pros: smaller bundles, no data waterfall, server-only deps stay server-side\n",[33,162,164],{"class":35,"line":163},22,[33,165,166],{"class":39},"\u002F\u002F Cons: no useState\u002FuseEffect\u002Fevent handlers in server components\n",[33,168,170],{"class":35,"line":169},23,[33,171,77],{"emptyLinePlaceholder":76},[33,173,175],{"class":35,"line":174},24,[33,176,177],{"class":39},"\u002F\u002F KEY DISTINCTION:\n",[33,179,181],{"class":35,"line":180},25,[33,182,183],{"class":39},"\u002F\u002F SSR = WHEN rendering first happens (server first, then hydrate)\n",[33,185,187],{"class":35,"line":186},26,[33,188,189],{"class":39},"\u002F\u002F RSC = WHERE a component's code permanently lives (some never leave server)\n",[33,191,193],{"class":35,"line":192},27,[33,194,195],{"class":39},"\u002F\u002F A framework can use BOTH — which is what Next.js App Router does.\n",[14,197,199],{"id":198},"server-components-the-default","Server Components — The Default",[19,201,203],{"filename":202,"language":22},"server_component.js",[24,204,206],{"className":26,"code":205,"language":22,"meta":28,"style":28},"\u002F\u002F Server Component (default — no directive needed)\n\u002F\u002F Can: be async, fetch data directly, access databases\u002Ffilesystem\u002Fsecrets\n\u002F\u002F Cannot: use useState\u002FuseEffect\u002Fany hook, attach event handlers, use browser APIs\n\nimport { db } from '.\u002Fdb'\nimport AddToCartButton from '.\u002FAddToCartButton'\n\nexport default async function ProductPage({ productId }) {\n  \u002F\u002F Direct async data fetching — no useEffect, no loading state,\n  \u002F\u002F no client-side waterfall. The component simply doesn't render\n  \u002F\u002F until its data is ready.\n  const product = await db.products.findUnique({ where: { id: productId } })\n\n  return (\n    \u003Cdiv>\n      \u003Ch1>{product.name}\u003C\u002Fh1>\n      \u003Cp>${product.price}\u003C\u002Fp>\n      {\u002F* Interactivity is delegated to a client component —\n          server components cannot attach onClick handlers *\u002F}\n      \u003CAddToCartButton productId={product.id} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,207,208,213,218,223,227,244,256,260,288,293,298,303,327,331,339,351,365,379,387,395,411,420,425],{"__ignoreMap":28},[33,209,210],{"class":35,"line":36},[33,211,212],{"class":39},"\u002F\u002F Server Component (default — no directive needed)\n",[33,214,215],{"class":35,"line":43},[33,216,217],{"class":39},"\u002F\u002F Can: be async, fetch data directly, access databases\u002Ffilesystem\u002Fsecrets\n",[33,219,220],{"class":35,"line":49},[33,221,222],{"class":39},"\u002F\u002F Cannot: use useState\u002FuseEffect\u002Fany hook, attach event handlers, use browser APIs\n",[33,224,225],{"class":35,"line":55},[33,226,77],{"emptyLinePlaceholder":76},[33,228,229,233,237,240],{"class":35,"line":61},[33,230,232],{"class":231},"svdQ7","import",[33,234,236],{"class":235},"ssxIu"," { db } ",[33,238,239],{"class":231},"from",[33,241,243],{"class":242},"sJ6F3"," '.\u002Fdb'\n",[33,245,246,248,251,253],{"class":35,"line":67},[33,247,232],{"class":231},[33,249,250],{"class":235}," AddToCartButton ",[33,252,239],{"class":231},[33,254,255],{"class":242}," '.\u002FAddToCartButton'\n",[33,257,258],{"class":35,"line":73},[33,259,77],{"emptyLinePlaceholder":76},[33,261,262,265,268,271,274,278,281,285],{"class":35,"line":80},[33,263,264],{"class":231},"export",[33,266,267],{"class":231}," default",[33,269,270],{"class":231}," async",[33,272,273],{"class":231}," function",[33,275,277],{"class":276},"sIsaT"," ProductPage",[33,279,280],{"class":235},"({ ",[33,282,284],{"class":283},"sCrzJ","productId",[33,286,287],{"class":235}," }) {\n",[33,289,290],{"class":35,"line":86},[33,291,292],{"class":39},"  \u002F\u002F Direct async data fetching — no useEffect, no loading state,\n",[33,294,295],{"class":35,"line":92},[33,296,297],{"class":39},"  \u002F\u002F no client-side waterfall. The component simply doesn't render\n",[33,299,300],{"class":35,"line":98},[33,301,302],{"class":39},"  \u002F\u002F until its data is ready.\n",[33,304,305,308,312,315,318,321,324],{"class":35,"line":104},[33,306,307],{"class":231},"  const",[33,309,311],{"class":310},"snvgF"," product",[33,313,314],{"class":231}," =",[33,316,317],{"class":231}," await",[33,319,320],{"class":235}," db.products.",[33,322,323],{"class":276},"findUnique",[33,325,326],{"class":235},"({ where: { id: productId } })\n",[33,328,329],{"class":35,"line":110},[33,330,77],{"emptyLinePlaceholder":76},[33,332,333,336],{"class":35,"line":116},[33,334,335],{"class":231},"  return",[33,337,338],{"class":235}," (\n",[33,340,341,344,348],{"class":35,"line":122},[33,342,343],{"class":235},"    \u003C",[33,345,347],{"class":346},"sk71V","div",[33,349,350],{"class":235},">\n",[33,352,353,356,358,361,363],{"class":35,"line":127},[33,354,355],{"class":235},"      \u003C",[33,357,10],{"class":346},[33,359,360],{"class":235},">{product.name}\u003C\u002F",[33,362,10],{"class":346},[33,364,350],{"class":235},[33,366,367,369,372,375,377],{"class":35,"line":133},[33,368,355],{"class":235},[33,370,371],{"class":346},"p",[33,373,374],{"class":235},">${product.price}\u003C\u002F",[33,376,371],{"class":346},[33,378,350],{"class":235},[33,380,381,384],{"class":35,"line":139},[33,382,383],{"class":235},"      {",[33,385,386],{"class":39},"\u002F* Interactivity is delegated to a client component —\n",[33,388,389,392],{"class":35,"line":145},[33,390,391],{"class":39},"          server components cannot attach onClick handlers *\u002F",[33,393,394],{"class":235},"}\n",[33,396,397,399,402,405,408],{"class":35,"line":151},[33,398,355],{"class":235},[33,400,401],{"class":310},"AddToCartButton",[33,403,404],{"class":276}," productId",[33,406,407],{"class":231},"=",[33,409,410],{"class":235},"{product.id} \u002F>\n",[33,412,413,416,418],{"class":35,"line":157},[33,414,415],{"class":235},"    \u003C\u002F",[33,417,347],{"class":346},[33,419,350],{"class":235},[33,421,422],{"class":35,"line":163},[33,423,424],{"class":235},"  )\n",[33,426,427],{"class":35,"line":169},[33,428,394],{"class":235},[14,430,432],{"id":431},"client-components-use-client-directive","Client Components — 'use client' Directive",[19,434,436],{"filename":435,"language":22},"client_component.js",[24,437,439],{"className":26,"code":438,"language":22,"meta":28,"style":28},"\u002F\u002F Client Component (explicit opt-in required)\n\u002F\u002F The 'use client' directive marks the boundary: everything in this file\n\u002F\u002F (and everything it imports that isn't passed from outside) is client bundle.\n'use client'\n\nimport { useState } from 'react'\n\nexport default function AddToCartButton({ productId }) {\n  const [isAdding, setIsAdding] = useState(false)\n\n  async function handleClick() {\n    setIsAdding(true)\n    await fetch('\u002Fapi\u002Fcart', {\n      method: 'POST',\n      body: JSON.stringify({ productId }),\n      headers: { 'Content-Type': 'application\u002Fjson' },\n    })\n    setIsAdding(false)\n  }\n\n  return (\n    \u003Cbutton onClick={handleClick} disabled={isAdding}>\n      {isAdding ? 'Adding…' : 'Add to Cart'}\n    \u003C\u002Fbutton>\n  )\n}\n\n\u002F\u002F 'use client' does NOT mean \"only renders on the client\" — a client\n\u002F\u002F component still participates in SSR for the initial page load.\n\u002F\u002F It means: \"this component's code is allowed to run in the browser,\n\u002F\u002F and therefore must be included in the client bundle\" — which is\n\u002F\u002F also the ONLY way to use useState, useEffect, event handlers, or\n\u002F\u002F any browser-only API (window, localStorage).\n",[30,440,441,446,451,456,461,465,477,481,498,531,535,548,560,576,587,604,621,626,636,641,645,651,674,693,701,705,709,713,719,725,731,737,743],{"__ignoreMap":28},[33,442,443],{"class":35,"line":36},[33,444,445],{"class":39},"\u002F\u002F Client Component (explicit opt-in required)\n",[33,447,448],{"class":35,"line":43},[33,449,450],{"class":39},"\u002F\u002F The 'use client' directive marks the boundary: everything in this file\n",[33,452,453],{"class":35,"line":49},[33,454,455],{"class":39},"\u002F\u002F (and everything it imports that isn't passed from outside) is client bundle.\n",[33,457,458],{"class":35,"line":55},[33,459,460],{"class":242},"'use client'\n",[33,462,463],{"class":35,"line":61},[33,464,77],{"emptyLinePlaceholder":76},[33,466,467,469,472,474],{"class":35,"line":67},[33,468,232],{"class":231},[33,470,471],{"class":235}," { useState } ",[33,473,239],{"class":231},[33,475,476],{"class":242}," 'react'\n",[33,478,479],{"class":35,"line":73},[33,480,77],{"emptyLinePlaceholder":76},[33,482,483,485,487,489,492,494,496],{"class":35,"line":80},[33,484,264],{"class":231},[33,486,267],{"class":231},[33,488,273],{"class":231},[33,490,491],{"class":276}," AddToCartButton",[33,493,280],{"class":235},[33,495,284],{"class":283},[33,497,287],{"class":235},[33,499,500,502,505,508,511,514,517,519,522,525,528],{"class":35,"line":86},[33,501,307],{"class":231},[33,503,504],{"class":235}," [",[33,506,507],{"class":310},"isAdding",[33,509,510],{"class":235},", ",[33,512,513],{"class":310},"setIsAdding",[33,515,516],{"class":235},"] ",[33,518,407],{"class":231},[33,520,521],{"class":276}," useState",[33,523,524],{"class":235},"(",[33,526,527],{"class":310},"false",[33,529,530],{"class":235},")\n",[33,532,533],{"class":35,"line":92},[33,534,77],{"emptyLinePlaceholder":76},[33,536,537,540,542,545],{"class":35,"line":98},[33,538,539],{"class":231},"  async",[33,541,273],{"class":231},[33,543,544],{"class":276}," handleClick",[33,546,547],{"class":235},"() {\n",[33,549,550,553,555,558],{"class":35,"line":104},[33,551,552],{"class":276},"    setIsAdding",[33,554,524],{"class":235},[33,556,557],{"class":310},"true",[33,559,530],{"class":235},[33,561,562,565,568,570,573],{"class":35,"line":110},[33,563,564],{"class":231},"    await",[33,566,567],{"class":276}," fetch",[33,569,524],{"class":235},[33,571,572],{"class":242},"'\u002Fapi\u002Fcart'",[33,574,575],{"class":235},", {\n",[33,577,578,581,584],{"class":35,"line":116},[33,579,580],{"class":235},"      method: ",[33,582,583],{"class":242},"'POST'",[33,585,586],{"class":235},",\n",[33,588,589,592,595,598,601],{"class":35,"line":122},[33,590,591],{"class":235},"      body: ",[33,593,594],{"class":310},"JSON",[33,596,597],{"class":235},".",[33,599,600],{"class":276},"stringify",[33,602,603],{"class":235},"({ productId }),\n",[33,605,606,609,612,615,618],{"class":35,"line":127},[33,607,608],{"class":235},"      headers: { ",[33,610,611],{"class":242},"'Content-Type'",[33,613,614],{"class":235},": ",[33,616,617],{"class":242},"'application\u002Fjson'",[33,619,620],{"class":235}," },\n",[33,622,623],{"class":35,"line":133},[33,624,625],{"class":235},"    })\n",[33,627,628,630,632,634],{"class":35,"line":139},[33,629,552],{"class":276},[33,631,524],{"class":235},[33,633,527],{"class":310},[33,635,530],{"class":235},[33,637,638],{"class":35,"line":145},[33,639,640],{"class":235},"  }\n",[33,642,643],{"class":35,"line":151},[33,644,77],{"emptyLinePlaceholder":76},[33,646,647,649],{"class":35,"line":157},[33,648,335],{"class":231},[33,650,338],{"class":235},[33,652,653,655,658,661,663,666,669,671],{"class":35,"line":163},[33,654,343],{"class":235},[33,656,657],{"class":346},"button",[33,659,660],{"class":276}," onClick",[33,662,407],{"class":231},[33,664,665],{"class":235},"{handleClick} ",[33,667,668],{"class":276},"disabled",[33,670,407],{"class":231},[33,672,673],{"class":235},"{isAdding}>\n",[33,675,676,679,682,685,688,691],{"class":35,"line":169},[33,677,678],{"class":235},"      {isAdding ",[33,680,681],{"class":231},"?",[33,683,684],{"class":242}," 'Adding…'",[33,686,687],{"class":231}," :",[33,689,690],{"class":242}," 'Add to Cart'",[33,692,394],{"class":235},[33,694,695,697,699],{"class":35,"line":174},[33,696,415],{"class":235},[33,698,657],{"class":346},[33,700,350],{"class":235},[33,702,703],{"class":35,"line":180},[33,704,424],{"class":235},[33,706,707],{"class":35,"line":186},[33,708,394],{"class":235},[33,710,711],{"class":35,"line":192},[33,712,77],{"emptyLinePlaceholder":76},[33,714,716],{"class":35,"line":715},28,[33,717,718],{"class":39},"\u002F\u002F 'use client' does NOT mean \"only renders on the client\" — a client\n",[33,720,722],{"class":35,"line":721},29,[33,723,724],{"class":39},"\u002F\u002F component still participates in SSR for the initial page load.\n",[33,726,728],{"class":35,"line":727},30,[33,729,730],{"class":39},"\u002F\u002F It means: \"this component's code is allowed to run in the browser,\n",[33,732,734],{"class":35,"line":733},31,[33,735,736],{"class":39},"\u002F\u002F and therefore must be included in the client bundle\" — which is\n",[33,738,740],{"class":35,"line":739},32,[33,741,742],{"class":39},"\u002F\u002F also the ONLY way to use useState, useEffect, event handlers, or\n",[33,744,746],{"class":35,"line":745},33,[33,747,748],{"class":39},"\u002F\u002F any browser-only API (window, localStorage).\n",[14,750,752],{"id":751},"the-boundary-only-goes-one-direction","The Boundary Only Goes One Direction",[19,754,756],{"filename":755,"language":22},"boundary_direction.js",[24,757,759],{"className":26,"code":758,"language":22,"meta":28,"style":28},"\u002F\u002F A server component CAN import and render a client component directly.\n\u002F\u002F The REVERSE is NOT allowed: a client component CANNOT import a server\n\u002F\u002F component, because everything inside client-component code must be\n\u002F\u002F safe to ship to the browser, and server components may import\n\u002F\u002F database clients, filesystem APIs, or secrets.\n\n\u002F\u002F BROKEN: a client component cannot import a server component directly\n'use client'\nimport ProductReviews from '.\u002FProductReviews'  \u002F\u002F server component — FAILS\n\nfunction ProductDetailClient({ productId }) {\n  const [tab, setTab] = useState('description')\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => setTab('reviews')}>Reviews\u003C\u002Fbutton>\n      {tab === 'reviews' && \u003CProductReviews productId={productId} \u002F>}\n    \u003C\u002Fdiv>\n  )\n}\n",[30,760,761,766,771,776,781,786,790,795,799,814,818,832,859,865,873,904,931,939,943],{"__ignoreMap":28},[33,762,763],{"class":35,"line":36},[33,764,765],{"class":39},"\u002F\u002F A server component CAN import and render a client component directly.\n",[33,767,768],{"class":35,"line":43},[33,769,770],{"class":39},"\u002F\u002F The REVERSE is NOT allowed: a client component CANNOT import a server\n",[33,772,773],{"class":35,"line":49},[33,774,775],{"class":39},"\u002F\u002F component, because everything inside client-component code must be\n",[33,777,778],{"class":35,"line":55},[33,779,780],{"class":39},"\u002F\u002F safe to ship to the browser, and server components may import\n",[33,782,783],{"class":35,"line":61},[33,784,785],{"class":39},"\u002F\u002F database clients, filesystem APIs, or secrets.\n",[33,787,788],{"class":35,"line":67},[33,789,77],{"emptyLinePlaceholder":76},[33,791,792],{"class":35,"line":73},[33,793,794],{"class":39},"\u002F\u002F BROKEN: a client component cannot import a server component directly\n",[33,796,797],{"class":35,"line":80},[33,798,460],{"class":242},[33,800,801,803,806,808,811],{"class":35,"line":86},[33,802,232],{"class":231},[33,804,805],{"class":235}," ProductReviews ",[33,807,239],{"class":231},[33,809,810],{"class":242}," '.\u002FProductReviews'",[33,812,813],{"class":39},"  \u002F\u002F server component — FAILS\n",[33,815,816],{"class":35,"line":92},[33,817,77],{"emptyLinePlaceholder":76},[33,819,820,823,826,828,830],{"class":35,"line":98},[33,821,822],{"class":231},"function",[33,824,825],{"class":276}," ProductDetailClient",[33,827,280],{"class":235},[33,829,284],{"class":283},[33,831,287],{"class":235},[33,833,834,836,838,841,843,846,848,850,852,854,857],{"class":35,"line":104},[33,835,307],{"class":231},[33,837,504],{"class":235},[33,839,840],{"class":310},"tab",[33,842,510],{"class":235},[33,844,845],{"class":310},"setTab",[33,847,516],{"class":235},[33,849,407],{"class":231},[33,851,521],{"class":276},[33,853,524],{"class":235},[33,855,856],{"class":242},"'description'",[33,858,530],{"class":235},[33,860,861,863],{"class":35,"line":110},[33,862,335],{"class":231},[33,864,338],{"class":235},[33,866,867,869,871],{"class":35,"line":116},[33,868,343],{"class":235},[33,870,347],{"class":346},[33,872,350],{"class":235},[33,874,875,877,879,881,883,886,889,892,894,897,900,902],{"class":35,"line":122},[33,876,355],{"class":235},[33,878,657],{"class":346},[33,880,660],{"class":276},[33,882,407],{"class":231},[33,884,885],{"class":235},"{() ",[33,887,888],{"class":231},"=>",[33,890,891],{"class":276}," setTab",[33,893,524],{"class":235},[33,895,896],{"class":242},"'reviews'",[33,898,899],{"class":235},")}>Reviews\u003C\u002F",[33,901,657],{"class":346},[33,903,350],{"class":235},[33,905,906,909,912,915,918,921,924,926,928],{"class":35,"line":127},[33,907,908],{"class":235},"      {tab ",[33,910,911],{"class":231},"===",[33,913,914],{"class":242}," 'reviews'",[33,916,917],{"class":231}," &&",[33,919,920],{"class":235}," \u003C",[33,922,923],{"class":310},"ProductReviews",[33,925,404],{"class":276},[33,927,407],{"class":231},[33,929,930],{"class":235},"{productId} \u002F>}\n",[33,932,933,935,937],{"class":35,"line":133},[33,934,415],{"class":235},[33,936,347],{"class":346},[33,938,350],{"class":235},[33,940,941],{"class":35,"line":139},[33,942,424],{"class":235},[33,944,945],{"class":35,"line":145},[33,946,394],{"class":235},[948,949,951],"h3",{"id":950},"the-children-as-props-escape-hatch","The Children-as-Props Escape Hatch",[19,953,955],{"filename":954,"language":22},"children_slot.js",[24,956,958],{"className":26,"code":957,"language":22,"meta":28,"style":28},"\u002F\u002F ProductDetailClient.js — Client Component, renders whatever `children`\n\u002F\u002F it's given without ever importing it — it doesn't know or care that\n\u002F\u002F children is a Server Component.\n'use client'\n\nimport { useState } from 'react'\n\nfunction ProductDetailClient({ children }) {\n  const [tab, setTab] = useState('description')\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => setTab('reviews')}>Reviews\u003C\u002Fbutton>\n      {tab === 'reviews' && children}\n    \u003C\u002Fdiv>\n  )\n}\n",[30,959,960,965,970,975,979,983,993,997,1010,1034,1040,1048,1074,1087,1095,1099],{"__ignoreMap":28},[33,961,962],{"class":35,"line":36},[33,963,964],{"class":39},"\u002F\u002F ProductDetailClient.js — Client Component, renders whatever `children`\n",[33,966,967],{"class":35,"line":43},[33,968,969],{"class":39},"\u002F\u002F it's given without ever importing it — it doesn't know or care that\n",[33,971,972],{"class":35,"line":49},[33,973,974],{"class":39},"\u002F\u002F children is a Server Component.\n",[33,976,977],{"class":35,"line":55},[33,978,460],{"class":242},[33,980,981],{"class":35,"line":61},[33,982,77],{"emptyLinePlaceholder":76},[33,984,985,987,989,991],{"class":35,"line":67},[33,986,232],{"class":231},[33,988,471],{"class":235},[33,990,239],{"class":231},[33,992,476],{"class":242},[33,994,995],{"class":35,"line":73},[33,996,77],{"emptyLinePlaceholder":76},[33,998,999,1001,1003,1005,1008],{"class":35,"line":80},[33,1000,822],{"class":231},[33,1002,825],{"class":276},[33,1004,280],{"class":235},[33,1006,1007],{"class":283},"children",[33,1009,287],{"class":235},[33,1011,1012,1014,1016,1018,1020,1022,1024,1026,1028,1030,1032],{"class":35,"line":86},[33,1013,307],{"class":231},[33,1015,504],{"class":235},[33,1017,840],{"class":310},[33,1019,510],{"class":235},[33,1021,845],{"class":310},[33,1023,516],{"class":235},[33,1025,407],{"class":231},[33,1027,521],{"class":276},[33,1029,524],{"class":235},[33,1031,856],{"class":242},[33,1033,530],{"class":235},[33,1035,1036,1038],{"class":35,"line":92},[33,1037,335],{"class":231},[33,1039,338],{"class":235},[33,1041,1042,1044,1046],{"class":35,"line":98},[33,1043,343],{"class":235},[33,1045,347],{"class":346},[33,1047,350],{"class":235},[33,1049,1050,1052,1054,1056,1058,1060,1062,1064,1066,1068,1070,1072],{"class":35,"line":104},[33,1051,355],{"class":235},[33,1053,657],{"class":346},[33,1055,660],{"class":276},[33,1057,407],{"class":231},[33,1059,885],{"class":235},[33,1061,888],{"class":231},[33,1063,891],{"class":276},[33,1065,524],{"class":235},[33,1067,896],{"class":242},[33,1069,899],{"class":235},[33,1071,657],{"class":346},[33,1073,350],{"class":235},[33,1075,1076,1078,1080,1082,1084],{"class":35,"line":110},[33,1077,908],{"class":235},[33,1079,911],{"class":231},[33,1081,914],{"class":242},[33,1083,917],{"class":231},[33,1085,1086],{"class":235}," children}\n",[33,1088,1089,1091,1093],{"class":35,"line":116},[33,1090,415],{"class":235},[33,1092,347],{"class":346},[33,1094,350],{"class":235},[33,1096,1097],{"class":35,"line":122},[33,1098,424],{"class":235},[33,1100,1101],{"class":35,"line":127},[33,1102,394],{"class":235},[19,1104,1106],{"filename":1105,"language":22},"children_slot_composition.js",[24,1107,1109],{"className":26,"code":1108,"language":22,"meta":28,"style":28},"\u002F\u002F ProductPage.js — Server Component composes the two, passing the server\n\u002F\u002F component as children INTO the client component from outside its own file.\n\u002F\u002F The composition happens ABOVE the client boundary, not inside it.\n\nimport ProductDetailClient from '.\u002FProductDetailClient'\nimport ProductReviews from '.\u002FProductReviews'\n\nexport default async function ProductPage({ productId }) {\n  return (\n    \u003CProductDetailClient>\n      \u003CProductReviews productId={productId} \u002F>\n    \u003C\u002FProductDetailClient>\n  )\n}\n\n\u002F\u002F This \"slot\" pattern — server components composed into client components\n\u002F\u002F via children\u002Fprops rather than direct imports — is the standard trick\n\u002F\u002F for maximizing how much of a tree stays server-only while still\n\u002F\u002F allowing interactive islands where they're genuinely needed.\n",[30,1110,1111,1116,1121,1126,1130,1142,1153,1157,1175,1181,1190,1203,1211,1215,1219,1223,1228,1233,1238],{"__ignoreMap":28},[33,1112,1113],{"class":35,"line":36},[33,1114,1115],{"class":39},"\u002F\u002F ProductPage.js — Server Component composes the two, passing the server\n",[33,1117,1118],{"class":35,"line":43},[33,1119,1120],{"class":39},"\u002F\u002F component as children INTO the client component from outside its own file.\n",[33,1122,1123],{"class":35,"line":49},[33,1124,1125],{"class":39},"\u002F\u002F The composition happens ABOVE the client boundary, not inside it.\n",[33,1127,1128],{"class":35,"line":55},[33,1129,77],{"emptyLinePlaceholder":76},[33,1131,1132,1134,1137,1139],{"class":35,"line":61},[33,1133,232],{"class":231},[33,1135,1136],{"class":235}," ProductDetailClient ",[33,1138,239],{"class":231},[33,1140,1141],{"class":242}," '.\u002FProductDetailClient'\n",[33,1143,1144,1146,1148,1150],{"class":35,"line":67},[33,1145,232],{"class":231},[33,1147,805],{"class":235},[33,1149,239],{"class":231},[33,1151,1152],{"class":242}," '.\u002FProductReviews'\n",[33,1154,1155],{"class":35,"line":73},[33,1156,77],{"emptyLinePlaceholder":76},[33,1158,1159,1161,1163,1165,1167,1169,1171,1173],{"class":35,"line":80},[33,1160,264],{"class":231},[33,1162,267],{"class":231},[33,1164,270],{"class":231},[33,1166,273],{"class":231},[33,1168,277],{"class":276},[33,1170,280],{"class":235},[33,1172,284],{"class":283},[33,1174,287],{"class":235},[33,1176,1177,1179],{"class":35,"line":86},[33,1178,335],{"class":231},[33,1180,338],{"class":235},[33,1182,1183,1185,1188],{"class":35,"line":92},[33,1184,343],{"class":235},[33,1186,1187],{"class":310},"ProductDetailClient",[33,1189,350],{"class":235},[33,1191,1192,1194,1196,1198,1200],{"class":35,"line":98},[33,1193,355],{"class":235},[33,1195,923],{"class":310},[33,1197,404],{"class":276},[33,1199,407],{"class":231},[33,1201,1202],{"class":235},"{productId} \u002F>\n",[33,1204,1205,1207,1209],{"class":35,"line":104},[33,1206,415],{"class":235},[33,1208,1187],{"class":310},[33,1210,350],{"class":235},[33,1212,1213],{"class":35,"line":110},[33,1214,424],{"class":235},[33,1216,1217],{"class":35,"line":116},[33,1218,394],{"class":235},[33,1220,1221],{"class":35,"line":122},[33,1222,77],{"emptyLinePlaceholder":76},[33,1224,1225],{"class":35,"line":127},[33,1226,1227],{"class":39},"\u002F\u002F This \"slot\" pattern — server components composed into client components\n",[33,1229,1230],{"class":35,"line":133},[33,1231,1232],{"class":39},"\u002F\u002F via children\u002Fprops rather than direct imports — is the standard trick\n",[33,1234,1235],{"class":35,"line":139},[33,1236,1237],{"class":39},"\u002F\u002F for maximizing how much of a tree stays server-only while still\n",[33,1239,1240],{"class":35,"line":145},[33,1241,1242],{"class":39},"\u002F\u002F allowing interactive islands where they're genuinely needed.\n",[14,1244,1246],{"id":1245},"zero-bundle-size-components","Zero-Bundle-Size Components",[19,1248,1250],{"filename":1249,"language":22},"zero_bundle.js",[24,1251,1253],{"className":26,"code":1252,"language":22,"meta":28,"style":28},"\u002F\u002F Server Component: `marked` (a markdown parser) and its dependencies\n\u002F\u002F NEVER ship to the browser at all — only the resulting HTML-like output does.\nimport { marked } from 'marked'\nimport { db } from '.\u002Fdb'\n\nasync function ArticleBody({ articleId }) {\n  const article = await db.articles.findUnique({ where: { id: articleId } })\n  const html = marked(article.markdownContent)  \u002F\u002F runs on server only\n  return \u003Cdiv dangerouslySetInnerHTML={{ __html: html }} \u002F>\n}\n\n\u002F\u002F Contrast with pre-RSC: `marked` would be a client-side dependency\n\u002F\u002F (bundled and downloaded by every visitor) purely because it ran\n\u002F\u002F inside a component, even though its output is static text with\n\u002F\u002F no interactivity. RSC lets bundle size scale with how much of an\n\u002F\u002F app is actually INTERACTIVE, not with how much of it merely RENDERS.\n\n\u002F\u002F ANTI-PATTERN: marking a file 'use client' when only a tiny part\n\u002F\u002F needs interactivity. The ENTIRE file + its imports ship to the client.\n\u002F\u002F Extract the interactive bit into its own 'use client' leaf component\n\u002F\u002F and keep the rest server-only.\n",[30,1254,1255,1260,1265,1277,1287,1291,1308,1327,1345,1361,1365,1369,1374,1379,1384,1389,1394,1398,1403,1408,1413],{"__ignoreMap":28},[33,1256,1257],{"class":35,"line":36},[33,1258,1259],{"class":39},"\u002F\u002F Server Component: `marked` (a markdown parser) and its dependencies\n",[33,1261,1262],{"class":35,"line":43},[33,1263,1264],{"class":39},"\u002F\u002F NEVER ship to the browser at all — only the resulting HTML-like output does.\n",[33,1266,1267,1269,1272,1274],{"class":35,"line":49},[33,1268,232],{"class":231},[33,1270,1271],{"class":235}," { marked } ",[33,1273,239],{"class":231},[33,1275,1276],{"class":242}," 'marked'\n",[33,1278,1279,1281,1283,1285],{"class":35,"line":55},[33,1280,232],{"class":231},[33,1282,236],{"class":235},[33,1284,239],{"class":231},[33,1286,243],{"class":242},[33,1288,1289],{"class":35,"line":61},[33,1290,77],{"emptyLinePlaceholder":76},[33,1292,1293,1296,1298,1301,1303,1306],{"class":35,"line":67},[33,1294,1295],{"class":231},"async",[33,1297,273],{"class":231},[33,1299,1300],{"class":276}," ArticleBody",[33,1302,280],{"class":235},[33,1304,1305],{"class":283},"articleId",[33,1307,287],{"class":235},[33,1309,1310,1312,1315,1317,1319,1322,1324],{"class":35,"line":73},[33,1311,307],{"class":231},[33,1313,1314],{"class":310}," article",[33,1316,314],{"class":231},[33,1318,317],{"class":231},[33,1320,1321],{"class":235}," db.articles.",[33,1323,323],{"class":276},[33,1325,1326],{"class":235},"({ where: { id: articleId } })\n",[33,1328,1329,1331,1334,1336,1339,1342],{"class":35,"line":80},[33,1330,307],{"class":231},[33,1332,1333],{"class":310}," html",[33,1335,314],{"class":231},[33,1337,1338],{"class":276}," marked",[33,1340,1341],{"class":235},"(article.markdownContent)  ",[33,1343,1344],{"class":39},"\u002F\u002F runs on server only\n",[33,1346,1347,1349,1351,1353,1356,1358],{"class":35,"line":86},[33,1348,335],{"class":231},[33,1350,920],{"class":235},[33,1352,347],{"class":346},[33,1354,1355],{"class":276}," dangerouslySetInnerHTML",[33,1357,407],{"class":231},[33,1359,1360],{"class":235},"{{ __html: html }} \u002F>\n",[33,1362,1363],{"class":35,"line":92},[33,1364,394],{"class":235},[33,1366,1367],{"class":35,"line":98},[33,1368,77],{"emptyLinePlaceholder":76},[33,1370,1371],{"class":35,"line":104},[33,1372,1373],{"class":39},"\u002F\u002F Contrast with pre-RSC: `marked` would be a client-side dependency\n",[33,1375,1376],{"class":35,"line":110},[33,1377,1378],{"class":39},"\u002F\u002F (bundled and downloaded by every visitor) purely because it ran\n",[33,1380,1381],{"class":35,"line":116},[33,1382,1383],{"class":39},"\u002F\u002F inside a component, even though its output is static text with\n",[33,1385,1386],{"class":35,"line":122},[33,1387,1388],{"class":39},"\u002F\u002F no interactivity. RSC lets bundle size scale with how much of an\n",[33,1390,1391],{"class":35,"line":127},[33,1392,1393],{"class":39},"\u002F\u002F app is actually INTERACTIVE, not with how much of it merely RENDERS.\n",[33,1395,1396],{"class":35,"line":133},[33,1397,77],{"emptyLinePlaceholder":76},[33,1399,1400],{"class":35,"line":139},[33,1401,1402],{"class":39},"\u002F\u002F ANTI-PATTERN: marking a file 'use client' when only a tiny part\n",[33,1404,1405],{"class":35,"line":145},[33,1406,1407],{"class":39},"\u002F\u002F needs interactivity. The ENTIRE file + its imports ship to the client.\n",[33,1409,1410],{"class":35,"line":151},[33,1411,1412],{"class":39},"\u002F\u002F Extract the interactive bit into its own 'use client' leaf component\n",[33,1414,1415],{"class":35,"line":157},[33,1416,1417],{"class":39},"\u002F\u002F and keep the rest server-only.\n",[14,1419,1421],{"id":1420},"data-fetching-without-a-waterfall","Data Fetching Without a Waterfall",[19,1423,1425],{"filename":1424,"language":22},"data_fetching.js",[24,1426,1428],{"className":26,"code":1427,"language":22,"meta":28,"style":28},"\u002F\u002F Sibling server components fetching independently both start their\n\u002F\u002F requests CONCURRENTLY on the server — NOT a client-side waterfall\n\u002F\u002F of sequential useEffect calls.\n\nasync function ProductPage({ productId }) {\n  return (\n    \u003Cdiv>\n      \u003CProductInfo productId={productId} \u002F>\n      \u003CProductReviews productId={productId} \u002F>\n      \u003CRelatedProducts productId={productId} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n\nasync function ProductInfo({ productId }) {\n  const product = await db.products.findUnique({ where: { id: productId } })\n  return \u003Ch1>{product.name}\u003C\u002Fh1>\n}\n\nasync function ProductReviews({ productId }) {\n  const reviews = await db.reviews.findMany({ where: { productId } })\n  return reviews.map(r => \u003CReview key={r.id} review={r} \u002F>)\n}\n\n\u002F\u002F All three async components start fetching in parallel — the server\n\u002F\u002F renders them concurrently, no useEffect waterfall, no loading states\n\u002F\u002F for the initial render. The page simply isn't sent until all data\n\u002F\u002F is ready (unless streaming with Suspense — see below).\n",[30,1429,1430,1435,1440,1445,1449,1463,1469,1477,1490,1502,1515,1523,1527,1531,1535,1550,1566,1580,1584,1588,1603,1623,1662,1666,1670,1675,1680,1685],{"__ignoreMap":28},[33,1431,1432],{"class":35,"line":36},[33,1433,1434],{"class":39},"\u002F\u002F Sibling server components fetching independently both start their\n",[33,1436,1437],{"class":35,"line":43},[33,1438,1439],{"class":39},"\u002F\u002F requests CONCURRENTLY on the server — NOT a client-side waterfall\n",[33,1441,1442],{"class":35,"line":49},[33,1443,1444],{"class":39},"\u002F\u002F of sequential useEffect calls.\n",[33,1446,1447],{"class":35,"line":55},[33,1448,77],{"emptyLinePlaceholder":76},[33,1450,1451,1453,1455,1457,1459,1461],{"class":35,"line":61},[33,1452,1295],{"class":231},[33,1454,273],{"class":231},[33,1456,277],{"class":276},[33,1458,280],{"class":235},[33,1460,284],{"class":283},[33,1462,287],{"class":235},[33,1464,1465,1467],{"class":35,"line":67},[33,1466,335],{"class":231},[33,1468,338],{"class":235},[33,1470,1471,1473,1475],{"class":35,"line":73},[33,1472,343],{"class":235},[33,1474,347],{"class":346},[33,1476,350],{"class":235},[33,1478,1479,1481,1484,1486,1488],{"class":35,"line":80},[33,1480,355],{"class":235},[33,1482,1483],{"class":310},"ProductInfo",[33,1485,404],{"class":276},[33,1487,407],{"class":231},[33,1489,1202],{"class":235},[33,1491,1492,1494,1496,1498,1500],{"class":35,"line":86},[33,1493,355],{"class":235},[33,1495,923],{"class":310},[33,1497,404],{"class":276},[33,1499,407],{"class":231},[33,1501,1202],{"class":235},[33,1503,1504,1506,1509,1511,1513],{"class":35,"line":92},[33,1505,355],{"class":235},[33,1507,1508],{"class":310},"RelatedProducts",[33,1510,404],{"class":276},[33,1512,407],{"class":231},[33,1514,1202],{"class":235},[33,1516,1517,1519,1521],{"class":35,"line":98},[33,1518,415],{"class":235},[33,1520,347],{"class":346},[33,1522,350],{"class":235},[33,1524,1525],{"class":35,"line":104},[33,1526,424],{"class":235},[33,1528,1529],{"class":35,"line":110},[33,1530,394],{"class":235},[33,1532,1533],{"class":35,"line":116},[33,1534,77],{"emptyLinePlaceholder":76},[33,1536,1537,1539,1541,1544,1546,1548],{"class":35,"line":122},[33,1538,1295],{"class":231},[33,1540,273],{"class":231},[33,1542,1543],{"class":276}," ProductInfo",[33,1545,280],{"class":235},[33,1547,284],{"class":283},[33,1549,287],{"class":235},[33,1551,1552,1554,1556,1558,1560,1562,1564],{"class":35,"line":127},[33,1553,307],{"class":231},[33,1555,311],{"class":310},[33,1557,314],{"class":231},[33,1559,317],{"class":231},[33,1561,320],{"class":235},[33,1563,323],{"class":276},[33,1565,326],{"class":235},[33,1567,1568,1570,1572,1574,1576,1578],{"class":35,"line":133},[33,1569,335],{"class":231},[33,1571,920],{"class":235},[33,1573,10],{"class":346},[33,1575,360],{"class":235},[33,1577,10],{"class":346},[33,1579,350],{"class":235},[33,1581,1582],{"class":35,"line":139},[33,1583,394],{"class":235},[33,1585,1586],{"class":35,"line":145},[33,1587,77],{"emptyLinePlaceholder":76},[33,1589,1590,1592,1594,1597,1599,1601],{"class":35,"line":151},[33,1591,1295],{"class":231},[33,1593,273],{"class":231},[33,1595,1596],{"class":276}," ProductReviews",[33,1598,280],{"class":235},[33,1600,284],{"class":283},[33,1602,287],{"class":235},[33,1604,1605,1607,1610,1612,1614,1617,1620],{"class":35,"line":157},[33,1606,307],{"class":231},[33,1608,1609],{"class":310}," reviews",[33,1611,314],{"class":231},[33,1613,317],{"class":231},[33,1615,1616],{"class":235}," db.reviews.",[33,1618,1619],{"class":276},"findMany",[33,1621,1622],{"class":235},"({ where: { productId } })\n",[33,1624,1625,1627,1630,1633,1635,1638,1641,1643,1646,1649,1651,1654,1657,1659],{"class":35,"line":163},[33,1626,335],{"class":231},[33,1628,1629],{"class":235}," reviews.",[33,1631,1632],{"class":276},"map",[33,1634,524],{"class":235},[33,1636,1637],{"class":283},"r",[33,1639,1640],{"class":231}," =>",[33,1642,920],{"class":235},[33,1644,1645],{"class":310},"Review",[33,1647,1648],{"class":276}," key",[33,1650,407],{"class":231},[33,1652,1653],{"class":235},"{r.id} ",[33,1655,1656],{"class":276},"review",[33,1658,407],{"class":231},[33,1660,1661],{"class":235},"{r} \u002F>)\n",[33,1663,1664],{"class":35,"line":169},[33,1665,394],{"class":235},[33,1667,1668],{"class":35,"line":174},[33,1669,77],{"emptyLinePlaceholder":76},[33,1671,1672],{"class":35,"line":180},[33,1673,1674],{"class":39},"\u002F\u002F All three async components start fetching in parallel — the server\n",[33,1676,1677],{"class":35,"line":186},[33,1678,1679],{"class":39},"\u002F\u002F renders them concurrently, no useEffect waterfall, no loading states\n",[33,1681,1682],{"class":35,"line":192},[33,1683,1684],{"class":39},"\u002F\u002F for the initial render. The page simply isn't sent until all data\n",[33,1686,1687],{"class":35,"line":715},[33,1688,1689],{"class":39},"\u002F\u002F is ready (unless streaming with Suspense — see below).\n",[14,1691,1693],{"id":1692},"streaming-with-suspense","Streaming with Suspense",[19,1695,1697],{"filename":1696,"language":22},"streaming_suspense.js",[24,1698,1700],{"className":26,"code":1699,"language":22,"meta":28,"style":28},"import { Suspense } from 'react'\n\n\u002F\u002F Wrapping a slower server component in \u003CSuspense> lets faster siblings\n\u002F\u002F STREAM to the browser first — the same \u003CSuspense> primitive from Ch 20-21,\n\u002F\u002F now doing double duty as the actual data-loading mechanism.\n\nasync function ProductPage({ productId }) {\n  return (\n    \u003Cdiv>\n      {\u002F* ProductInfo is fast — renders immediately *\u002F}\n      \u003CProductInfo productId={productId} \u002F>\n\n      {\u002F* Reviews are slow — Suspense lets the rest of the page stream\n          to the browser without waiting on this specific piece *\u002F}\n      \u003CSuspense fallback={\u003CReviewsSkeleton \u002F>}>\n        \u003CProductReviews productId={productId} \u002F>\n      \u003C\u002FSuspense>\n\n      {\u002F* Related products also slow — independent Suspense boundary *\u002F}\n      \u003CSuspense fallback={\u003CRelatedSkeleton \u002F>}>\n        \u003CRelatedProducts productId={productId} \u002F>\n      \u003C\u002FSuspense>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F The browser receives the page shell + product info first, then reviews\n\u002F\u002F and related products stream in as their data resolves — progressive\n\u002F\u002F rendering without the user staring at a blank page or a single spinner.\n",[30,1701,1702,1713,1717,1722,1727,1732,1736,1750,1756,1764,1773,1785,1789,1796,1803,1824,1837,1846,1850,1859,1876,1888,1896,1904,1908,1912,1916,1921,1926],{"__ignoreMap":28},[33,1703,1704,1706,1709,1711],{"class":35,"line":36},[33,1705,232],{"class":231},[33,1707,1708],{"class":235}," { Suspense } ",[33,1710,239],{"class":231},[33,1712,476],{"class":242},[33,1714,1715],{"class":35,"line":43},[33,1716,77],{"emptyLinePlaceholder":76},[33,1718,1719],{"class":35,"line":49},[33,1720,1721],{"class":39},"\u002F\u002F Wrapping a slower server component in \u003CSuspense> lets faster siblings\n",[33,1723,1724],{"class":35,"line":55},[33,1725,1726],{"class":39},"\u002F\u002F STREAM to the browser first — the same \u003CSuspense> primitive from Ch 20-21,\n",[33,1728,1729],{"class":35,"line":61},[33,1730,1731],{"class":39},"\u002F\u002F now doing double duty as the actual data-loading mechanism.\n",[33,1733,1734],{"class":35,"line":67},[33,1735,77],{"emptyLinePlaceholder":76},[33,1737,1738,1740,1742,1744,1746,1748],{"class":35,"line":73},[33,1739,1295],{"class":231},[33,1741,273],{"class":231},[33,1743,277],{"class":276},[33,1745,280],{"class":235},[33,1747,284],{"class":283},[33,1749,287],{"class":235},[33,1751,1752,1754],{"class":35,"line":80},[33,1753,335],{"class":231},[33,1755,338],{"class":235},[33,1757,1758,1760,1762],{"class":35,"line":86},[33,1759,343],{"class":235},[33,1761,347],{"class":346},[33,1763,350],{"class":235},[33,1765,1766,1768,1771],{"class":35,"line":92},[33,1767,383],{"class":235},[33,1769,1770],{"class":39},"\u002F* ProductInfo is fast — renders immediately *\u002F",[33,1772,394],{"class":235},[33,1774,1775,1777,1779,1781,1783],{"class":35,"line":98},[33,1776,355],{"class":235},[33,1778,1483],{"class":310},[33,1780,404],{"class":276},[33,1782,407],{"class":231},[33,1784,1202],{"class":235},[33,1786,1787],{"class":35,"line":104},[33,1788,77],{"emptyLinePlaceholder":76},[33,1790,1791,1793],{"class":35,"line":110},[33,1792,383],{"class":235},[33,1794,1795],{"class":39},"\u002F* Reviews are slow — Suspense lets the rest of the page stream\n",[33,1797,1798,1801],{"class":35,"line":116},[33,1799,1800],{"class":39},"          to the browser without waiting on this specific piece *\u002F",[33,1802,394],{"class":235},[33,1804,1805,1807,1810,1813,1815,1818,1821],{"class":35,"line":122},[33,1806,355],{"class":235},[33,1808,1809],{"class":310},"Suspense",[33,1811,1812],{"class":276}," fallback",[33,1814,407],{"class":231},[33,1816,1817],{"class":235},"{\u003C",[33,1819,1820],{"class":310},"ReviewsSkeleton",[33,1822,1823],{"class":235}," \u002F>}>\n",[33,1825,1826,1829,1831,1833,1835],{"class":35,"line":127},[33,1827,1828],{"class":235},"        \u003C",[33,1830,923],{"class":310},[33,1832,404],{"class":276},[33,1834,407],{"class":231},[33,1836,1202],{"class":235},[33,1838,1839,1842,1844],{"class":35,"line":133},[33,1840,1841],{"class":235},"      \u003C\u002F",[33,1843,1809],{"class":310},[33,1845,350],{"class":235},[33,1847,1848],{"class":35,"line":139},[33,1849,77],{"emptyLinePlaceholder":76},[33,1851,1852,1854,1857],{"class":35,"line":145},[33,1853,383],{"class":235},[33,1855,1856],{"class":39},"\u002F* Related products also slow — independent Suspense boundary *\u002F",[33,1858,394],{"class":235},[33,1860,1861,1863,1865,1867,1869,1871,1874],{"class":35,"line":151},[33,1862,355],{"class":235},[33,1864,1809],{"class":310},[33,1866,1812],{"class":276},[33,1868,407],{"class":231},[33,1870,1817],{"class":235},[33,1872,1873],{"class":310},"RelatedSkeleton",[33,1875,1823],{"class":235},[33,1877,1878,1880,1882,1884,1886],{"class":35,"line":157},[33,1879,1828],{"class":235},[33,1881,1508],{"class":310},[33,1883,404],{"class":276},[33,1885,407],{"class":231},[33,1887,1202],{"class":235},[33,1889,1890,1892,1894],{"class":35,"line":163},[33,1891,1841],{"class":235},[33,1893,1809],{"class":310},[33,1895,350],{"class":235},[33,1897,1898,1900,1902],{"class":35,"line":169},[33,1899,415],{"class":235},[33,1901,347],{"class":346},[33,1903,350],{"class":235},[33,1905,1906],{"class":35,"line":174},[33,1907,424],{"class":235},[33,1909,1910],{"class":35,"line":180},[33,1911,394],{"class":235},[33,1913,1914],{"class":35,"line":186},[33,1915,77],{"emptyLinePlaceholder":76},[33,1917,1918],{"class":35,"line":192},[33,1919,1920],{"class":39},"\u002F\u002F The browser receives the page shell + product info first, then reviews\n",[33,1922,1923],{"class":35,"line":715},[33,1924,1925],{"class":39},"\u002F\u002F and related products stream in as their data resolves — progressive\n",[33,1927,1928],{"class":35,"line":721},[33,1929,1930],{"class":39},"\u002F\u002F rendering without the user staring at a blank page or a single spinner.\n",[14,1932,1934],{"id":1933},"nextjs-app-router-where-rsc-lives","Next.js App Router — Where RSC Lives",[19,1936,1939],{"filename":1937,"language":1938},"nextjs_setup.sh","bash",[24,1940,1943],{"className":1941,"code":1942,"language":1938,"meta":28,"style":28},"language-bash shiki shiki-themes github-light github-dark","# Next.js App Router is the primary production environment for RSC today.\n# Every file under app\u002F is a server component by default unless it opts\n# in with 'use client'.\n\nnpx create-next-app@latest my-app\n# Would you like to use the App Router? Yes\ncd my-app\nnpm run dev\n",[30,1944,1945,1950,1955,1960,1964,1975,1980,1987],{"__ignoreMap":28},[33,1946,1947],{"class":35,"line":36},[33,1948,1949],{"class":39},"# Next.js App Router is the primary production environment for RSC today.\n",[33,1951,1952],{"class":35,"line":43},[33,1953,1954],{"class":39},"# Every file under app\u002F is a server component by default unless it opts\n",[33,1956,1957],{"class":35,"line":49},[33,1958,1959],{"class":39},"# in with 'use client'.\n",[33,1961,1962],{"class":35,"line":55},[33,1963,77],{"emptyLinePlaceholder":76},[33,1965,1966,1969,1972],{"class":35,"line":61},[33,1967,1968],{"class":276},"npx",[33,1970,1971],{"class":242}," create-next-app@latest",[33,1973,1974],{"class":242}," my-app\n",[33,1976,1977],{"class":35,"line":67},[33,1978,1979],{"class":39},"# Would you like to use the App Router? Yes\n",[33,1981,1982,1985],{"class":35,"line":73},[33,1983,1984],{"class":310},"cd",[33,1986,1974],{"class":242},[33,1988,1989,1992,1995],{"class":35,"line":80},[33,1990,1991],{"class":276},"npm",[33,1993,1994],{"class":242}," run",[33,1996,1997],{"class":242}," dev\n",[19,1999,2001],{"filename":2000,"language":22},"nextjs_structure.js",[24,2002,2004],{"className":26,"code":2003,"language":22,"meta":28,"style":28},"\u002F\u002F Next.js App Router file conventions:\n\u002F\u002F app\u002F\n\u002F\u002F   layout.js        — root layout (server component by default), wraps every page\n\u002F\u002F   page.js          — route's UI (server component by default)\n\u002F\u002F   loading.js       — Suspense fallback for the route (auto-wrapped in \u003CSuspense>)\n\u002F\u002F   error.js         — error boundary for the route (must be a client component)\n\u002F\u002F   not-found.js     — 404 UI for the route\n\u002F\u002F   [id]\u002F            — dynamic segment (useParams equivalent)\n\u002F\u002F   products\u002F\n\u002F\u002F     page.js        — \u002Fproducts route\n\u002F\u002F     [productId]\u002F\n\u002F\u002F       page.js      — \u002Fproducts\u002F:productId route\n\n\u002F\u002F app\u002Fproducts\u002F[productId]\u002Fpage.js — Server Component by default\nimport { db } from '@\u002Flib\u002Fdb'\n\nexport default async function ProductPage({ params }) {\n  const product = await db.products.findUnique({ where: { id: params.productId } })\n  return (\n    \u003Cdiv>\n      \u003Ch1>{product.name}\u003C\u002Fh1>\n      \u003Cp>{product.price}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F app\u002Fproducts\u002F[productId]\u002Floading.js — auto-wrapped in \u003CSuspense>\nexport default function Loading() {\n  return \u003CProductSkeleton \u002F>\n}\n",[30,2005,2006,2011,2016,2021,2026,2031,2036,2041,2046,2051,2056,2061,2066,2070,2075,2086,2090,2109,2126,2132,2140,2152,2165,2173,2177,2181,2185,2190,2203,2215],{"__ignoreMap":28},[33,2007,2008],{"class":35,"line":36},[33,2009,2010],{"class":39},"\u002F\u002F Next.js App Router file conventions:\n",[33,2012,2013],{"class":35,"line":43},[33,2014,2015],{"class":39},"\u002F\u002F app\u002F\n",[33,2017,2018],{"class":35,"line":49},[33,2019,2020],{"class":39},"\u002F\u002F   layout.js        — root layout (server component by default), wraps every page\n",[33,2022,2023],{"class":35,"line":55},[33,2024,2025],{"class":39},"\u002F\u002F   page.js          — route's UI (server component by default)\n",[33,2027,2028],{"class":35,"line":61},[33,2029,2030],{"class":39},"\u002F\u002F   loading.js       — Suspense fallback for the route (auto-wrapped in \u003CSuspense>)\n",[33,2032,2033],{"class":35,"line":67},[33,2034,2035],{"class":39},"\u002F\u002F   error.js         — error boundary for the route (must be a client component)\n",[33,2037,2038],{"class":35,"line":73},[33,2039,2040],{"class":39},"\u002F\u002F   not-found.js     — 404 UI for the route\n",[33,2042,2043],{"class":35,"line":80},[33,2044,2045],{"class":39},"\u002F\u002F   [id]\u002F            — dynamic segment (useParams equivalent)\n",[33,2047,2048],{"class":35,"line":86},[33,2049,2050],{"class":39},"\u002F\u002F   products\u002F\n",[33,2052,2053],{"class":35,"line":92},[33,2054,2055],{"class":39},"\u002F\u002F     page.js        — \u002Fproducts route\n",[33,2057,2058],{"class":35,"line":98},[33,2059,2060],{"class":39},"\u002F\u002F     [productId]\u002F\n",[33,2062,2063],{"class":35,"line":104},[33,2064,2065],{"class":39},"\u002F\u002F       page.js      — \u002Fproducts\u002F:productId route\n",[33,2067,2068],{"class":35,"line":110},[33,2069,77],{"emptyLinePlaceholder":76},[33,2071,2072],{"class":35,"line":116},[33,2073,2074],{"class":39},"\u002F\u002F app\u002Fproducts\u002F[productId]\u002Fpage.js — Server Component by default\n",[33,2076,2077,2079,2081,2083],{"class":35,"line":122},[33,2078,232],{"class":231},[33,2080,236],{"class":235},[33,2082,239],{"class":231},[33,2084,2085],{"class":242}," '@\u002Flib\u002Fdb'\n",[33,2087,2088],{"class":35,"line":127},[33,2089,77],{"emptyLinePlaceholder":76},[33,2091,2092,2094,2096,2098,2100,2102,2104,2107],{"class":35,"line":133},[33,2093,264],{"class":231},[33,2095,267],{"class":231},[33,2097,270],{"class":231},[33,2099,273],{"class":231},[33,2101,277],{"class":276},[33,2103,280],{"class":235},[33,2105,2106],{"class":283},"params",[33,2108,287],{"class":235},[33,2110,2111,2113,2115,2117,2119,2121,2123],{"class":35,"line":139},[33,2112,307],{"class":231},[33,2114,311],{"class":310},[33,2116,314],{"class":231},[33,2118,317],{"class":231},[33,2120,320],{"class":235},[33,2122,323],{"class":276},[33,2124,2125],{"class":235},"({ where: { id: params.productId } })\n",[33,2127,2128,2130],{"class":35,"line":145},[33,2129,335],{"class":231},[33,2131,338],{"class":235},[33,2133,2134,2136,2138],{"class":35,"line":151},[33,2135,343],{"class":235},[33,2137,347],{"class":346},[33,2139,350],{"class":235},[33,2141,2142,2144,2146,2148,2150],{"class":35,"line":157},[33,2143,355],{"class":235},[33,2145,10],{"class":346},[33,2147,360],{"class":235},[33,2149,10],{"class":346},[33,2151,350],{"class":235},[33,2153,2154,2156,2158,2161,2163],{"class":35,"line":163},[33,2155,355],{"class":235},[33,2157,371],{"class":346},[33,2159,2160],{"class":235},">{product.price}\u003C\u002F",[33,2162,371],{"class":346},[33,2164,350],{"class":235},[33,2166,2167,2169,2171],{"class":35,"line":169},[33,2168,415],{"class":235},[33,2170,347],{"class":346},[33,2172,350],{"class":235},[33,2174,2175],{"class":35,"line":174},[33,2176,424],{"class":235},[33,2178,2179],{"class":35,"line":180},[33,2180,394],{"class":235},[33,2182,2183],{"class":35,"line":186},[33,2184,77],{"emptyLinePlaceholder":76},[33,2186,2187],{"class":35,"line":192},[33,2188,2189],{"class":39},"\u002F\u002F app\u002Fproducts\u002F[productId]\u002Floading.js — auto-wrapped in \u003CSuspense>\n",[33,2191,2192,2194,2196,2198,2201],{"class":35,"line":715},[33,2193,264],{"class":231},[33,2195,267],{"class":231},[33,2197,273],{"class":231},[33,2199,2200],{"class":276}," Loading",[33,2202,547],{"class":235},[33,2204,2205,2207,2209,2212],{"class":35,"line":721},[33,2206,335],{"class":231},[33,2208,920],{"class":235},[33,2210,2211],{"class":310},"ProductSkeleton",[33,2213,2214],{"class":235}," \u002F>\n",[33,2216,2217],{"class":35,"line":727},[33,2218,394],{"class":235},[14,2220,2222],{"id":2221},"hydration-server-html-meets-client-js","Hydration — Server HTML Meets Client JS",[19,2224,2226],{"filename":2225,"language":22},"hydration.js",[24,2227,2229],{"className":26,"code":2228,"language":22,"meta":28,"style":28},"\u002F\u002F Hydration = React attaching event listeners and internal state to\n\u002F\u002F already-rendered server HTML rather than re-creating DOM nodes.\n\u002F\u002F CRITICAL: the HTML the server sends and the first client render\n\u002F\u002F MUST MATCH, or React logs a hydration mismatch warning and\n\u002F\u002F DISCARDS the server HTML, re-rendering client-side — losing the\n\u002F\u002F fast-paint benefit SSR was meant to provide.\n\n\u002F\u002F BROKEN: Date.now() \u002F Math.random() \u002F typeof window checks differ\n\u002F\u002F between server and client render → hydration mismatch.\n'use client'\nimport { useState } from 'react'\n\nfunction Timestamp() {\n  \u002F\u002F Server renders this at time T1, client hydrates at time T2.\n  \u002F\u002F T1 !== T2 → mismatch → React discards server HTML, re-renders.\n  return \u003Cspan>{new Date().toLocaleTimeString()}\u003C\u002Fspan>\n}\n",[30,2230,2231,2236,2241,2246,2251,2256,2261,2265,2270,2275,2279,2289,2293,2302,2307,2312,2342],{"__ignoreMap":28},[33,2232,2233],{"class":35,"line":36},[33,2234,2235],{"class":39},"\u002F\u002F Hydration = React attaching event listeners and internal state to\n",[33,2237,2238],{"class":35,"line":43},[33,2239,2240],{"class":39},"\u002F\u002F already-rendered server HTML rather than re-creating DOM nodes.\n",[33,2242,2243],{"class":35,"line":49},[33,2244,2245],{"class":39},"\u002F\u002F CRITICAL: the HTML the server sends and the first client render\n",[33,2247,2248],{"class":35,"line":55},[33,2249,2250],{"class":39},"\u002F\u002F MUST MATCH, or React logs a hydration mismatch warning and\n",[33,2252,2253],{"class":35,"line":61},[33,2254,2255],{"class":39},"\u002F\u002F DISCARDS the server HTML, re-rendering client-side — losing the\n",[33,2257,2258],{"class":35,"line":67},[33,2259,2260],{"class":39},"\u002F\u002F fast-paint benefit SSR was meant to provide.\n",[33,2262,2263],{"class":35,"line":73},[33,2264,77],{"emptyLinePlaceholder":76},[33,2266,2267],{"class":35,"line":80},[33,2268,2269],{"class":39},"\u002F\u002F BROKEN: Date.now() \u002F Math.random() \u002F typeof window checks differ\n",[33,2271,2272],{"class":35,"line":86},[33,2273,2274],{"class":39},"\u002F\u002F between server and client render → hydration mismatch.\n",[33,2276,2277],{"class":35,"line":92},[33,2278,460],{"class":242},[33,2280,2281,2283,2285,2287],{"class":35,"line":98},[33,2282,232],{"class":231},[33,2284,471],{"class":235},[33,2286,239],{"class":231},[33,2288,476],{"class":242},[33,2290,2291],{"class":35,"line":104},[33,2292,77],{"emptyLinePlaceholder":76},[33,2294,2295,2297,2300],{"class":35,"line":110},[33,2296,822],{"class":231},[33,2298,2299],{"class":276}," Timestamp",[33,2301,547],{"class":235},[33,2303,2304],{"class":35,"line":116},[33,2305,2306],{"class":39},"  \u002F\u002F Server renders this at time T1, client hydrates at time T2.\n",[33,2308,2309],{"class":35,"line":122},[33,2310,2311],{"class":39},"  \u002F\u002F T1 !== T2 → mismatch → React discards server HTML, re-renders.\n",[33,2313,2314,2316,2318,2320,2323,2326,2329,2332,2335,2338,2340],{"class":35,"line":127},[33,2315,335],{"class":231},[33,2317,920],{"class":235},[33,2319,33],{"class":346},[33,2321,2322],{"class":235},">{",[33,2324,2325],{"class":231},"new",[33,2327,2328],{"class":276}," Date",[33,2330,2331],{"class":235},"().",[33,2333,2334],{"class":276},"toLocaleTimeString",[33,2336,2337],{"class":235},"()}\u003C\u002F",[33,2339,33],{"class":346},[33,2341,350],{"class":235},[33,2343,2344],{"class":35,"line":133},[33,2345,394],{"class":235},[948,2347,2349],{"id":2348},"fixing-hydration-mismatches","Fixing Hydration Mismatches",[19,2351,2353],{"filename":2352,"language":22},"hydration_fix.js",[24,2354,2356],{"className":26,"code":2355,"language":22,"meta":28,"style":28},"'use client'\nimport { useState, useEffect } from 'react'\n\n\u002F\u002F FIXED: render nothing (or a static placeholder) on the server\u002Ffirst\n\u002F\u002F client render, then swap in the real, environment-dependent value\n\u002F\u002F only after mount — by which point hydration has completed and a\n\u002F\u002F mismatch can't occur.\nfunction Timestamp() {\n  const [time, setTime] = useState(null)\n\n  useEffect(() => {\n    setTime(new Date().toLocaleTimeString())\n  }, [])\n\n  return \u003Cspan>{time ?? '--:--:--'}\u003C\u002Fspan>\n  \u002F\u002F Server renders \"--:--:--\", client first render also renders \"--:--:--\"\n  \u002F\u002F (match!), then useEffect runs and updates to the real time.\n}\n\n\u002F\u002F Pattern for any environment-dependent value:\nfunction ClientOnly({ children }) {\n  const [mounted, setMounted] = useState(false)\n  useEffect(() => setMounted(true), [])\n  return mounted ? children : null\n  \u002F\u002F Server renders null, client first render renders null (match!),\n  \u002F\u002F then after mount, children render. Use sparingly — it means SSR\n \u002F\u002F provides no content for this subtree.\n}\n",[30,2357,2358,2362,2373,2377,2382,2387,2392,2397,2405,2432,2436,2449,2467,2472,2476,2500,2505,2510,2514,2518,2523,2536,2562,2580,2598,2603,2608,2613],{"__ignoreMap":28},[33,2359,2360],{"class":35,"line":36},[33,2361,460],{"class":242},[33,2363,2364,2366,2369,2371],{"class":35,"line":43},[33,2365,232],{"class":231},[33,2367,2368],{"class":235}," { useState, useEffect } ",[33,2370,239],{"class":231},[33,2372,476],{"class":242},[33,2374,2375],{"class":35,"line":49},[33,2376,77],{"emptyLinePlaceholder":76},[33,2378,2379],{"class":35,"line":55},[33,2380,2381],{"class":39},"\u002F\u002F FIXED: render nothing (or a static placeholder) on the server\u002Ffirst\n",[33,2383,2384],{"class":35,"line":61},[33,2385,2386],{"class":39},"\u002F\u002F client render, then swap in the real, environment-dependent value\n",[33,2388,2389],{"class":35,"line":67},[33,2390,2391],{"class":39},"\u002F\u002F only after mount — by which point hydration has completed and a\n",[33,2393,2394],{"class":35,"line":73},[33,2395,2396],{"class":39},"\u002F\u002F mismatch can't occur.\n",[33,2398,2399,2401,2403],{"class":35,"line":80},[33,2400,822],{"class":231},[33,2402,2299],{"class":276},[33,2404,547],{"class":235},[33,2406,2407,2409,2411,2414,2416,2419,2421,2423,2425,2427,2430],{"class":35,"line":86},[33,2408,307],{"class":231},[33,2410,504],{"class":235},[33,2412,2413],{"class":310},"time",[33,2415,510],{"class":235},[33,2417,2418],{"class":310},"setTime",[33,2420,516],{"class":235},[33,2422,407],{"class":231},[33,2424,521],{"class":276},[33,2426,524],{"class":235},[33,2428,2429],{"class":310},"null",[33,2431,530],{"class":235},[33,2433,2434],{"class":35,"line":92},[33,2435,77],{"emptyLinePlaceholder":76},[33,2437,2438,2441,2444,2446],{"class":35,"line":98},[33,2439,2440],{"class":276},"  useEffect",[33,2442,2443],{"class":235},"(() ",[33,2445,888],{"class":231},[33,2447,2448],{"class":235}," {\n",[33,2450,2451,2454,2456,2458,2460,2462,2464],{"class":35,"line":104},[33,2452,2453],{"class":276},"    setTime",[33,2455,524],{"class":235},[33,2457,2325],{"class":231},[33,2459,2328],{"class":276},[33,2461,2331],{"class":235},[33,2463,2334],{"class":276},[33,2465,2466],{"class":235},"())\n",[33,2468,2469],{"class":35,"line":110},[33,2470,2471],{"class":235},"  }, [])\n",[33,2473,2474],{"class":35,"line":116},[33,2475,77],{"emptyLinePlaceholder":76},[33,2477,2478,2480,2482,2484,2487,2490,2493,2496,2498],{"class":35,"line":122},[33,2479,335],{"class":231},[33,2481,920],{"class":235},[33,2483,33],{"class":346},[33,2485,2486],{"class":235},">{time ",[33,2488,2489],{"class":231},"??",[33,2491,2492],{"class":242}," '--:--:--'",[33,2494,2495],{"class":235},"}\u003C\u002F",[33,2497,33],{"class":346},[33,2499,350],{"class":235},[33,2501,2502],{"class":35,"line":127},[33,2503,2504],{"class":39},"  \u002F\u002F Server renders \"--:--:--\", client first render also renders \"--:--:--\"\n",[33,2506,2507],{"class":35,"line":133},[33,2508,2509],{"class":39},"  \u002F\u002F (match!), then useEffect runs and updates to the real time.\n",[33,2511,2512],{"class":35,"line":139},[33,2513,394],{"class":235},[33,2515,2516],{"class":35,"line":145},[33,2517,77],{"emptyLinePlaceholder":76},[33,2519,2520],{"class":35,"line":151},[33,2521,2522],{"class":39},"\u002F\u002F Pattern for any environment-dependent value:\n",[33,2524,2525,2527,2530,2532,2534],{"class":35,"line":157},[33,2526,822],{"class":231},[33,2528,2529],{"class":276}," ClientOnly",[33,2531,280],{"class":235},[33,2533,1007],{"class":283},[33,2535,287],{"class":235},[33,2537,2538,2540,2542,2545,2547,2550,2552,2554,2556,2558,2560],{"class":35,"line":163},[33,2539,307],{"class":231},[33,2541,504],{"class":235},[33,2543,2544],{"class":310},"mounted",[33,2546,510],{"class":235},[33,2548,2549],{"class":310},"setMounted",[33,2551,516],{"class":235},[33,2553,407],{"class":231},[33,2555,521],{"class":276},[33,2557,524],{"class":235},[33,2559,527],{"class":310},[33,2561,530],{"class":235},[33,2563,2564,2566,2568,2570,2573,2575,2577],{"class":35,"line":169},[33,2565,2440],{"class":276},[33,2567,2443],{"class":235},[33,2569,888],{"class":231},[33,2571,2572],{"class":276}," setMounted",[33,2574,524],{"class":235},[33,2576,557],{"class":310},[33,2578,2579],{"class":235},"), [])\n",[33,2581,2582,2584,2587,2589,2592,2595],{"class":35,"line":174},[33,2583,335],{"class":231},[33,2585,2586],{"class":235}," mounted ",[33,2588,681],{"class":231},[33,2590,2591],{"class":235}," children ",[33,2593,2594],{"class":231},":",[33,2596,2597],{"class":310}," null\n",[33,2599,2600],{"class":35,"line":180},[33,2601,2602],{"class":39},"  \u002F\u002F Server renders null, client first render renders null (match!),\n",[33,2604,2605],{"class":35,"line":186},[33,2606,2607],{"class":39},"  \u002F\u002F then after mount, children render. Use sparingly — it means SSR\n",[33,2609,2610],{"class":35,"line":192},[33,2611,2612],{"class":39}," \u002F\u002F provides no content for this subtree.\n",[33,2614,2615],{"class":35,"line":715},[33,2616,394],{"class":235},[14,2618,2620],{"id":2619},"metadata-and-seo","Metadata and SEO",[19,2622,2624],{"filename":2623,"language":22},"metadata.js",[24,2625,2627],{"className":26,"code":2626,"language":22,"meta":28,"style":28},"\u002F\u002F Next.js App Router: metadata API for server components.\n\u002F\u002F Server-side metadata = SEO-friendly, no client JS needed for crawlers.\n\n\u002F\u002F Static metadata:\nexport const metadata = {\n  title: 'Product Catalog',\n  description: 'Browse our full product range',\n}\n\n\u002F\u002F Dynamic metadata (async — can fetch data):\nexport async function generateMetadata({ params }) {\n  const product = await db.products.findUnique({ where: { id: params.productId } })\n  return {\n    title: product.name,\n    description: product.description,\n    openGraph: {\n      images: [product.imageUrl],\n    },\n  }\n}\n\n\u002F\u002F ANTI-PATTERN: setting document.title in a client component useEffect.\n\u002F\u002F This runs AFTER hydration — crawlers that don't execute JS see the\n\u002F\u002F default title. Use the metadata API in a server component instead.\n",[30,2628,2629,2634,2639,2643,2648,2662,2672,2682,2686,2690,2695,2712,2728,2734,2739,2744,2749,2754,2759,2763,2767,2771,2776,2781],{"__ignoreMap":28},[33,2630,2631],{"class":35,"line":36},[33,2632,2633],{"class":39},"\u002F\u002F Next.js App Router: metadata API for server components.\n",[33,2635,2636],{"class":35,"line":43},[33,2637,2638],{"class":39},"\u002F\u002F Server-side metadata = SEO-friendly, no client JS needed for crawlers.\n",[33,2640,2641],{"class":35,"line":49},[33,2642,77],{"emptyLinePlaceholder":76},[33,2644,2645],{"class":35,"line":55},[33,2646,2647],{"class":39},"\u002F\u002F Static metadata:\n",[33,2649,2650,2652,2655,2658,2660],{"class":35,"line":61},[33,2651,264],{"class":231},[33,2653,2654],{"class":231}," const",[33,2656,2657],{"class":310}," metadata",[33,2659,314],{"class":231},[33,2661,2448],{"class":235},[33,2663,2664,2667,2670],{"class":35,"line":67},[33,2665,2666],{"class":235},"  title: ",[33,2668,2669],{"class":242},"'Product Catalog'",[33,2671,586],{"class":235},[33,2673,2674,2677,2680],{"class":35,"line":73},[33,2675,2676],{"class":235},"  description: ",[33,2678,2679],{"class":242},"'Browse our full product range'",[33,2681,586],{"class":235},[33,2683,2684],{"class":35,"line":80},[33,2685,394],{"class":235},[33,2687,2688],{"class":35,"line":86},[33,2689,77],{"emptyLinePlaceholder":76},[33,2691,2692],{"class":35,"line":92},[33,2693,2694],{"class":39},"\u002F\u002F Dynamic metadata (async — can fetch data):\n",[33,2696,2697,2699,2701,2703,2706,2708,2710],{"class":35,"line":98},[33,2698,264],{"class":231},[33,2700,270],{"class":231},[33,2702,273],{"class":231},[33,2704,2705],{"class":276}," generateMetadata",[33,2707,280],{"class":235},[33,2709,2106],{"class":283},[33,2711,287],{"class":235},[33,2713,2714,2716,2718,2720,2722,2724,2726],{"class":35,"line":104},[33,2715,307],{"class":231},[33,2717,311],{"class":310},[33,2719,314],{"class":231},[33,2721,317],{"class":231},[33,2723,320],{"class":235},[33,2725,323],{"class":276},[33,2727,2125],{"class":235},[33,2729,2730,2732],{"class":35,"line":110},[33,2731,335],{"class":231},[33,2733,2448],{"class":235},[33,2735,2736],{"class":35,"line":116},[33,2737,2738],{"class":235},"    title: product.name,\n",[33,2740,2741],{"class":35,"line":122},[33,2742,2743],{"class":235},"    description: product.description,\n",[33,2745,2746],{"class":35,"line":127},[33,2747,2748],{"class":235},"    openGraph: {\n",[33,2750,2751],{"class":35,"line":133},[33,2752,2753],{"class":235},"      images: [product.imageUrl],\n",[33,2755,2756],{"class":35,"line":139},[33,2757,2758],{"class":235},"    },\n",[33,2760,2761],{"class":35,"line":145},[33,2762,640],{"class":235},[33,2764,2765],{"class":35,"line":151},[33,2766,394],{"class":235},[33,2768,2769],{"class":35,"line":157},[33,2770,77],{"emptyLinePlaceholder":76},[33,2772,2773],{"class":35,"line":163},[33,2774,2775],{"class":39},"\u002F\u002F ANTI-PATTERN: setting document.title in a client component useEffect.\n",[33,2777,2778],{"class":35,"line":169},[33,2779,2780],{"class":39},"\u002F\u002F This runs AFTER hydration — crawlers that don't execute JS see the\n",[33,2782,2783],{"class":35,"line":174},[33,2784,2785],{"class":39},"\u002F\u002F default title. Use the metadata API in a server component instead.\n",[14,2787,2789],{"id":2788},"complex-implementation-full-product-page","Complex Implementation — Full Product Page",[19,2791,2793],{"filename":2792,"language":22},"complex_product_page.js",[24,2794,2796],{"className":26,"code":2795,"language":22,"meta":28,"style":28},"\u002F\u002F app\u002Fproducts\u002F[productId]\u002Fpage.js — Server Component\nimport { Suspense } from 'react'\nimport { db } from '@\u002Flib\u002Fdb'\nimport AddToCartButton from '@\u002Fcomponents\u002FAddToCartButton'\nimport ProductGallery from '@\u002Fcomponents\u002FProductGallery'\nimport ReviewForm from '@\u002Fcomponents\u002FReviewForm'\n\nexport async function generateMetadata({ params }) {\n  const product = await db.products.findUnique({ where: { id: params.productId } })\n  return { title: product.name, description: product.description }\n}\n\nexport default async function ProductPage({ params }) {\n  const product = await db.products.findUnique({\n    where: { id: params.productId },\n    include: { category: true },\n  })\n\n  if (!product) notFound()\n\n  return (\n    \u003Cdiv>\n      {\u002F* Server component: zero client bundle cost for gallery logic *\u002F}\n      \u003CProductGallery images={product.images} \u002F>\n\n      \u003Cdiv>\n        \u003Ch1>{product.name}\u003C\u002Fh1>\n        \u003Cp className=\"text-2xl\">${product.price}\u003C\u002Fp>\n        \u003Cp>{product.description}\u003C\u002Fp>\n\n        {\u002F* Client component: needs interactivity (useState, onClick) *\u002F}\n        \u003CAddToCartButton productId={product.id} \u002F>\n      \u003C\u002Fdiv>\n\n      {\u002F* Reviews stream in independently — no blocking the rest of the page *\u002F}\n      \u003CSuspense fallback={\u003CReviewsSkeleton \u002F>}>\n        \u003CReviewsSection productId={product.id} \u002F>\n      \u003C\u002FSuspense>\n\n      {\u002F* Review form is a client component (needs form state) *\u002F}\n      \u003CReviewForm productId={product.id} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F Server component — fetches reviews, no client bundle cost\nasync function ReviewsSection({ productId }) {\n  const reviews = await db.reviews.findMany({\n    where: { productId },\n    orderBy: { createdAt: 'desc' },\n    take: 10,\n  })\n\n  if (reviews.length === 0) return \u003Cp>No reviews yet.\u003C\u002Fp>\n\n  return (\n    \u003Cdiv>\n      \u003Ch2>Reviews\u003C\u002Fh2>\n      {reviews.map(review => (\n        \u003Cdiv key={review.id}>\n          \u003Cp>{review.author}\u003C\u002Fp>\n          \u003Cp>{review.rating} stars\u003C\u002Fp>\n          \u003Cp>{review.comment}\u003C\u002Fp>\n        \u003C\u002Fdiv>\n      ))}\n    \u003C\u002Fdiv>\n  )\n}\n\nfunction ReviewsSkeleton() {\n  return (\n    \u003Cdiv>\n      \u003Ch2>Reviews\u003C\u002Fh2>\n      \u003Cdiv className=\"animate-pulse\">Loading reviews…\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,2797,2798,2803,2813,2823,2834,2846,2858,2862,2878,2894,2901,2905,2909,2927,2944,2949,2958,2963,2967,2987,2991,2997,3005,3014,3029,3033,3041,3053,3073,3086,3090,3100,3112,3120,3125,3135,3152,3166,3175,3180,3190,3204,3213,3218,3223,3228,3234,3250,3267,3273,3284,3295,3300,3305,3339,3344,3351,3360,3374,3390,3404,3419,3433,3447,3457,3463,3472,3477,3482,3487,3497,3504,3513,3526,3547,3556,3561],{"__ignoreMap":28},[33,2799,2800],{"class":35,"line":36},[33,2801,2802],{"class":39},"\u002F\u002F app\u002Fproducts\u002F[productId]\u002Fpage.js — Server Component\n",[33,2804,2805,2807,2809,2811],{"class":35,"line":43},[33,2806,232],{"class":231},[33,2808,1708],{"class":235},[33,2810,239],{"class":231},[33,2812,476],{"class":242},[33,2814,2815,2817,2819,2821],{"class":35,"line":49},[33,2816,232],{"class":231},[33,2818,236],{"class":235},[33,2820,239],{"class":231},[33,2822,2085],{"class":242},[33,2824,2825,2827,2829,2831],{"class":35,"line":55},[33,2826,232],{"class":231},[33,2828,250],{"class":235},[33,2830,239],{"class":231},[33,2832,2833],{"class":242}," '@\u002Fcomponents\u002FAddToCartButton'\n",[33,2835,2836,2838,2841,2843],{"class":35,"line":61},[33,2837,232],{"class":231},[33,2839,2840],{"class":235}," ProductGallery ",[33,2842,239],{"class":231},[33,2844,2845],{"class":242}," '@\u002Fcomponents\u002FProductGallery'\n",[33,2847,2848,2850,2853,2855],{"class":35,"line":67},[33,2849,232],{"class":231},[33,2851,2852],{"class":235}," ReviewForm ",[33,2854,239],{"class":231},[33,2856,2857],{"class":242}," '@\u002Fcomponents\u002FReviewForm'\n",[33,2859,2860],{"class":35,"line":73},[33,2861,77],{"emptyLinePlaceholder":76},[33,2863,2864,2866,2868,2870,2872,2874,2876],{"class":35,"line":80},[33,2865,264],{"class":231},[33,2867,270],{"class":231},[33,2869,273],{"class":231},[33,2871,2705],{"class":276},[33,2873,280],{"class":235},[33,2875,2106],{"class":283},[33,2877,287],{"class":235},[33,2879,2880,2882,2884,2886,2888,2890,2892],{"class":35,"line":86},[33,2881,307],{"class":231},[33,2883,311],{"class":310},[33,2885,314],{"class":231},[33,2887,317],{"class":231},[33,2889,320],{"class":235},[33,2891,323],{"class":276},[33,2893,2125],{"class":235},[33,2895,2896,2898],{"class":35,"line":92},[33,2897,335],{"class":231},[33,2899,2900],{"class":235}," { title: product.name, description: product.description }\n",[33,2902,2903],{"class":35,"line":98},[33,2904,394],{"class":235},[33,2906,2907],{"class":35,"line":104},[33,2908,77],{"emptyLinePlaceholder":76},[33,2910,2911,2913,2915,2917,2919,2921,2923,2925],{"class":35,"line":110},[33,2912,264],{"class":231},[33,2914,267],{"class":231},[33,2916,270],{"class":231},[33,2918,273],{"class":231},[33,2920,277],{"class":276},[33,2922,280],{"class":235},[33,2924,2106],{"class":283},[33,2926,287],{"class":235},[33,2928,2929,2931,2933,2935,2937,2939,2941],{"class":35,"line":116},[33,2930,307],{"class":231},[33,2932,311],{"class":310},[33,2934,314],{"class":231},[33,2936,317],{"class":231},[33,2938,320],{"class":235},[33,2940,323],{"class":276},[33,2942,2943],{"class":235},"({\n",[33,2945,2946],{"class":35,"line":122},[33,2947,2948],{"class":235},"    where: { id: params.productId },\n",[33,2950,2951,2954,2956],{"class":35,"line":127},[33,2952,2953],{"class":235},"    include: { category: ",[33,2955,557],{"class":310},[33,2957,620],{"class":235},[33,2959,2960],{"class":35,"line":133},[33,2961,2962],{"class":235},"  })\n",[33,2964,2965],{"class":35,"line":139},[33,2966,77],{"emptyLinePlaceholder":76},[33,2968,2969,2972,2975,2978,2981,2984],{"class":35,"line":145},[33,2970,2971],{"class":231},"  if",[33,2973,2974],{"class":235}," (",[33,2976,2977],{"class":231},"!",[33,2979,2980],{"class":235},"product) ",[33,2982,2983],{"class":276},"notFound",[33,2985,2986],{"class":235},"()\n",[33,2988,2989],{"class":35,"line":151},[33,2990,77],{"emptyLinePlaceholder":76},[33,2992,2993,2995],{"class":35,"line":157},[33,2994,335],{"class":231},[33,2996,338],{"class":235},[33,2998,2999,3001,3003],{"class":35,"line":163},[33,3000,343],{"class":235},[33,3002,347],{"class":346},[33,3004,350],{"class":235},[33,3006,3007,3009,3012],{"class":35,"line":169},[33,3008,383],{"class":235},[33,3010,3011],{"class":39},"\u002F* Server component: zero client bundle cost for gallery logic *\u002F",[33,3013,394],{"class":235},[33,3015,3016,3018,3021,3024,3026],{"class":35,"line":174},[33,3017,355],{"class":235},[33,3019,3020],{"class":310},"ProductGallery",[33,3022,3023],{"class":276}," images",[33,3025,407],{"class":231},[33,3027,3028],{"class":235},"{product.images} \u002F>\n",[33,3030,3031],{"class":35,"line":180},[33,3032,77],{"emptyLinePlaceholder":76},[33,3034,3035,3037,3039],{"class":35,"line":186},[33,3036,355],{"class":235},[33,3038,347],{"class":346},[33,3040,350],{"class":235},[33,3042,3043,3045,3047,3049,3051],{"class":35,"line":192},[33,3044,1828],{"class":235},[33,3046,10],{"class":346},[33,3048,360],{"class":235},[33,3050,10],{"class":346},[33,3052,350],{"class":235},[33,3054,3055,3057,3059,3062,3064,3067,3069,3071],{"class":35,"line":715},[33,3056,1828],{"class":235},[33,3058,371],{"class":346},[33,3060,3061],{"class":276}," className",[33,3063,407],{"class":231},[33,3065,3066],{"class":242},"\"text-2xl\"",[33,3068,374],{"class":235},[33,3070,371],{"class":346},[33,3072,350],{"class":235},[33,3074,3075,3077,3079,3082,3084],{"class":35,"line":721},[33,3076,1828],{"class":235},[33,3078,371],{"class":346},[33,3080,3081],{"class":235},">{product.description}\u003C\u002F",[33,3083,371],{"class":346},[33,3085,350],{"class":235},[33,3087,3088],{"class":35,"line":727},[33,3089,77],{"emptyLinePlaceholder":76},[33,3091,3092,3095,3098],{"class":35,"line":733},[33,3093,3094],{"class":235},"        {",[33,3096,3097],{"class":39},"\u002F* Client component: needs interactivity (useState, onClick) *\u002F",[33,3099,394],{"class":235},[33,3101,3102,3104,3106,3108,3110],{"class":35,"line":739},[33,3103,1828],{"class":235},[33,3105,401],{"class":310},[33,3107,404],{"class":276},[33,3109,407],{"class":231},[33,3111,410],{"class":235},[33,3113,3114,3116,3118],{"class":35,"line":745},[33,3115,1841],{"class":235},[33,3117,347],{"class":346},[33,3119,350],{"class":235},[33,3121,3123],{"class":35,"line":3122},34,[33,3124,77],{"emptyLinePlaceholder":76},[33,3126,3128,3130,3133],{"class":35,"line":3127},35,[33,3129,383],{"class":235},[33,3131,3132],{"class":39},"\u002F* Reviews stream in independently — no blocking the rest of the page *\u002F",[33,3134,394],{"class":235},[33,3136,3138,3140,3142,3144,3146,3148,3150],{"class":35,"line":3137},36,[33,3139,355],{"class":235},[33,3141,1809],{"class":310},[33,3143,1812],{"class":276},[33,3145,407],{"class":231},[33,3147,1817],{"class":235},[33,3149,1820],{"class":310},[33,3151,1823],{"class":235},[33,3153,3155,3157,3160,3162,3164],{"class":35,"line":3154},37,[33,3156,1828],{"class":235},[33,3158,3159],{"class":310},"ReviewsSection",[33,3161,404],{"class":276},[33,3163,407],{"class":231},[33,3165,410],{"class":235},[33,3167,3169,3171,3173],{"class":35,"line":3168},38,[33,3170,1841],{"class":235},[33,3172,1809],{"class":310},[33,3174,350],{"class":235},[33,3176,3178],{"class":35,"line":3177},39,[33,3179,77],{"emptyLinePlaceholder":76},[33,3181,3183,3185,3188],{"class":35,"line":3182},40,[33,3184,383],{"class":235},[33,3186,3187],{"class":39},"\u002F* Review form is a client component (needs form state) *\u002F",[33,3189,394],{"class":235},[33,3191,3193,3195,3198,3200,3202],{"class":35,"line":3192},41,[33,3194,355],{"class":235},[33,3196,3197],{"class":310},"ReviewForm",[33,3199,404],{"class":276},[33,3201,407],{"class":231},[33,3203,410],{"class":235},[33,3205,3207,3209,3211],{"class":35,"line":3206},42,[33,3208,415],{"class":235},[33,3210,347],{"class":346},[33,3212,350],{"class":235},[33,3214,3216],{"class":35,"line":3215},43,[33,3217,424],{"class":235},[33,3219,3221],{"class":35,"line":3220},44,[33,3222,394],{"class":235},[33,3224,3226],{"class":35,"line":3225},45,[33,3227,77],{"emptyLinePlaceholder":76},[33,3229,3231],{"class":35,"line":3230},46,[33,3232,3233],{"class":39},"\u002F\u002F Server component — fetches reviews, no client bundle cost\n",[33,3235,3237,3239,3241,3244,3246,3248],{"class":35,"line":3236},47,[33,3238,1295],{"class":231},[33,3240,273],{"class":231},[33,3242,3243],{"class":276}," ReviewsSection",[33,3245,280],{"class":235},[33,3247,284],{"class":283},[33,3249,287],{"class":235},[33,3251,3253,3255,3257,3259,3261,3263,3265],{"class":35,"line":3252},48,[33,3254,307],{"class":231},[33,3256,1609],{"class":310},[33,3258,314],{"class":231},[33,3260,317],{"class":231},[33,3262,1616],{"class":235},[33,3264,1619],{"class":276},[33,3266,2943],{"class":235},[33,3268,3270],{"class":35,"line":3269},49,[33,3271,3272],{"class":235},"    where: { productId },\n",[33,3274,3276,3279,3282],{"class":35,"line":3275},50,[33,3277,3278],{"class":235},"    orderBy: { createdAt: ",[33,3280,3281],{"class":242},"'desc'",[33,3283,620],{"class":235},[33,3285,3287,3290,3293],{"class":35,"line":3286},51,[33,3288,3289],{"class":235},"    take: ",[33,3291,3292],{"class":310},"10",[33,3294,586],{"class":235},[33,3296,3298],{"class":35,"line":3297},52,[33,3299,2962],{"class":235},[33,3301,3303],{"class":35,"line":3302},53,[33,3304,77],{"emptyLinePlaceholder":76},[33,3306,3308,3310,3313,3316,3319,3322,3325,3328,3330,3332,3335,3337],{"class":35,"line":3307},54,[33,3309,2971],{"class":231},[33,3311,3312],{"class":235}," (reviews.",[33,3314,3315],{"class":310},"length",[33,3317,3318],{"class":231}," ===",[33,3320,3321],{"class":310}," 0",[33,3323,3324],{"class":235},") ",[33,3326,3327],{"class":231},"return",[33,3329,920],{"class":235},[33,3331,371],{"class":346},[33,3333,3334],{"class":235},">No reviews yet.\u003C\u002F",[33,3336,371],{"class":346},[33,3338,350],{"class":235},[33,3340,3342],{"class":35,"line":3341},55,[33,3343,77],{"emptyLinePlaceholder":76},[33,3345,3347,3349],{"class":35,"line":3346},56,[33,3348,335],{"class":231},[33,3350,338],{"class":235},[33,3352,3354,3356,3358],{"class":35,"line":3353},57,[33,3355,343],{"class":235},[33,3357,347],{"class":346},[33,3359,350],{"class":235},[33,3361,3363,3365,3367,3370,3372],{"class":35,"line":3362},58,[33,3364,355],{"class":235},[33,3366,14],{"class":346},[33,3368,3369],{"class":235},">Reviews\u003C\u002F",[33,3371,14],{"class":346},[33,3373,350],{"class":235},[33,3375,3377,3380,3382,3384,3386,3388],{"class":35,"line":3376},59,[33,3378,3379],{"class":235},"      {reviews.",[33,3381,1632],{"class":276},[33,3383,524],{"class":235},[33,3385,1656],{"class":283},[33,3387,1640],{"class":231},[33,3389,338],{"class":235},[33,3391,3393,3395,3397,3399,3401],{"class":35,"line":3392},60,[33,3394,1828],{"class":235},[33,3396,347],{"class":346},[33,3398,1648],{"class":276},[33,3400,407],{"class":231},[33,3402,3403],{"class":235},"{review.id}>\n",[33,3405,3407,3410,3412,3415,3417],{"class":35,"line":3406},61,[33,3408,3409],{"class":235},"          \u003C",[33,3411,371],{"class":346},[33,3413,3414],{"class":235},">{review.author}\u003C\u002F",[33,3416,371],{"class":346},[33,3418,350],{"class":235},[33,3420,3422,3424,3426,3429,3431],{"class":35,"line":3421},62,[33,3423,3409],{"class":235},[33,3425,371],{"class":346},[33,3427,3428],{"class":235},">{review.rating} stars\u003C\u002F",[33,3430,371],{"class":346},[33,3432,350],{"class":235},[33,3434,3436,3438,3440,3443,3445],{"class":35,"line":3435},63,[33,3437,3409],{"class":235},[33,3439,371],{"class":346},[33,3441,3442],{"class":235},">{review.comment}\u003C\u002F",[33,3444,371],{"class":346},[33,3446,350],{"class":235},[33,3448,3450,3453,3455],{"class":35,"line":3449},64,[33,3451,3452],{"class":235},"        \u003C\u002F",[33,3454,347],{"class":346},[33,3456,350],{"class":235},[33,3458,3460],{"class":35,"line":3459},65,[33,3461,3462],{"class":235},"      ))}\n",[33,3464,3466,3468,3470],{"class":35,"line":3465},66,[33,3467,415],{"class":235},[33,3469,347],{"class":346},[33,3471,350],{"class":235},[33,3473,3475],{"class":35,"line":3474},67,[33,3476,424],{"class":235},[33,3478,3480],{"class":35,"line":3479},68,[33,3481,394],{"class":235},[33,3483,3485],{"class":35,"line":3484},69,[33,3486,77],{"emptyLinePlaceholder":76},[33,3488,3490,3492,3495],{"class":35,"line":3489},70,[33,3491,822],{"class":231},[33,3493,3494],{"class":276}," ReviewsSkeleton",[33,3496,547],{"class":235},[33,3498,3500,3502],{"class":35,"line":3499},71,[33,3501,335],{"class":231},[33,3503,338],{"class":235},[33,3505,3507,3509,3511],{"class":35,"line":3506},72,[33,3508,343],{"class":235},[33,3510,347],{"class":346},[33,3512,350],{"class":235},[33,3514,3516,3518,3520,3522,3524],{"class":35,"line":3515},73,[33,3517,355],{"class":235},[33,3519,14],{"class":346},[33,3521,3369],{"class":235},[33,3523,14],{"class":346},[33,3525,350],{"class":235},[33,3527,3529,3531,3533,3535,3537,3540,3543,3545],{"class":35,"line":3528},74,[33,3530,355],{"class":235},[33,3532,347],{"class":346},[33,3534,3061],{"class":276},[33,3536,407],{"class":231},[33,3538,3539],{"class":242},"\"animate-pulse\"",[33,3541,3542],{"class":235},">Loading reviews…\u003C\u002F",[33,3544,347],{"class":346},[33,3546,350],{"class":235},[33,3548,3550,3552,3554],{"class":35,"line":3549},75,[33,3551,415],{"class":235},[33,3553,347],{"class":346},[33,3555,350],{"class":235},[33,3557,3559],{"class":35,"line":3558},76,[33,3560,424],{"class":235},[33,3562,3564],{"class":35,"line":3563},77,[33,3565,394],{"class":235},[14,3567,3569],{"id":3568},"anti-patterns","Anti-Patterns",[19,3571,3573],{"filename":3572,"language":22},"anti_patterns.js",[24,3574,3576],{"className":26,"code":3575,"language":22,"meta":28,"style":28},"\u002F\u002F ANTI-PATTERN: marking an entire page 'use client' when only one\n\u002F\u002F small component needs interactivity. The ENTIRE page + all its\n\u002F\u002F imports ship to the client bundle. Extract the interactive bit\n\u002F\u002F into its own 'use client' leaf and keep the rest server-only.\n\n\u002F\u002F ANTI-PATTERN: a client component importing a server component\n'use client'\nimport ServerDataComponent from '.\u002FServerDataComponent'  \u002F\u002F FAILS at build\n\u002F\u002F Fix: pass it as children from a server component parent (slot pattern).\n\n\u002F\u002F ANTI-PATTERN: passing non-serializable props from server to client\n\u002F\u002F Server component:\n\u003CClientComponent onClick={() => console.log('hi')} \u002F>\n\u002F\u002F Functions can't cross the serialization boundary (except Server Actions).\n\u002F\u002F Fix: move the interactivity into the client component itself.\n\n\u002F\u002F ANTI-PATTERN: Date.now() \u002F Math.random() \u002F window access in a\n\u002F\u002F component that renders on both server and client → hydration mismatch.\n\u002F\u002F Fix: defer environment-dependent values to useEffect (after mount).\n\n\u002F\u002F ANTI-PATTERN: using useEffect for data fetching in a server component\nasync function ProductPage({ productId }) {\n  \u002F\u002F Server components are ALREADY async — just await directly.\n  \u002F\u002F useEffect doesn't exist in server components at all.\n  const product = await fetchProduct(productId)  \u002F\u002F correct\n  return \u003CProductView product={product} \u002F>\n}\n\n\u002F\u002F ANTI-PATTERN: 'use client' at the top of a shared utility file\n\u002F\u002F that's imported by both server and client components. Everything\n\u002F\u002F in that file + its deps become client bundle. Split server-only\n\u002F\u002F logic and client-facing logic into separate modules.\n",[30,3577,3578,3583,3588,3593,3598,3602,3607,3611,3626,3631,3635,3640,3645,3675,3680,3685,3689,3694,3699,3704,3708,3713,3727,3732,3737,3756,3772,3776,3780,3785,3790,3795],{"__ignoreMap":28},[33,3579,3580],{"class":35,"line":36},[33,3581,3582],{"class":39},"\u002F\u002F ANTI-PATTERN: marking an entire page 'use client' when only one\n",[33,3584,3585],{"class":35,"line":43},[33,3586,3587],{"class":39},"\u002F\u002F small component needs interactivity. The ENTIRE page + all its\n",[33,3589,3590],{"class":35,"line":49},[33,3591,3592],{"class":39},"\u002F\u002F imports ship to the client bundle. Extract the interactive bit\n",[33,3594,3595],{"class":35,"line":55},[33,3596,3597],{"class":39},"\u002F\u002F into its own 'use client' leaf and keep the rest server-only.\n",[33,3599,3600],{"class":35,"line":61},[33,3601,77],{"emptyLinePlaceholder":76},[33,3603,3604],{"class":35,"line":67},[33,3605,3606],{"class":39},"\u002F\u002F ANTI-PATTERN: a client component importing a server component\n",[33,3608,3609],{"class":35,"line":73},[33,3610,460],{"class":242},[33,3612,3613,3615,3618,3620,3623],{"class":35,"line":80},[33,3614,232],{"class":231},[33,3616,3617],{"class":235}," ServerDataComponent ",[33,3619,239],{"class":231},[33,3621,3622],{"class":242}," '.\u002FServerDataComponent'",[33,3624,3625],{"class":39},"  \u002F\u002F FAILS at build\n",[33,3627,3628],{"class":35,"line":86},[33,3629,3630],{"class":39},"\u002F\u002F Fix: pass it as children from a server component parent (slot pattern).\n",[33,3632,3633],{"class":35,"line":92},[33,3634,77],{"emptyLinePlaceholder":76},[33,3636,3637],{"class":35,"line":98},[33,3638,3639],{"class":39},"\u002F\u002F ANTI-PATTERN: passing non-serializable props from server to client\n",[33,3641,3642],{"class":35,"line":104},[33,3643,3644],{"class":39},"\u002F\u002F Server component:\n",[33,3646,3647,3650,3653,3655,3657,3659,3661,3664,3667,3669,3672],{"class":35,"line":110},[33,3648,3649],{"class":235},"\u003C",[33,3651,3652],{"class":310},"ClientComponent",[33,3654,660],{"class":276},[33,3656,407],{"class":231},[33,3658,885],{"class":235},[33,3660,888],{"class":231},[33,3662,3663],{"class":235}," console.",[33,3665,3666],{"class":276},"log",[33,3668,524],{"class":235},[33,3670,3671],{"class":242},"'hi'",[33,3673,3674],{"class":235},")} \u002F>\n",[33,3676,3677],{"class":35,"line":116},[33,3678,3679],{"class":39},"\u002F\u002F Functions can't cross the serialization boundary (except Server Actions).\n",[33,3681,3682],{"class":35,"line":122},[33,3683,3684],{"class":39},"\u002F\u002F Fix: move the interactivity into the client component itself.\n",[33,3686,3687],{"class":35,"line":127},[33,3688,77],{"emptyLinePlaceholder":76},[33,3690,3691],{"class":35,"line":133},[33,3692,3693],{"class":39},"\u002F\u002F ANTI-PATTERN: Date.now() \u002F Math.random() \u002F window access in a\n",[33,3695,3696],{"class":35,"line":139},[33,3697,3698],{"class":39},"\u002F\u002F component that renders on both server and client → hydration mismatch.\n",[33,3700,3701],{"class":35,"line":145},[33,3702,3703],{"class":39},"\u002F\u002F Fix: defer environment-dependent values to useEffect (after mount).\n",[33,3705,3706],{"class":35,"line":151},[33,3707,77],{"emptyLinePlaceholder":76},[33,3709,3710],{"class":35,"line":157},[33,3711,3712],{"class":39},"\u002F\u002F ANTI-PATTERN: using useEffect for data fetching in a server component\n",[33,3714,3715,3717,3719,3721,3723,3725],{"class":35,"line":163},[33,3716,1295],{"class":231},[33,3718,273],{"class":231},[33,3720,277],{"class":276},[33,3722,280],{"class":235},[33,3724,284],{"class":283},[33,3726,287],{"class":235},[33,3728,3729],{"class":35,"line":169},[33,3730,3731],{"class":39},"  \u002F\u002F Server components are ALREADY async — just await directly.\n",[33,3733,3734],{"class":35,"line":174},[33,3735,3736],{"class":39},"  \u002F\u002F useEffect doesn't exist in server components at all.\n",[33,3738,3739,3741,3743,3745,3747,3750,3753],{"class":35,"line":180},[33,3740,307],{"class":231},[33,3742,311],{"class":310},[33,3744,314],{"class":231},[33,3746,317],{"class":231},[33,3748,3749],{"class":276}," fetchProduct",[33,3751,3752],{"class":235},"(productId)  ",[33,3754,3755],{"class":39},"\u002F\u002F correct\n",[33,3757,3758,3760,3762,3765,3767,3769],{"class":35,"line":186},[33,3759,335],{"class":231},[33,3761,920],{"class":235},[33,3763,3764],{"class":310},"ProductView",[33,3766,311],{"class":276},[33,3768,407],{"class":231},[33,3770,3771],{"class":235},"{product} \u002F>\n",[33,3773,3774],{"class":35,"line":192},[33,3775,394],{"class":235},[33,3777,3778],{"class":35,"line":715},[33,3779,77],{"emptyLinePlaceholder":76},[33,3781,3782],{"class":35,"line":721},[33,3783,3784],{"class":39},"\u002F\u002F ANTI-PATTERN: 'use client' at the top of a shared utility file\n",[33,3786,3787],{"class":35,"line":727},[33,3788,3789],{"class":39},"\u002F\u002F that's imported by both server and client components. Everything\n",[33,3791,3792],{"class":35,"line":733},[33,3793,3794],{"class":39},"\u002F\u002F in that file + its deps become client bundle. Split server-only\n",[33,3796,3797],{"class":35,"line":739},[33,3798,3799],{"class":39},"\u002F\u002F logic and client-facing logic into separate modules.\n",[14,3801,3803],{"id":3802},"tips-tricks","💡 Tips & Tricks",[19,3805,3807],{"filename":3806,"language":22},"tips.js",[24,3808,3810],{"className":26,"code":3809,"language":22,"meta":28,"style":28},"\u002F\u002F [Idiom] Default to server components for anything that doesn't need\n\u002F\u002F state, effects, or event handlers — data display, formatting, layout.\n\u002F\u002F Reach for 'use client' only at specific leaf components that genuinely\n\u002F\u002F need interactivity, keeping the client bundle as small as the app's\n\u002F\u002F actual interactive surface.\n\n\u002F\u002F [Idiom] Use the children-as-props \"slot\" pattern to compose a server\n\u002F\u002F component inside a client component's UI without the client component\n\u002F\u002F ever importing it — the standard workaround for the one-directional\n\u002F\u002F import restriction.\n\n\u002F\u002F [Performance] Wrap slower, independently-loading sections of a\n\u002F\u002F server-rendered page in \u003CSuspense> so faster siblings can stream to\n\u002F\u002F the browser without waiting on the slowest piece of data.\n\n\u002F\u002F [Debug] A \"you're importing a Server Component into a Client Component\"\n\u002F\u002F build error almost always means a shared file needs its exports split —\n\u002F\u002F keep server-only logic in one module and client-facing pieces in another.\n\n\u002F\u002F [Portability] Treat any value that legitimately differs between server\n\u002F\u002F and browser execution (current time, window\u002Fnavigator access, random\n\u002F\u002F values, locale-dependent formatting) as a hydration-mismatch risk by\n\u002F\u002F default — defer rendering it until after mount.\n\n\u002F\u002F [Idiom] In Next.js App Router, use the metadata API (export const\n\u002F\u002F metadata or generateMetadata) in server components for SEO — setting\n\u002F\u002F document.title in a client useEffect runs after hydration and is\n\u002F\u002F invisible to crawlers that don't execute JS.\n\n\u002F\u002F [Performance] Server components can import heavy libraries (markdown\n\u002F\u002F parsers, syntax highlighters, formatting utils) with ZERO client bundle\n\u002F\u002F cost — only the rendered output is sent. Move static rendering logic\n\u002F\u002F to the server to shrink the bundle.\n",[30,3811,3812,3817,3822,3827,3832,3837,3841,3846,3851,3856,3861,3865,3870,3875,3880,3884,3889,3894,3899,3903,3908,3913,3918,3923,3927,3932,3937,3942,3947,3951,3956,3961,3966],{"__ignoreMap":28},[33,3813,3814],{"class":35,"line":36},[33,3815,3816],{"class":39},"\u002F\u002F [Idiom] Default to server components for anything that doesn't need\n",[33,3818,3819],{"class":35,"line":43},[33,3820,3821],{"class":39},"\u002F\u002F state, effects, or event handlers — data display, formatting, layout.\n",[33,3823,3824],{"class":35,"line":49},[33,3825,3826],{"class":39},"\u002F\u002F Reach for 'use client' only at specific leaf components that genuinely\n",[33,3828,3829],{"class":35,"line":55},[33,3830,3831],{"class":39},"\u002F\u002F need interactivity, keeping the client bundle as small as the app's\n",[33,3833,3834],{"class":35,"line":61},[33,3835,3836],{"class":39},"\u002F\u002F actual interactive surface.\n",[33,3838,3839],{"class":35,"line":67},[33,3840,77],{"emptyLinePlaceholder":76},[33,3842,3843],{"class":35,"line":73},[33,3844,3845],{"class":39},"\u002F\u002F [Idiom] Use the children-as-props \"slot\" pattern to compose a server\n",[33,3847,3848],{"class":35,"line":80},[33,3849,3850],{"class":39},"\u002F\u002F component inside a client component's UI without the client component\n",[33,3852,3853],{"class":35,"line":86},[33,3854,3855],{"class":39},"\u002F\u002F ever importing it — the standard workaround for the one-directional\n",[33,3857,3858],{"class":35,"line":92},[33,3859,3860],{"class":39},"\u002F\u002F import restriction.\n",[33,3862,3863],{"class":35,"line":98},[33,3864,77],{"emptyLinePlaceholder":76},[33,3866,3867],{"class":35,"line":104},[33,3868,3869],{"class":39},"\u002F\u002F [Performance] Wrap slower, independently-loading sections of a\n",[33,3871,3872],{"class":35,"line":110},[33,3873,3874],{"class":39},"\u002F\u002F server-rendered page in \u003CSuspense> so faster siblings can stream to\n",[33,3876,3877],{"class":35,"line":116},[33,3878,3879],{"class":39},"\u002F\u002F the browser without waiting on the slowest piece of data.\n",[33,3881,3882],{"class":35,"line":122},[33,3883,77],{"emptyLinePlaceholder":76},[33,3885,3886],{"class":35,"line":127},[33,3887,3888],{"class":39},"\u002F\u002F [Debug] A \"you're importing a Server Component into a Client Component\"\n",[33,3890,3891],{"class":35,"line":133},[33,3892,3893],{"class":39},"\u002F\u002F build error almost always means a shared file needs its exports split —\n",[33,3895,3896],{"class":35,"line":139},[33,3897,3898],{"class":39},"\u002F\u002F keep server-only logic in one module and client-facing pieces in another.\n",[33,3900,3901],{"class":35,"line":145},[33,3902,77],{"emptyLinePlaceholder":76},[33,3904,3905],{"class":35,"line":151},[33,3906,3907],{"class":39},"\u002F\u002F [Portability] Treat any value that legitimately differs between server\n",[33,3909,3910],{"class":35,"line":157},[33,3911,3912],{"class":39},"\u002F\u002F and browser execution (current time, window\u002Fnavigator access, random\n",[33,3914,3915],{"class":35,"line":163},[33,3916,3917],{"class":39},"\u002F\u002F values, locale-dependent formatting) as a hydration-mismatch risk by\n",[33,3919,3920],{"class":35,"line":169},[33,3921,3922],{"class":39},"\u002F\u002F default — defer rendering it until after mount.\n",[33,3924,3925],{"class":35,"line":174},[33,3926,77],{"emptyLinePlaceholder":76},[33,3928,3929],{"class":35,"line":180},[33,3930,3931],{"class":39},"\u002F\u002F [Idiom] In Next.js App Router, use the metadata API (export const\n",[33,3933,3934],{"class":35,"line":186},[33,3935,3936],{"class":39},"\u002F\u002F metadata or generateMetadata) in server components for SEO — setting\n",[33,3938,3939],{"class":35,"line":192},[33,3940,3941],{"class":39},"\u002F\u002F document.title in a client useEffect runs after hydration and is\n",[33,3943,3944],{"class":35,"line":715},[33,3945,3946],{"class":39},"\u002F\u002F invisible to crawlers that don't execute JS.\n",[33,3948,3949],{"class":35,"line":721},[33,3950,77],{"emptyLinePlaceholder":76},[33,3952,3953],{"class":35,"line":727},[33,3954,3955],{"class":39},"\u002F\u002F [Performance] Server components can import heavy libraries (markdown\n",[33,3957,3958],{"class":35,"line":733},[33,3959,3960],{"class":39},"\u002F\u002F parsers, syntax highlighters, formatting utils) with ZERO client bundle\n",[33,3962,3963],{"class":35,"line":739},[33,3964,3965],{"class":39},"\u002F\u002F cost — only the rendered output is sent. Move static rendering logic\n",[33,3967,3968],{"class":35,"line":745},[33,3969,3970],{"class":39},"\u002F\u002F to the server to shrink the bundle.\n",[14,3972,3974],{"id":3973},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[19,3976,3978],{"filename":3977,"language":22},"edge_cases.js",[24,3979,3981],{"className":26,"code":3980,"language":22,"meta":28,"style":28},"\u002F\u002F [Gotcha] 'use client' marks a BOUNDARY, not just a single file —\n\u002F\u002F every component imported into a 'use client' file is treated as part\n\u002F\u002F of the client bundle too (unless passed in as children\u002Fprops from\n\u002F\u002F outside). An innocuous-looking import deep in a client component's\n\u002F\u002F dependency tree can silently balloon bundle size.\n\n\u002F\u002F [Gotcha] Server components CANNOT use useState, useEffect, useContext,\n\u002F\u002F or any hook that assumes a browser runtime — attempting to do so is a\n\u002F\u002F build-time\u002Fruntime error. Move that piece into a 'use client' child.\n\n\u002F\u002F [Gotcha] Props passed from a server component to a client component\n\u002F\u002F MUST be serializable — functions, class instances, and other\n\u002F\u002F non-serializable values can't cross the boundary (with the specific\n\u002F\u002F exception of Server Actions). The boundary is a real serialization step.\n\n\u002F\u002F [Gotcha] A hydration mismatch doesn't just log a warning and move on —\n\u002F\u002F React DISCARDS and re-renders the mismatched DOM subtree client-side.\n\u002F\u002F The SSR performance benefit for that section is lost, and content can\n\u002F\u002F flicker as the corrected client-rendered version replaces the server HTML.\n\n\u002F\u002F [Gotcha] RSC and traditional SSR are easy to conflate. Pre-RSC \"isomorphic\"\n\u002F\u002F SSR ships EVERY component's code to the client regardless of whether the\n\u002F\u002F component's UI actually needs interactivity. RSC breaks this assumption —\n\u002F\u002F older SSR-only mental models don't automatically transfer.\n\n\u002F\u002F [Gotcha] In Next.js App Router, 'use client' doesn't mean \"this only\n\u002F\u002F renders on the client.\" Client components still SSR for the initial page\n\u002F\u002F load — they just ALSO ship to the browser for hydration and interactivity.\n\u002F\u002F The directive controls bundle inclusion, not render location.\n\n\u002F\u002F [Gotcha] Server components can't use Context (useContext) — context is\n\u002F\u002F a runtime concept that requires React's client-side re-rendering model.\n\u002F\u002F To share values across server and client components, pass props or use\n\u002F\u002F a framework-specific mechanism (Next.js headers(), cookies(), etc.).\n\n\u002F\u002F [Gotcha] Dynamic route params in Next.js App Router are promises in\n\u002F\u002F Next.js 15+ — you must `await params` before accessing properties.\n\u002F\u002F In earlier versions, params was a plain object. Check your version.\n",[30,3982,3983,3988,3993,3998,4003,4008,4012,4017,4022,4027,4031,4036,4041,4046,4051,4055,4060,4065,4070,4075,4079,4084,4089,4094,4099,4103,4108,4113,4118,4123,4127,4132,4137,4142,4147,4151,4156,4161],{"__ignoreMap":28},[33,3984,3985],{"class":35,"line":36},[33,3986,3987],{"class":39},"\u002F\u002F [Gotcha] 'use client' marks a BOUNDARY, not just a single file —\n",[33,3989,3990],{"class":35,"line":43},[33,3991,3992],{"class":39},"\u002F\u002F every component imported into a 'use client' file is treated as part\n",[33,3994,3995],{"class":35,"line":49},[33,3996,3997],{"class":39},"\u002F\u002F of the client bundle too (unless passed in as children\u002Fprops from\n",[33,3999,4000],{"class":35,"line":55},[33,4001,4002],{"class":39},"\u002F\u002F outside). An innocuous-looking import deep in a client component's\n",[33,4004,4005],{"class":35,"line":61},[33,4006,4007],{"class":39},"\u002F\u002F dependency tree can silently balloon bundle size.\n",[33,4009,4010],{"class":35,"line":67},[33,4011,77],{"emptyLinePlaceholder":76},[33,4013,4014],{"class":35,"line":73},[33,4015,4016],{"class":39},"\u002F\u002F [Gotcha] Server components CANNOT use useState, useEffect, useContext,\n",[33,4018,4019],{"class":35,"line":80},[33,4020,4021],{"class":39},"\u002F\u002F or any hook that assumes a browser runtime — attempting to do so is a\n",[33,4023,4024],{"class":35,"line":86},[33,4025,4026],{"class":39},"\u002F\u002F build-time\u002Fruntime error. Move that piece into a 'use client' child.\n",[33,4028,4029],{"class":35,"line":92},[33,4030,77],{"emptyLinePlaceholder":76},[33,4032,4033],{"class":35,"line":98},[33,4034,4035],{"class":39},"\u002F\u002F [Gotcha] Props passed from a server component to a client component\n",[33,4037,4038],{"class":35,"line":104},[33,4039,4040],{"class":39},"\u002F\u002F MUST be serializable — functions, class instances, and other\n",[33,4042,4043],{"class":35,"line":110},[33,4044,4045],{"class":39},"\u002F\u002F non-serializable values can't cross the boundary (with the specific\n",[33,4047,4048],{"class":35,"line":116},[33,4049,4050],{"class":39},"\u002F\u002F exception of Server Actions). The boundary is a real serialization step.\n",[33,4052,4053],{"class":35,"line":122},[33,4054,77],{"emptyLinePlaceholder":76},[33,4056,4057],{"class":35,"line":127},[33,4058,4059],{"class":39},"\u002F\u002F [Gotcha] A hydration mismatch doesn't just log a warning and move on —\n",[33,4061,4062],{"class":35,"line":133},[33,4063,4064],{"class":39},"\u002F\u002F React DISCARDS and re-renders the mismatched DOM subtree client-side.\n",[33,4066,4067],{"class":35,"line":139},[33,4068,4069],{"class":39},"\u002F\u002F The SSR performance benefit for that section is lost, and content can\n",[33,4071,4072],{"class":35,"line":145},[33,4073,4074],{"class":39},"\u002F\u002F flicker as the corrected client-rendered version replaces the server HTML.\n",[33,4076,4077],{"class":35,"line":151},[33,4078,77],{"emptyLinePlaceholder":76},[33,4080,4081],{"class":35,"line":157},[33,4082,4083],{"class":39},"\u002F\u002F [Gotcha] RSC and traditional SSR are easy to conflate. Pre-RSC \"isomorphic\"\n",[33,4085,4086],{"class":35,"line":163},[33,4087,4088],{"class":39},"\u002F\u002F SSR ships EVERY component's code to the client regardless of whether the\n",[33,4090,4091],{"class":35,"line":169},[33,4092,4093],{"class":39},"\u002F\u002F component's UI actually needs interactivity. RSC breaks this assumption —\n",[33,4095,4096],{"class":35,"line":174},[33,4097,4098],{"class":39},"\u002F\u002F older SSR-only mental models don't automatically transfer.\n",[33,4100,4101],{"class":35,"line":180},[33,4102,77],{"emptyLinePlaceholder":76},[33,4104,4105],{"class":35,"line":186},[33,4106,4107],{"class":39},"\u002F\u002F [Gotcha] In Next.js App Router, 'use client' doesn't mean \"this only\n",[33,4109,4110],{"class":35,"line":192},[33,4111,4112],{"class":39},"\u002F\u002F renders on the client.\" Client components still SSR for the initial page\n",[33,4114,4115],{"class":35,"line":715},[33,4116,4117],{"class":39},"\u002F\u002F load — they just ALSO ship to the browser for hydration and interactivity.\n",[33,4119,4120],{"class":35,"line":721},[33,4121,4122],{"class":39},"\u002F\u002F The directive controls bundle inclusion, not render location.\n",[33,4124,4125],{"class":35,"line":727},[33,4126,77],{"emptyLinePlaceholder":76},[33,4128,4129],{"class":35,"line":733},[33,4130,4131],{"class":39},"\u002F\u002F [Gotcha] Server components can't use Context (useContext) — context is\n",[33,4133,4134],{"class":35,"line":739},[33,4135,4136],{"class":39},"\u002F\u002F a runtime concept that requires React's client-side re-rendering model.\n",[33,4138,4139],{"class":35,"line":745},[33,4140,4141],{"class":39},"\u002F\u002F To share values across server and client components, pass props or use\n",[33,4143,4144],{"class":35,"line":3122},[33,4145,4146],{"class":39},"\u002F\u002F a framework-specific mechanism (Next.js headers(), cookies(), etc.).\n",[33,4148,4149],{"class":35,"line":3127},[33,4150,77],{"emptyLinePlaceholder":76},[33,4152,4153],{"class":35,"line":3137},[33,4154,4155],{"class":39},"\u002F\u002F [Gotcha] Dynamic route params in Next.js App Router are promises in\n",[33,4157,4158],{"class":35,"line":3154},[33,4159,4160],{"class":39},"\u002F\u002F Next.js 15+ — you must `await params` before accessing properties.\n",[33,4162,4163],{"class":35,"line":3168},[33,4164,4165],{"class":39},"\u002F\u002F In earlier versions, params was a plain object. Check your version.\n",[14,4167,4169],{"id":4168},"spot-the-bug","🧠 Spot the Bug",[371,4171,4172],{},"A team migrates a product detail page to use Server Components, expecting the bundle size to shrink significantly since most of the page is static content. The bundle barely changes.",[19,4174,4176],{"filename":4175,"language":22},"spot_the_bug.js",[24,4177,4179],{"className":26,"code":4178,"language":22,"meta":28,"style":28},"'use client'\n\nimport { formatCurrency } from '.\u002Futils\u002FformatCurrency'\nimport { ProductSpecsTable } from '.\u002FProductSpecsTable'\nimport { useState } from 'react'\n\nexport default function ProductDetailPage({ product }) {\n  const [quantity, setQuantity] = useState(1)\n\n  return (\n    \u003Cdiv>\n      \u003Ch1>{product.name}\u003C\u002Fh1>\n      \u003Cp>{formatCurrency(product.price)}\u003C\u002Fp>\n      \u003CProductSpecsTable specs={product.specs} \u002F>\n      \u003Cinput\n        type=\"number\"\n        value={quantity}\n        onChange={e => setQuantity(Number(e.target.value))}\n      \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,4180,4181,4185,4189,4201,4213,4223,4227,4245,4272,4276,4282,4290,4302,4320,4335,4342,4352,4362,4388,4393,4401,4405],{"__ignoreMap":28},[33,4182,4183],{"class":35,"line":36},[33,4184,460],{"class":242},[33,4186,4187],{"class":35,"line":43},[33,4188,77],{"emptyLinePlaceholder":76},[33,4190,4191,4193,4196,4198],{"class":35,"line":49},[33,4192,232],{"class":231},[33,4194,4195],{"class":235}," { formatCurrency } ",[33,4197,239],{"class":231},[33,4199,4200],{"class":242}," '.\u002Futils\u002FformatCurrency'\n",[33,4202,4203,4205,4208,4210],{"class":35,"line":55},[33,4204,232],{"class":231},[33,4206,4207],{"class":235}," { ProductSpecsTable } ",[33,4209,239],{"class":231},[33,4211,4212],{"class":242}," '.\u002FProductSpecsTable'\n",[33,4214,4215,4217,4219,4221],{"class":35,"line":61},[33,4216,232],{"class":231},[33,4218,471],{"class":235},[33,4220,239],{"class":231},[33,4222,476],{"class":242},[33,4224,4225],{"class":35,"line":67},[33,4226,77],{"emptyLinePlaceholder":76},[33,4228,4229,4231,4233,4235,4238,4240,4243],{"class":35,"line":73},[33,4230,264],{"class":231},[33,4232,267],{"class":231},[33,4234,273],{"class":231},[33,4236,4237],{"class":276}," ProductDetailPage",[33,4239,280],{"class":235},[33,4241,4242],{"class":283},"product",[33,4244,287],{"class":235},[33,4246,4247,4249,4251,4254,4256,4259,4261,4263,4265,4267,4270],{"class":35,"line":80},[33,4248,307],{"class":231},[33,4250,504],{"class":235},[33,4252,4253],{"class":310},"quantity",[33,4255,510],{"class":235},[33,4257,4258],{"class":310},"setQuantity",[33,4260,516],{"class":235},[33,4262,407],{"class":231},[33,4264,521],{"class":276},[33,4266,524],{"class":235},[33,4268,4269],{"class":310},"1",[33,4271,530],{"class":235},[33,4273,4274],{"class":35,"line":86},[33,4275,77],{"emptyLinePlaceholder":76},[33,4277,4278,4280],{"class":35,"line":92},[33,4279,335],{"class":231},[33,4281,338],{"class":235},[33,4283,4284,4286,4288],{"class":35,"line":98},[33,4285,343],{"class":235},[33,4287,347],{"class":346},[33,4289,350],{"class":235},[33,4291,4292,4294,4296,4298,4300],{"class":35,"line":104},[33,4293,355],{"class":235},[33,4295,10],{"class":346},[33,4297,360],{"class":235},[33,4299,10],{"class":346},[33,4301,350],{"class":235},[33,4303,4304,4306,4308,4310,4313,4316,4318],{"class":35,"line":110},[33,4305,355],{"class":235},[33,4307,371],{"class":346},[33,4309,2322],{"class":235},[33,4311,4312],{"class":276},"formatCurrency",[33,4314,4315],{"class":235},"(product.price)}\u003C\u002F",[33,4317,371],{"class":346},[33,4319,350],{"class":235},[33,4321,4322,4324,4327,4330,4332],{"class":35,"line":116},[33,4323,355],{"class":235},[33,4325,4326],{"class":310},"ProductSpecsTable",[33,4328,4329],{"class":276}," specs",[33,4331,407],{"class":231},[33,4333,4334],{"class":235},"{product.specs} \u002F>\n",[33,4336,4337,4339],{"class":35,"line":122},[33,4338,355],{"class":235},[33,4340,4341],{"class":346},"input\n",[33,4343,4344,4347,4349],{"class":35,"line":127},[33,4345,4346],{"class":276},"        type",[33,4348,407],{"class":231},[33,4350,4351],{"class":242},"\"number\"\n",[33,4353,4354,4357,4359],{"class":35,"line":133},[33,4355,4356],{"class":276},"        value",[33,4358,407],{"class":231},[33,4360,4361],{"class":235},"{quantity}\n",[33,4363,4364,4367,4369,4372,4375,4377,4380,4382,4385],{"class":35,"line":139},[33,4365,4366],{"class":276},"        onChange",[33,4368,407],{"class":231},[33,4370,4371],{"class":235},"{",[33,4373,4374],{"class":283},"e",[33,4376,1640],{"class":231},[33,4378,4379],{"class":276}," setQuantity",[33,4381,524],{"class":235},[33,4383,4384],{"class":276},"Number",[33,4386,4387],{"class":235},"(e.target.value))}\n",[33,4389,4390],{"class":35,"line":145},[33,4391,4392],{"class":235},"      \u002F>\n",[33,4394,4395,4397,4399],{"class":35,"line":151},[33,4396,415],{"class":235},[33,4398,347],{"class":346},[33,4400,350],{"class":235},[33,4402,4403],{"class":35,"line":157},[33,4404,424],{"class":235},[33,4406,4407],{"class":35,"line":163},[33,4408,394],{"class":235},[4410,4411,4412,4416,4438,4459,4730,4736],"details",{},[4413,4414,4415],"summary",{},"Answer",[371,4417,4418,4419,4422,4423,4426,4427,4429,4430,510,4432,4434,4435,597],{},"The entire page is marked ",[30,4420,4421],{},"'use client'"," at the top, even though the only genuinely interactive piece is the quantity ",[30,4424,4425],{},"\u003Cinput>",". Because ",[30,4428,4421],{}," marks the whole file (and everything it imports that isn't passed in from outside) as client-bundle code, ",[30,4431,4326],{},[30,4433,4312],{},", and the static heading\u002Fprice markup all ship to the browser and hydrate on the client too — none of the bundle-size benefit RSC offers actually materializes, since only one small input genuinely needs ",[30,4436,4437],{},"useState",[371,4439,4440,4444,4445,4448,4449,4451,4452,4455,4456,4458],{},[4441,4442,4443],"strong",{},"Fix",": extract ",[30,4446,4447],{},"QuantityInput"," into its own ",[30,4450,4421],{}," file and leave ",[30,4453,4454],{},"ProductDetailPage"," (and ",[30,4457,4326],{},") as server components:",[24,4460,4462],{"className":26,"code":4461,"language":22,"meta":28,"style":28},"\u002F\u002F ProductDetailPage.js — Server Component (no directive)\nimport { formatCurrency } from '.\u002Futils\u002FformatCurrency'\nimport { ProductSpecsTable } from '.\u002FProductSpecsTable'\nimport QuantityInput from '.\u002FQuantityInput'\n\nexport default async function ProductDetailPage({ productId }) {\n  const product = await db.products.findUnique({ where: { id: productId } })\n  return (\n    \u003Cdiv>\n      \u003Ch1>{product.name}\u003C\u002Fh1>\n      \u003Cp>{formatCurrency(product.price)}\u003C\u002Fp>\n      \u003CProductSpecsTable specs={product.specs} \u002F>\n      \u003CQuantityInput \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F QuantityInput.js — Client Component (only this ships to browser)\n'use client'\nimport { useState } from 'react'\n\nexport default function QuantityInput() {\n  const [quantity, setQuantity] = useState(1)\n  return \u003Cinput type=\"number\" value={quantity} onChange={e => setQuantity(Number(e.target.value))} \u002F>\n}\n",[30,4463,4464,4469,4479,4489,4501,4505,4523,4539,4545,4553,4565,4581,4593,4601,4609,4613,4617,4621,4626,4630,4640,4644,4657,4681,4726],{"__ignoreMap":28},[33,4465,4466],{"class":35,"line":36},[33,4467,4468],{"class":39},"\u002F\u002F ProductDetailPage.js — Server Component (no directive)\n",[33,4470,4471,4473,4475,4477],{"class":35,"line":43},[33,4472,232],{"class":231},[33,4474,4195],{"class":235},[33,4476,239],{"class":231},[33,4478,4200],{"class":242},[33,4480,4481,4483,4485,4487],{"class":35,"line":49},[33,4482,232],{"class":231},[33,4484,4207],{"class":235},[33,4486,239],{"class":231},[33,4488,4212],{"class":242},[33,4490,4491,4493,4496,4498],{"class":35,"line":55},[33,4492,232],{"class":231},[33,4494,4495],{"class":235}," QuantityInput ",[33,4497,239],{"class":231},[33,4499,4500],{"class":242}," '.\u002FQuantityInput'\n",[33,4502,4503],{"class":35,"line":61},[33,4504,77],{"emptyLinePlaceholder":76},[33,4506,4507,4509,4511,4513,4515,4517,4519,4521],{"class":35,"line":67},[33,4508,264],{"class":231},[33,4510,267],{"class":231},[33,4512,270],{"class":231},[33,4514,273],{"class":231},[33,4516,4237],{"class":276},[33,4518,280],{"class":235},[33,4520,284],{"class":283},[33,4522,287],{"class":235},[33,4524,4525,4527,4529,4531,4533,4535,4537],{"class":35,"line":73},[33,4526,307],{"class":231},[33,4528,311],{"class":310},[33,4530,314],{"class":231},[33,4532,317],{"class":231},[33,4534,320],{"class":235},[33,4536,323],{"class":276},[33,4538,326],{"class":235},[33,4540,4541,4543],{"class":35,"line":80},[33,4542,335],{"class":231},[33,4544,338],{"class":235},[33,4546,4547,4549,4551],{"class":35,"line":86},[33,4548,343],{"class":235},[33,4550,347],{"class":346},[33,4552,350],{"class":235},[33,4554,4555,4557,4559,4561,4563],{"class":35,"line":92},[33,4556,355],{"class":235},[33,4558,10],{"class":346},[33,4560,360],{"class":235},[33,4562,10],{"class":346},[33,4564,350],{"class":235},[33,4566,4567,4569,4571,4573,4575,4577,4579],{"class":35,"line":98},[33,4568,355],{"class":235},[33,4570,371],{"class":346},[33,4572,2322],{"class":235},[33,4574,4312],{"class":276},[33,4576,4315],{"class":235},[33,4578,371],{"class":346},[33,4580,350],{"class":235},[33,4582,4583,4585,4587,4589,4591],{"class":35,"line":104},[33,4584,355],{"class":235},[33,4586,4326],{"class":310},[33,4588,4329],{"class":276},[33,4590,407],{"class":231},[33,4592,4334],{"class":235},[33,4594,4595,4597,4599],{"class":35,"line":110},[33,4596,355],{"class":235},[33,4598,4447],{"class":310},[33,4600,2214],{"class":235},[33,4602,4603,4605,4607],{"class":35,"line":116},[33,4604,415],{"class":235},[33,4606,347],{"class":346},[33,4608,350],{"class":235},[33,4610,4611],{"class":35,"line":122},[33,4612,424],{"class":235},[33,4614,4615],{"class":35,"line":127},[33,4616,394],{"class":235},[33,4618,4619],{"class":35,"line":133},[33,4620,77],{"emptyLinePlaceholder":76},[33,4622,4623],{"class":35,"line":139},[33,4624,4625],{"class":39},"\u002F\u002F QuantityInput.js — Client Component (only this ships to browser)\n",[33,4627,4628],{"class":35,"line":145},[33,4629,460],{"class":242},[33,4631,4632,4634,4636,4638],{"class":35,"line":151},[33,4633,232],{"class":231},[33,4635,471],{"class":235},[33,4637,239],{"class":231},[33,4639,476],{"class":242},[33,4641,4642],{"class":35,"line":157},[33,4643,77],{"emptyLinePlaceholder":76},[33,4645,4646,4648,4650,4652,4655],{"class":35,"line":163},[33,4647,264],{"class":231},[33,4649,267],{"class":231},[33,4651,273],{"class":231},[33,4653,4654],{"class":276}," QuantityInput",[33,4656,547],{"class":235},[33,4658,4659,4661,4663,4665,4667,4669,4671,4673,4675,4677,4679],{"class":35,"line":169},[33,4660,307],{"class":231},[33,4662,504],{"class":235},[33,4664,4253],{"class":310},[33,4666,510],{"class":235},[33,4668,4258],{"class":310},[33,4670,516],{"class":235},[33,4672,407],{"class":231},[33,4674,521],{"class":276},[33,4676,524],{"class":235},[33,4678,4269],{"class":310},[33,4680,530],{"class":235},[33,4682,4683,4685,4687,4690,4693,4695,4698,4701,4703,4706,4709,4711,4713,4715,4717,4719,4721,4723],{"class":35,"line":174},[33,4684,335],{"class":231},[33,4686,920],{"class":235},[33,4688,4689],{"class":346},"input",[33,4691,4692],{"class":276}," type",[33,4694,407],{"class":231},[33,4696,4697],{"class":242},"\"number\"",[33,4699,4700],{"class":276}," value",[33,4702,407],{"class":231},[33,4704,4705],{"class":235},"{quantity} ",[33,4707,4708],{"class":276},"onChange",[33,4710,407],{"class":231},[33,4712,4371],{"class":235},[33,4714,4374],{"class":283},[33,4716,1640],{"class":231},[33,4718,4379],{"class":276},[33,4720,524],{"class":235},[33,4722,4384],{"class":276},[33,4724,4725],{"class":235},"(e.target.value))} \u002F>\n",[33,4727,4728],{"class":35,"line":180},[33,4729,394],{"class":235},[371,4731,4732,4733,4735],{},"Now only ",[30,4734,4447],{},"'s code and its dependencies ship to the browser instead of the entire page.",[371,4737,4738,614,4741,4743],{},[4441,4739,4740],{},"The lesson",[30,4742,4421],{}," should be pushed down to the smallest component that actually needs interactivity — not applied at the page level when only a tiny part needs state.",[14,4745,4747],{"id":4746},"key-takeaways","Key Takeaways",[19,4749,4751],{"filename":4750,"language":22},"key_takeaways.js",[24,4752,4754],{"className":26,"code":4753,"language":22,"meta":28,"style":28},"\u002F\u002F 1. SSR (rendering to HTML on server, then hydrating) and RSC (components\n\u002F\u002F    whose code NEVER ships to client) solve DIFFERENT problems. SSR is\n\u002F\u002F    about WHEN first render happens; RSC is about WHERE code lives.\n\n\u002F\u002F 2. Components are server components (the default) unless marked\n\u002F\u002F    'use client'. Server components can be async, fetch data directly,\n\u002F\u002F    and access server-only resources. Client components can use hooks,\n\u002F\u002F    event handlers, and browser APIs.\n\n\u002F\u002F 3. The client\u002Fserver import boundary is ONE-DIRECTIONAL — a client\n\u002F\u002F    component cannot import a server component, but a server component\n\u002F\u002F    can pass another server component as children\u002Fprops into a client\n\u002F\u002F    component (the \"slot\" pattern).\n\n\u002F\u002F 4. RSC's payoff is ZERO client-bundle cost for server-only dependencies\n\u002F\u002F    (database clients, markdown parsers, heavy formatting libraries) and\n\u002F\u002F    concurrent, Suspense-streamable data fetching without useEffect\n\u002F\u002F    waterfalls. Bundle size scales with interactivity, not with rendering.\n\n\u002F\u002F 5. Hydration requires server HTML and client's first render to MATCH.\n\u002F\u002F    Values that differ between environments (time, window, randomness)\n\u002F\u002F    must be deferred until after mount to avoid hydration mismatches.\n\n\u002F\u002F 6. Next.js App Router is the primary production RSC environment —\n\u002F\u002F    every file under app\u002F is a server component by default. Use the\n\u002F\u002F    metadata API for SEO, loading.js for Suspense fallbacks, and\n\u002F\u002F    push 'use client' to the smallest interactive leaf.\n\n\u002F\u002F 7. Server components can't use Context (useContext) — it requires\n\u002F\u002F    React's client-side re-rendering model. Pass props or use\n\u002F\u002F    framework-specific mechanisms for cross-boundary value sharing.\n",[30,4755,4756,4761,4766,4771,4775,4780,4785,4790,4795,4799,4804,4809,4814,4819,4823,4828,4833,4838,4843,4847,4852,4857,4862,4866,4871,4876,4881,4886,4890,4895,4900],{"__ignoreMap":28},[33,4757,4758],{"class":35,"line":36},[33,4759,4760],{"class":39},"\u002F\u002F 1. SSR (rendering to HTML on server, then hydrating) and RSC (components\n",[33,4762,4763],{"class":35,"line":43},[33,4764,4765],{"class":39},"\u002F\u002F    whose code NEVER ships to client) solve DIFFERENT problems. SSR is\n",[33,4767,4768],{"class":35,"line":49},[33,4769,4770],{"class":39},"\u002F\u002F    about WHEN first render happens; RSC is about WHERE code lives.\n",[33,4772,4773],{"class":35,"line":55},[33,4774,77],{"emptyLinePlaceholder":76},[33,4776,4777],{"class":35,"line":61},[33,4778,4779],{"class":39},"\u002F\u002F 2. Components are server components (the default) unless marked\n",[33,4781,4782],{"class":35,"line":67},[33,4783,4784],{"class":39},"\u002F\u002F    'use client'. Server components can be async, fetch data directly,\n",[33,4786,4787],{"class":35,"line":73},[33,4788,4789],{"class":39},"\u002F\u002F    and access server-only resources. Client components can use hooks,\n",[33,4791,4792],{"class":35,"line":80},[33,4793,4794],{"class":39},"\u002F\u002F    event handlers, and browser APIs.\n",[33,4796,4797],{"class":35,"line":86},[33,4798,77],{"emptyLinePlaceholder":76},[33,4800,4801],{"class":35,"line":92},[33,4802,4803],{"class":39},"\u002F\u002F 3. The client\u002Fserver import boundary is ONE-DIRECTIONAL — a client\n",[33,4805,4806],{"class":35,"line":98},[33,4807,4808],{"class":39},"\u002F\u002F    component cannot import a server component, but a server component\n",[33,4810,4811],{"class":35,"line":104},[33,4812,4813],{"class":39},"\u002F\u002F    can pass another server component as children\u002Fprops into a client\n",[33,4815,4816],{"class":35,"line":110},[33,4817,4818],{"class":39},"\u002F\u002F    component (the \"slot\" pattern).\n",[33,4820,4821],{"class":35,"line":116},[33,4822,77],{"emptyLinePlaceholder":76},[33,4824,4825],{"class":35,"line":122},[33,4826,4827],{"class":39},"\u002F\u002F 4. RSC's payoff is ZERO client-bundle cost for server-only dependencies\n",[33,4829,4830],{"class":35,"line":127},[33,4831,4832],{"class":39},"\u002F\u002F    (database clients, markdown parsers, heavy formatting libraries) and\n",[33,4834,4835],{"class":35,"line":133},[33,4836,4837],{"class":39},"\u002F\u002F    concurrent, Suspense-streamable data fetching without useEffect\n",[33,4839,4840],{"class":35,"line":139},[33,4841,4842],{"class":39},"\u002F\u002F    waterfalls. Bundle size scales with interactivity, not with rendering.\n",[33,4844,4845],{"class":35,"line":145},[33,4846,77],{"emptyLinePlaceholder":76},[33,4848,4849],{"class":35,"line":151},[33,4850,4851],{"class":39},"\u002F\u002F 5. Hydration requires server HTML and client's first render to MATCH.\n",[33,4853,4854],{"class":35,"line":157},[33,4855,4856],{"class":39},"\u002F\u002F    Values that differ between environments (time, window, randomness)\n",[33,4858,4859],{"class":35,"line":163},[33,4860,4861],{"class":39},"\u002F\u002F    must be deferred until after mount to avoid hydration mismatches.\n",[33,4863,4864],{"class":35,"line":169},[33,4865,77],{"emptyLinePlaceholder":76},[33,4867,4868],{"class":35,"line":174},[33,4869,4870],{"class":39},"\u002F\u002F 6. Next.js App Router is the primary production RSC environment —\n",[33,4872,4873],{"class":35,"line":180},[33,4874,4875],{"class":39},"\u002F\u002F    every file under app\u002F is a server component by default. Use the\n",[33,4877,4878],{"class":35,"line":186},[33,4879,4880],{"class":39},"\u002F\u002F    metadata API for SEO, loading.js for Suspense fallbacks, and\n",[33,4882,4883],{"class":35,"line":192},[33,4884,4885],{"class":39},"\u002F\u002F    push 'use client' to the smallest interactive leaf.\n",[33,4887,4888],{"class":35,"line":715},[33,4889,77],{"emptyLinePlaceholder":76},[33,4891,4892],{"class":35,"line":721},[33,4893,4894],{"class":39},"\u002F\u002F 7. Server components can't use Context (useContext) — it requires\n",[33,4896,4897],{"class":35,"line":727},[33,4898,4899],{"class":39},"\u002F\u002F    React's client-side re-rendering model. Pass props or use\n",[33,4901,4902],{"class":35,"line":733},[33,4903,4904],{"class":39},"\u002F\u002F    framework-specific mechanisms for cross-boundary value sharing.\n",[4906,4907,4908],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sk71V, html code.shiki .sk71V{--shiki-default:#22863A;--shiki-github-dark:#85E89D}",{"title":28,"searchDepth":43,"depth":43,"links":4910},[4911,4912,4913,4914,4917,4918,4919,4920,4921,4924,4925,4926,4927,4928,4929,4930],{"id":16,"depth":43,"text":17},{"id":198,"depth":43,"text":199},{"id":431,"depth":43,"text":432},{"id":751,"depth":43,"text":752,"children":4915},[4916],{"id":950,"depth":49,"text":951},{"id":1245,"depth":43,"text":1246},{"id":1420,"depth":43,"text":1421},{"id":1692,"depth":43,"text":1693},{"id":1933,"depth":43,"text":1934},{"id":2221,"depth":43,"text":2222,"children":4922},[4923],{"id":2348,"depth":49,"text":2349},{"id":2619,"depth":43,"text":2620},{"id":2788,"depth":43,"text":2789},{"id":3568,"depth":43,"text":3569},{"id":3802,"depth":43,"text":3803},{"id":3973,"depth":43,"text":3974},{"id":4168,"depth":43,"text":4169},{"id":4746,"depth":43,"text":4747},"RSC vs SSR vs CSR, Next.js App Router, server\u002Fclient component boundary, 'use client' directive, data fetching in server components, streaming with Suspense, hydration, metadata, and zero-bundle-size components. Code-first reference for mid-to-senior React engineers.","md",{},"\u002Freact\u002F22-server-components-and-ssr",{"title":5,"description":4931},"react\u002F22-server-components-and-ssr","eARYa68jlHJKXz2QRdC2CjocIps77-0o-ckWD4VDm9E",1789924652509]