[{"data":1,"prerenderedAt":1199},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching":1190},{"id":4,"title":5,"body":6,"description":1170,"extension":1171,"meta":1172,"navigation":1183,"path":1184,"seo":1185,"stem":1188,"__hash__":1189},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Findex.md","Edge Compute & Dynamic Caching",{"type":7,"value":8,"toc":1151},"minimark",[9,14,24,32,145,150,157,184,187,279,283,322,326,342,346,354,358,365,428,432,435,473,477,571,703,707,713,719,725,735,741,745,748,931,935,945,949,955,958,961,965,976,980,986,990,993,997,1013,1022,1031,1040,1049,1058,1067,1071,1099,1103,1136,1141,1144,1147],[10,11,13],"h1",{"id":12},"edge-compute-dynamic-caching-fast-ttfb-for-pages-that-look-uncacheable","Edge Compute & Dynamic Caching: Fast TTFB for Pages That Look Uncacheable",[15,16,17,18,23],"p",{},"This topic extends ",[19,20,22],"a",{"href":21},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture"," to the pages a traditional CDN cannot cache: those that vary by user, by experiment, by location or by session. A static marketing page is easy — cache it at the edge and TTFB drops to tens of milliseconds. A product page showing a logged-in user's name, a personalised recommendation rail and a price in their currency usually goes all the way to the origin for every request, and its TTFB is whatever the origin manages: often 400–1000ms at p75 once you add distance, cold caches and database queries.",[15,25,26,27,31],{},"Edge compute — Cloudflare Workers, Fastly Compute, Vercel and Netlify edge functions, CloudFront Functions and Lambda@Edge, Akamai EdgeWorkers — runs your code in the CDN's points of presence, next to the cache. That changes what is cacheable. The page can be split into a shared, cacheable shell and small personalised fragments; the edge can choose a cached variant by cookie or header without fragmenting the cache; and per-user data can be fetched in parallel with streaming the cached shell. Done well, pages that \"had\" to be dynamic get the same sub-",[28,29,30],"code",{},"200ms"," TTFB as static ones.",[15,33,34],{},[35,36,42,43,42,50,42,54,42,57,42,66,42,72,42,81,42,87,42,92,42,97,42,100,42,103,42,106,42,109,42,112,42,115,42,120,42,124,42,126,42,130,42,132,42,135,42,137,42,140,42,142,42],"svg",{"viewBox":37,"width":38,"role":39,"ariaLabel":40,"style":41},"0 0 760 228","100%","img","Comparison of a personalised page rendered at the origin for every request with one assembled at the edge from a cached shell and small personal fragments.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[44,45],"rect",{"className":46,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],"svg-canvas","0","#ffffff",[51,52,53],"title",{},"Origin-rendered vs edge-assembled personalised page",[55,56,40],"desc",{},[44,58],{"x":59,"y":59,"width":60,"height":61,"rx":62,"fill":63,"stroke":64,"style":65},"1","758","226","10","none","currentColor","stroke-opacity:0.18",[67,68,53],"text",{"x":69,"y":70,"fill":64,"style":71},"28.0","34.0","font-size:16px;font-weight:700",[44,73],{"x":69,"y":74,"width":75,"height":76,"rx":77,"fill":78,"stroke":79,"style":80},"56.0","340.0","150.0","6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[67,82,86],{"x":83,"y":84,"fill":64,"style":85},"42.0","82.0","font-size:14px;font-weight:700","Origin-rendered (no cache)",[67,88,91],{"x":83,"y":89,"fill":64,"style":90},"108.0","font-size:12.5px;font-weight:700","•",[67,93,96],{"x":74,"y":89,"fill":64,"style":94,"textAnchor":95},"font-size:12.5px","start","Every request travels to the origin",[67,98,91],{"x":83,"y":99,"fill":64,"style":90},"132.0",[67,101,102],{"x":74,"y":99,"fill":64,"style":94,"textAnchor":95},"Full render + DB queries per request",[67,104,91],{"x":83,"y":105,"fill":64,"style":90},"156.0",[67,107,108],{"x":74,"y":105,"fill":64,"style":94,"textAnchor":95},"TTFB 600-1000ms at p75",[67,110,91],{"x":83,"y":111,"fill":64,"style":90},"180.0",[67,113,114],{"x":74,"y":111,"fill":64,"style":94,"textAnchor":95},"Origin load scales with traffic",[44,116],{"x":117,"y":74,"width":75,"height":76,"rx":77,"fill":118,"stroke":118,"style":119},"392.0","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[67,121,123],{"x":122,"y":84,"fill":64,"style":85},"406.0","Edge-assembled",[67,125,91],{"x":122,"y":89,"fill":64,"style":90},[67,127,129],{"x":128,"y":89,"fill":64,"style":94,"textAnchor":95},"420.0","Shared shell cached at every PoP",[67,131,91],{"x":122,"y":99,"fill":64,"style":90},[67,133,134],{"x":128,"y":99,"fill":64,"style":94,"textAnchor":95},"Personal fragment fetched in parallel",[67,136,91],{"x":122,"y":105,"fill":64,"style":90},[67,138,139],{"x":128,"y":105,"fill":64,"style":94,"textAnchor":95},"TTFB 60-150ms at p75",[67,141,91],{"x":122,"y":111,"fill":64,"style":90},[67,143,144],{"x":128,"y":111,"fill":64,"style":94,"textAnchor":95},"Origin serves small fragments only",[146,147,149],"h2",{"id":148},"the-metric-degradation-this-topic-addresses","The Metric Degradation This Topic Addresses",[15,151,152,153,156],{},"TTFB is the first phase of every LCP. For server-rendered pages, slow TTFB pushes everything else later: if TTFB is 800ms at p75, the LCP budget of ",[28,154,155],{},"2.5s"," has already lost a third. Field data typically shows a clear split:",[158,159,160,168,178],"ul",{},[161,162,163,167],"li",{},[164,165,166],"strong",{},"Anonymous traffic on cached pages:"," TTFB 50–150ms.",[161,169,170,177],{},[164,171,172,173,176],{},"Logged-in traffic, personalised pages, or pages with ",[28,174,175],{},"Vary: Cookie",":"," TTFB 400–1200ms, because they bypass the cache entirely.",[161,179,180,183],{},[164,181,182],{},"Pages with query-string variations or A\u002FB test cookies:"," cache hit rates that look fine in aggregate but collapse for the variants that matter.",[15,185,186],{},"The goal of this topic is to move the second and third groups into the first, without serving anyone the wrong content.",[15,188,189],{},[35,190,42,193,42,196,42,199,42,201,42,204,42,206,42,216,42,223,42,231,42,236,42,240,42,245,42,249,42,253,42,257,42,261,42,265,42,268,42,272,42],{"viewBox":191,"width":38,"role":39,"ariaLabel":192,"style":41},"0 0 760 216","Bar chart of p75 time to first byte for anonymous cached pages, logged-in pages, A\u002FB-tested pages and pages with currency variants.",[44,194],{"className":195,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,197,198],{},"TTFB p75 by request type before edge caching",[55,200,192],{},[44,202],{"x":59,"y":59,"width":60,"height":203,"rx":62,"fill":63,"stroke":64,"style":65},"214",[67,205,198],{"x":69,"y":70,"fill":64,"style":71},[207,208],"line",{"x1":209,"y1":210,"x2":209,"y2":211,"stroke":79,"strokeWidth":212,"strokeDashArray":213,"style":215},"288.8","72.0","194.0","1.5",[214,214],"4","stroke-opacity:0.9",[67,217,222],{"x":218,"y":219,"fill":64,"style":220,"textAnchor":221},"146.0","92.0","font-size:13px","end","Anonymous, cached",[44,224],{"x":225,"y":226,"width":227,"height":228,"rx":229,"fill":118,"stroke":118,"style":230},"158.0","78.0","62.1","19","3","fill-opacity:0.55;stroke-opacity:0.9",[67,232,235],{"x":233,"y":219,"fill":64,"style":234},"226.1","font-size:12px;font-weight:600","95ms",[67,237,239],{"x":218,"y":238,"fill":64,"style":220,"textAnchor":221},"123.0","Logged-in (bypass)",[44,241],{"x":225,"y":242,"width":243,"height":228,"rx":229,"fill":78,"stroke":79,"style":244},"109.0","510.0","fill-opacity:0.7;stroke-opacity:0.9",[67,246,248],{"x":247,"y":238,"fill":64,"style":234},"674.0","780ms",[67,250,252],{"x":218,"y":251,"fill":64,"style":220,"textAnchor":221},"154.0","A\u002FB variant cookie",[44,254],{"x":225,"y":255,"width":256,"height":228,"rx":229,"fill":78,"stroke":79,"style":244},"140.0","418.5",[67,258,260],{"x":259,"y":251,"fill":64,"style":234},"582.5","640ms",[67,262,264],{"x":218,"y":263,"fill":64,"style":220,"textAnchor":221},"185.0","Currency by geo",[44,266],{"x":225,"y":267,"width":75,"height":228,"rx":229,"fill":78,"stroke":79,"style":244},"171.0",[67,269,271],{"x":270,"y":263,"fill":64,"style":234},"504.0","520ms",[67,273,278],{"x":209,"y":274,"fill":275,"style":276,"textAnchor":277},"68.0","#8a6608","font-size:12px;font-weight:700","middle","200ms target",[146,280,282],{"id":281},"prerequisites","Prerequisites",[158,284,285,299,305,311],{},[161,286,287,290,291,294,295,298],{},[164,288,289],{},"An edge compute platform"," in front of your origin, with access to its cache API (Cloudflare's ",[28,292,293],{},"caches.default"," and ",[28,296,297],{},"cf"," options, Fastly's surrogate controls, Vercel's edge cache headers, and so on).",[161,300,301,304],{},[164,302,303],{},"A clear content inventory"," per template: which parts are the same for everyone, which vary by a small set of values (country, currency, experiment variant), and which are truly per user.",[161,306,307,310],{},[164,308,309],{},"Origin endpoints for fragments"," — small JSON or HTML endpoints for the personalised parts.",[161,312,313,316,317,321],{},[164,314,315],{},"Cache-key discipline:"," an understanding of what currently varies the cache key (cookies, headers, query parameters) — see ",[19,318,320],{"href":319},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002Fnormalizing-cache-keys-to-raise-hit-rate\u002F","normalizing cache keys to raise hit rate",".",[146,323,325],{"id":324},"_1-environment-setup-classify-every-byte-of-the-page","1. Environment Setup: Classify Every Byte of the Page",[15,327,328,329,333,334,337,338,341],{},"For each template, mark regions as ",[330,331,332],"em",{},"shared"," (identical for all users), ",[330,335,336],{},"variant"," (one of a few versions, chosen by a low-cardinality key such as country or experiment arm), or ",[330,339,340],{},"personal"," (unique per user). Most pages are overwhelmingly shared: on a typical product page, everything except the header's account menu, the cart count and a recommendation rail.",[146,343,345],{"id":344},"_2-capture-a-baseline","2. Capture a Baseline",[15,347,348,349,353],{},"Record TTFB p75 and cache hit ratio per template and per request type (anonymous vs logged-in) from RUM with ",[19,350,352],{"href":351},"\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fexposing-backend-timing-with-server-timing-headers\u002F","Server-Timing headers"," carrying cache status. This tells you how much traffic currently bypasses the cache and how slow it is.",[146,355,357],{"id":356},"_3-isolate-what-forces-bypass","3. Isolate What Forces Bypass",[15,359,360,361,364],{},"Usually one or two things push a whole page out of the cache: a session cookie read during rendering, a personalised header widget, or a variant decided at the origin. Find them in the origin code — every ",[28,362,363],{},"request.cookies"," read in a page render is a candidate.",[15,366,367],{},[35,368,42,371,42,374,42,377,42,379,42,382,42,384,42,388,42,393,42,398,42,402,42,406,42,409,42,411,42,415,42,418,42,421,42,425,42],{"viewBox":369,"width":38,"role":39,"ariaLabel":370,"style":41},"0 0 760 268","Regions of a product page classified as shared, variant or personal, with how each is served at the edge.",[44,372],{"className":373,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,375,376],{},"Anatomy of an edge-cacheable product page",[55,378,370],{},[44,380],{"x":59,"y":59,"width":60,"height":381,"rx":62,"fill":63,"stroke":64,"style":65},"266",[67,383,376],{"x":69,"y":70,"fill":64,"style":71},[44,385],{"x":69,"y":74,"width":386,"height":387,"rx":77,"fill":118,"stroke":118,"style":119},"704.0","40.0",[67,389,392],{"x":83,"y":390,"fill":64,"style":391},"80.5","font-size:13px;font-weight:700","Shared shell",[67,394,397],{"x":395,"y":390,"fill":64,"style":396,"textAnchor":95},"180.3","font-size:12px","layout, product details, images, reviews — cached for everyone, purged by tag",[44,399],{"x":69,"y":400,"width":386,"height":387,"rx":77,"fill":64,"stroke":64,"style":401},"106.0","fill-opacity:0.06;stroke-opacity:0.4",[67,403,405],{"x":83,"y":404,"fill":64,"style":391},"130.5","Variant regions",[67,407,408],{"x":395,"y":404,"fill":64,"style":396,"textAnchor":95},"price in currency, experiment arm — cached per variant key (few values)",[44,410],{"x":69,"y":105,"width":386,"height":387,"rx":77,"fill":78,"stroke":79,"style":80},[67,412,414],{"x":83,"y":413,"fill":64,"style":391},"180.5","Personal fragments",[67,416,417],{"x":395,"y":413,"fill":64,"style":396,"textAnchor":95},"account menu, cart count, recommendations — fetched per user, not cached",[44,419],{"x":69,"y":420,"width":386,"height":387,"rx":77,"fill":118,"stroke":118,"style":119},"206.0",[67,422,424],{"x":83,"y":423,"fill":64,"style":391},"230.5","Assembly",[67,426,427],{"x":395,"y":423,"fill":64,"style":396,"textAnchor":95},"edge function stitches or streams; client fetch as a fallback",[146,429,431],{"id":430},"_4-apply-split-key-and-assemble","4. Apply: Split, Key and Assemble",[15,433,434],{},"The fixes, in order of how much traffic they usually recover:",[436,437,438,444,452,463],"ol",{},[161,439,440,443],{},[164,441,442],{},"Stop varying the whole page on cookies."," Render the shared shell without reading session cookies; serve personal bits separately. This alone often moves logged-in traffic onto the cache.",[161,445,446,449,450,321],{},[164,447,448],{},"Key variants explicitly at the edge."," Derive a small variant key (country → currency, experiment cookie → arm) in the edge function and add it to the cache key, instead of ",[28,451,175],{},[161,453,454,457,458,462],{},[164,455,456],{},"Assemble personal fragments."," Either at the edge — fetching fragments in parallel and streaming them into the cached shell (see ",[19,459,461],{"href":460},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fedge-side-includes-vs-client-side-fragments\u002F","edge side includes vs client-side fragments",") — or on the client after the shell paints.",[161,464,465,468,469,321],{},[164,466,467],{},"Personalise at the edge only what must be in the first paint",", such as a localised hero, as in ",[19,470,472],{"href":471},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fpersonalizing-cached-pages-at-the-edge\u002F","personalizing cached pages at the edge",[146,474,476],{"id":475},"deconstructing-ttfb-for-an-edge-assembled-page","Deconstructing TTFB for an Edge-Assembled Page",[478,479,480,498],"table",{},[481,482,483],"thead",{},[484,485,486,490,493,495],"tr",{},[487,488,489],"th",{},"Phase",[487,491,492],{},"Origin-rendered",[487,494,123],{},[487,496,497],{},"Lever",[499,500,501,515,529,543,557],"tbody",{},[484,502,503,507,510,512],{},[504,505,506],"td",{},"Client → edge",[504,508,509],{},"20–80ms",[504,511,509],{},[504,513,514],{},"CDN coverage, HTTP\u002F3",[484,516,517,520,523,526],{},[504,518,519],{},"Edge → origin",[504,521,522],{},"50–200ms",[504,524,525],{},"0 (cache hit)",[504,527,528],{},"cacheability",[484,530,531,534,537,540],{},[504,532,533],{},"Origin render + data",[504,535,536],{},"200–700ms",[504,538,539],{},"0 for shell",[504,541,542],{},"splitting personal data out",[484,544,545,548,551,554],{},[504,546,547],{},"Fragment fetch",[504,549,550],{},"—",[504,552,553],{},"30–150ms, in parallel with shell streaming",[504,555,556],{},"fragment endpoint speed",[484,558,559,562,565,568],{},[504,560,561],{},"First byte to client",[504,563,564],{},"sum of the above",[504,566,567],{},"client→edge + edge compute (~1–10ms)",[504,569,570],{},"stream the shell immediately",[15,572,573],{},[35,574,42,577,42,580,42,583,42,585,42,588,42,590,42,594,42,598,42,602,42,608,42,612,42,617,42,621,42,625,42,629,42,632,42,635,42,638,42,642,42,646,42,651,42,656,42,662,42,666,42,668,42,672,42,676,42,680,42,684,42,688,42,692,42,696,42,699,42],{"viewBox":575,"width":38,"role":39,"ariaLabel":576,"style":41},"0 0 760 264","Two timelines comparing time to first byte for a page rendered at the origin and a cached shell streamed from the edge while a personal fragment is fetched.",[44,578],{"className":579,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,581,582],{},"First byte timing, origin-rendered vs edge-assembled",[55,584,576],{},[44,586],{"x":59,"y":59,"width":60,"height":587,"rx":62,"fill":63,"stroke":64,"style":65},"262",[67,589,582],{"x":69,"y":70,"fill":64,"style":71},[207,591],{"x1":592,"y1":210,"x2":592,"y2":211,"stroke":79,"strokeWidth":212,"strokeDashArray":593,"style":215},"241.7",[214,214],[67,595,597],{"x":69,"y":596,"fill":64,"style":90},"96.0","Origin",[44,599],{"x":600,"y":226,"width":387,"height":601,"rx":229,"fill":64,"stroke":64,"style":401},"104.1","26.0",[67,603,607],{"x":604,"y":605,"fill":64,"style":606,"textAnchor":277},"124.1","95.5","font-size:10.5px","to edge",[44,609],{"x":610,"y":226,"width":611,"height":601,"rx":229,"fill":64,"stroke":64,"style":401},"145.6","95.4",[67,613,616],{"x":614,"y":605,"fill":64,"style":615,"textAnchor":277},"193.3","font-size:11.5px","to origin",[44,618],{"x":619,"y":226,"width":620,"height":601,"rx":229,"fill":78,"stroke":79,"style":80},"242.4","385.9",[67,622,624],{"x":623,"y":605,"fill":64,"style":615,"textAnchor":277},"435.4","render + DB",[67,626,628],{"x":69,"y":627,"fill":64,"style":90},"134.0","Edge shell",[44,630],{"x":600,"y":631,"width":387,"height":601,"rx":229,"fill":64,"stroke":64,"style":401},"116.0",[67,633,607],{"x":604,"y":634,"fill":64,"style":606,"textAnchor":277},"133.5",[44,636],{"x":610,"y":631,"width":637,"height":601,"rx":229,"fill":118,"stroke":118,"style":119},"5.4",[67,639,641],{"x":69,"y":640,"fill":64,"style":90},"172.0","Fragment",[44,643],{"x":644,"y":251,"width":645,"height":601,"rx":229,"fill":118,"stroke":118,"style":119},"152.5","102.3",[67,647,650],{"x":648,"y":649,"fill":64,"style":615,"textAnchor":277},"203.6","171.5","personal JSON",[207,652],{"x1":653,"y1":211,"x2":654,"y2":211,"stroke":64,"style":655},"103.3","726.0","stroke-opacity:0.4",[67,657,661],{"x":658,"y":659,"fill":660,"style":615,"textAnchor":277},"114.0","211.0","#51617a","0ms",[67,663,665],{"x":664,"y":659,"fill":660,"style":615,"textAnchor":277},"172.5","100ms",[67,667,30],{"x":592,"y":659,"fill":660,"style":615,"textAnchor":277},[67,669,671],{"x":670,"y":659,"fill":660,"style":615,"textAnchor":277},"310.9","300ms",[67,673,675],{"x":674,"y":659,"fill":660,"style":615,"textAnchor":277},"380.1","400ms",[67,677,679],{"x":678,"y":659,"fill":660,"style":615,"textAnchor":277},"449.3","500ms",[67,681,683],{"x":682,"y":659,"fill":660,"style":615,"textAnchor":277},"518.4","600ms",[67,685,687],{"x":686,"y":659,"fill":660,"style":615,"textAnchor":277},"587.6","700ms",[67,689,691],{"x":690,"y":659,"fill":660,"style":615,"textAnchor":277},"656.8","800ms",[67,693,695],{"x":694,"y":659,"fill":660,"style":615,"textAnchor":277},"715.3","900ms",[67,697,698],{"x":592,"y":274,"fill":275,"style":276,"textAnchor":277},"200ms TTFB",[67,700,702],{"x":69,"y":701,"fill":660,"style":94},"242.0","The cached shell starts streaming at about 70ms; the personal fragment arrives in parallel and is inserted when ready.",[146,704,706],{"id":705},"advanced-diagnostics-and-edge-cases","Advanced Diagnostics and Edge Cases",[15,708,709,712],{},[164,710,711],{},"Cache poisoning through personal data."," The biggest risk of dynamic caching is serving one user's content to another. Any page cached at the edge must be rendered without reading per-user state; enforce it in code (render functions that do not receive the request's cookies) and test it (request as two users, compare responses).",[15,714,715,718],{},[164,716,717],{},"Edge compute cold starts."," Isolate-based platforms (Workers, Vercel\u002FNetlify edge) start in milliseconds; container- or VM-based edge functions can have cold starts of hundreds of milliseconds. Measure p95 edge processing time, not just averages.",[15,720,721,724],{},[164,722,723],{},"Data locality."," Edge functions are close to users, but databases usually are not. An edge function that queries a single-region database for every request can be slower than origin rendering. Keep data access at the edge to cached reads or globally replicated stores.",[15,726,727,730,731,321],{},[164,728,729],{},"Purging."," Cached shells must be invalidated when content changes. Tag-based purging keeps this precise — see ",[19,732,734],{"href":733},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcache-invalidation-patterns\u002Fpurging-cdn-cache-by-tag-on-deploy\u002F","purging CDN cache by tag on deploy",[15,736,737,740],{},[164,738,739],{},"Streaming and buffering."," Assembling at the edge only helps TTFB if the edge streams the shell immediately rather than buffering the full response. Some platforms buffer by default when you transform HTML; check with a timing waterfall.",[146,742,744],{"id":743},"validation-and-budgeting","Validation and Budgeting",[15,746,747],{},"Track TTFB p75 per template split by anonymous and logged-in traffic, the edge cache hit ratio for HTML, and a correctness check. Set budgets such as \"logged-in product page TTFB p75 under 200ms\" and \"HTML hit ratio over 85%\".",[749,750,755],"pre",{"className":751,"code":752,"language":753,"meta":754,"style":754},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F ci\u002Fcache-isolation.test.mjs — make sure two users never receive each other's content.\nconst a = await fetch(url, { headers: { cookie: 'session=userA' } }).then((r) => r.text());\nconst b = await fetch(url, { headers: { cookie: 'session=userB' } }).then((r) => r.text());\nif (a.includes('userA@example.com') && b.includes('userA@example.com')) {\n  throw new Error('Personal data leaked across users via the edge cache');\n}\n\u002F\u002F trade-off: string checks only catch the markers you look for. Render test\n\u002F\u002F users with unique, searchable markers in every personal region.\n","javascript","",[28,756,757,765,821,858,893,913,919,925],{"__ignoreMap":754},[758,759,761],"span",{"class":207,"line":760},1,[758,762,764],{"class":763},"sjfSM","\u002F\u002F ci\u002Fcache-isolation.test.mjs — make sure two users never receive each other's content.\n",[758,766,768,772,776,779,782,786,790,794,797,800,803,807,810,813,816,818],{"class":207,"line":767},2,[758,769,771],{"class":770},"sPARh","const",[758,773,775],{"class":774},"sPXB4"," a",[758,777,778],{"class":770}," =",[758,780,781],{"class":770}," await",[758,783,785],{"class":784},"smZ65"," fetch",[758,787,789],{"class":788},"saISM","(url, { headers: { cookie: ",[758,791,793],{"class":792},"sZ8jY","'session=userA'",[758,795,796],{"class":788}," } }).",[758,798,799],{"class":784},"then",[758,801,802],{"class":788},"((",[758,804,806],{"class":805},"sQw3B","r",[758,808,809],{"class":788},") ",[758,811,812],{"class":770},"=>",[758,814,815],{"class":788}," r.",[758,817,67],{"class":784},[758,819,820],{"class":788},"());\n",[758,822,824,826,829,831,833,835,837,840,842,844,846,848,850,852,854,856],{"class":207,"line":823},3,[758,825,771],{"class":770},[758,827,828],{"class":774}," b",[758,830,778],{"class":770},[758,832,781],{"class":770},[758,834,785],{"class":784},[758,836,789],{"class":788},[758,838,839],{"class":792},"'session=userB'",[758,841,796],{"class":788},[758,843,799],{"class":784},[758,845,802],{"class":788},[758,847,806],{"class":805},[758,849,809],{"class":788},[758,851,812],{"class":770},[758,853,815],{"class":788},[758,855,67],{"class":784},[758,857,820],{"class":788},[758,859,861,864,867,870,873,876,878,881,884,886,888,890],{"class":207,"line":860},4,[758,862,863],{"class":770},"if",[758,865,866],{"class":788}," (a.",[758,868,869],{"class":784},"includes",[758,871,872],{"class":788},"(",[758,874,875],{"class":792},"'userA@example.com'",[758,877,809],{"class":788},[758,879,880],{"class":770},"&&",[758,882,883],{"class":788}," b.",[758,885,869],{"class":784},[758,887,872],{"class":788},[758,889,875],{"class":792},[758,891,892],{"class":788},")) {\n",[758,894,896,899,902,905,907,910],{"class":207,"line":895},5,[758,897,898],{"class":770},"  throw",[758,900,901],{"class":770}," new",[758,903,904],{"class":784}," Error",[758,906,872],{"class":788},[758,908,909],{"class":792},"'Personal data leaked across users via the edge cache'",[758,911,912],{"class":788},");\n",[758,914,916],{"class":207,"line":915},6,[758,917,918],{"class":788},"}\n",[758,920,922],{"class":207,"line":921},7,[758,923,924],{"class":763},"\u002F\u002F trade-off: string checks only catch the markers you look for. Render test\n",[758,926,928],{"class":207,"line":927},8,[758,929,930],{"class":763},"\u002F\u002F users with unique, searchable markers in every personal region.\n",[146,932,934],{"id":933},"worked-example-moving-logged-in-traffic-onto-the-cache","Worked Example: Moving Logged-In Traffic onto the Cache",[15,936,937,938,941,942,944],{},"A retailer's logged-in users — 40% of traffic and most of the revenue — bypassed the CDN entirely because the product page template read the session cookie to render a greeting and the cart count. TTFB p75 for them was 820ms versus 110ms for anonymous users. The team removed all session reads from the template, served the greeting and cart count from a small ",[28,939,940],{},"\u002Fapi\u002Fme"," endpoint fetched by an edge function and streamed into a placeholder, and keyed the cache on a derived currency value instead of ",[28,943,175],{},". Logged-in TTFB p75 fell to 140ms, LCP p75 for logged-in users improved by 560ms, and origin CPU dropped by more than half. A nightly isolation test confirmed no personal data was cached.",[146,946,948],{"id":947},"choosing-fragment-granularity","Choosing Fragment Granularity",[15,950,951,952,954],{},"How finely to split a page is a trade-off between cache efficiency and request overhead. One personal fragment per page — a single ",[28,953,940],{}," response carrying the greeting, cart count and any small personal flags — is usually the sweet spot: one request, fetched in parallel with the shell, small enough to be fast. Splitting every personal widget into its own fragment multiplies requests and complicates assembly; merging personal data back into the page defeats caching.",[15,956,957],{},"Recommendation rails and other expensive personal regions are the exception. They often depend on slower services, so give them their own fragment, load them after the main content, and reserve their space. Treat them as progressive enhancement: the page is complete without them, and a slow recommendation service never delays the first paint.",[15,959,960],{},"Variant regions follow a different rule: keep the number of variants small. Currency by country might produce ten variants; experiment arms two or three. Multiplying several variant dimensions together (currency × experiment × device class × language) quickly produces hundreds of cache keys per URL, most of which never warm. Collapse dimensions where you can — device class rarely needs to be in the key if the HTML is responsive — and move rare variants to client-side adjustment.",[146,962,964],{"id":963},"measuring-edge-function-cost","Measuring Edge Function Cost",[15,966,967,968,971,972,975],{},"Edge compute is not free in latency or money. Add your own ",[28,969,970],{},"Server-Timing"," entry for edge processing time and track its p95; isolate runtimes typically add 1–10ms, but code that awaits subrequests sequentially, parses large HTML with string operations, or calls a distant database can add hundreds. Use streaming HTML rewriters (such as Cloudflare's ",[28,973,974],{},"HTMLRewriter",") instead of buffering and regex-replacing the full document; they transform chunks as they pass through and keep TTFB low. Watch request-count billing, too: an edge function on every request, including static assets, multiplies cost for no benefit. Route only HTML and fragment requests through compute, and let static assets hit the cache directly.",[146,977,979],{"id":978},"observability-for-dynamic-edge-caching","Observability for Dynamic Edge Caching",[15,981,982,983,985],{},"Once pages are assembled at the edge, a slow page can be slow for several new reasons: a cache miss on the shell, a slow fragment endpoint, an edge function waiting on a subrequest, or a purge storm after a deploy. Make each visible. Emit ",[28,984,970],{}," entries for shell cache status, edge processing time and fragment fetch time, carry them into RUM, and chart TTFB p75 segmented by shell hit or miss. Log fragment endpoint latency at p95 separately — fragments are on every request, so their tail latency matters more than their average. Finally, keep a per-template dashboard of HTML hit ratio over time with deploys annotated; a sharp dip after each deploy suggests purges are broader than necessary, and a slow decline suggests a new cache-key dimension is fragmenting the cache.",[146,987,989],{"id":988},"a-rollout-plan","A Rollout Plan",[15,991,992],{},"Edge caching of dynamic pages is safe when introduced in stages. Start with one high-traffic template and anonymous traffic only, confirming hit ratio and correctness. Then remove cookie reads from that template's render path and enable caching for logged-in traffic behind a feature flag for a small percentage of users, with the isolation test running continuously. Expand to other templates once the pattern is proven, and only then consider edge-side personalisation of the first paint. Keep a kill switch that forces cache bypass per template; it is the fastest way to recover if a personal region slips into a cached shell.",[146,994,996],{"id":995},"faq","FAQ",[998,999,1002,1006],"details",{"className":1000},[1001],"faq-item",[1003,1004,1005],"summary",{},"Is edge rendering the same as edge caching?",[15,1007,1008,1009,1012],{},"No. Edge rendering runs your full rendering code at the edge for every request; it helps when the render is cheap and data is available at the edge, but it does not avoid per-request work. Edge caching serves stored responses and is far cheaper. The patterns in this topic mostly use the edge to ",[330,1010,1011],{},"select and assemble cached content",", with rendering at the edge reserved for small personalised pieces.",[998,1014,1016,1019],{"className":1015},[1001],[1003,1017,1018],{},"Does client-side fetching of personal data hurt LCP?",[15,1020,1021],{},"Usually not, because personal regions (account menus, cart counts, recommendation rails) are rarely the LCP element. The cached shell, including the LCP element, paints quickly; personal regions fill in afterwards. Reserve space for them to avoid layout shift.",[998,1023,1025,1028],{"className":1024},[1001],[1003,1026,1027],{},"How do A\u002FB tests fit in?",[15,1029,1030],{},"Assign the experiment arm at the edge (from a cookie, or by bucketing a new visitor), add the arm to the cache key, and serve the cached variant. That keeps experiments flicker-free and cacheable, instead of rewriting the page client-side after load.",[998,1032,1034,1037],{"className":1033},[1001],[1003,1035,1036],{},"What about pages that are truly unique per user, like a dashboard?",[15,1038,1039],{},"Cache the application shell (layout, navigation, static UI) and fetch the user's data separately, either streamed from the edge or loaded on the client. The shell gives a fast first paint; the data determines when the page is useful, so optimise the data endpoint's latency as well.",[998,1041,1043,1046],{"className":1042},[1001],[1003,1044,1045],{},"Which platform should I choose?",[15,1047,1048],{},"Choose the edge platform that matches your CDN, since running compute next to the cache is the point. Prefer isolate-based runtimes for low cold-start latency, and check the platform's cache API, streaming support and HTML rewriting capabilities against the patterns you need.",[998,1050,1052,1055],{"className":1051},[1001],[1003,1053,1054],{},"Can static site generation replace edge caching?",[15,1056,1057],{},"For pages that are the same for everyone, yes — prebuilt HTML on a CDN is the simplest fast option. Static generation does not address personalisation, variants or content that changes faster than you can rebuild. Edge caching with tag-based purging covers those cases while keeping static-like TTFB.",[998,1059,1061,1064],{"className":1060},[1001],[1003,1062,1063],{},"How do I keep cached shells consistent with fragments after a deploy?",[15,1065,1066],{},"Version both. Include a build identifier in the shell and send it with fragment requests; if a fragment endpoint from a newer release returns data the old shell cannot render, the mismatch is detectable and the edge can fall back to fetching a fresh shell. Purging shells by tag on deploy keeps the window short.",[146,1068,1070],{"id":1069},"guides-in-this-topic","Guides in This Topic",[158,1072,1073,1079,1085,1092],{},[161,1074,1075,1078],{},[19,1076,1077],{"href":471},"Personalizing cached pages at the edge"," — rewrite cached HTML per request with low-cardinality data.",[161,1080,1081,1084],{},[19,1082,1083],{"href":460},"Edge Side Includes vs client-side fragments"," — where to assemble personal pieces.",[161,1086,1087,1091],{},[19,1088,1090],{"href":1089},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fcaching-html-for-logged-in-users-safely\u002F","Caching HTML for logged-in users safely"," — the correctness rules.",[161,1093,1094,1098],{},[19,1095,1097],{"href":1096},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fusing-the-cache-api-in-cloudflare-workers\u002F","Using the Cache API in Cloudflare Workers"," — a concrete platform implementation.",[146,1100,1102],{"id":1101},"related","Related",[158,1104,1105,1112,1122,1129],{},[161,1106,1107,1111],{},[19,1108,1110],{"href":1109},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002F","CDN edge caching configuration"," — the cache rules edge compute builds on.",[161,1113,1114,1118,1119,1121],{},[19,1115,1117],{"href":1116},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002Fvary-header-pitfalls-that-destroy-cdn-hit-rate\u002F","Vary header pitfalls that destroy CDN hit rate"," — why ",[28,1120,175],{}," is the wrong tool.",[161,1123,1124,1128],{},[19,1125,1127],{"href":1126},"\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002F","Time to First Byte (TTFB) optimization"," — the metric this topic improves.",[161,1130,1131,1135],{},[19,1132,1134],{"href":1133},"\u002Fnetwork-protocol-optimization\u002Fstreaming-ssr-and-early-flush\u002F","Streaming SSR and early flush"," — streaming the cached shell immediately.",[1137,1138,1140],"script",{"type":1139},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Edge Compute & Dynamic Caching: Fast TTFB for Pages That Look Uncacheable\",\n  \"description\": \"How edge compute lets you cache pages that look uncacheable — personalised, logged-in, A\u002FB-tested — and serve them with a fast TTFB worldwide.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Environment Setup: Classify Every Byte of the Page\",\n      \"text\": \"For each template, mark regions as shared (identical for all users), variant (one of a few versions, chosen by a low-cardinality key such as country or experiment arm), or personal (unique per user).\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Capture a Baseline\",\n      \"text\": \"Record TTFB p75 and cache hit ratio per template and per request type (anonymous vs logged-in) from RUM with Server-Timing headers carrying cache status.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Isolate What Forces Bypass\",\n      \"text\": \"Usually one or two things push a whole page out of the cache: a session cookie read during rendering, a personalised header widget, or a variant decided at the origin.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Apply: Split, Key and Assemble\",\n      \"text\": \"The fixes, in order of how much traffic they usually recover:\"\n    }\n  ]\n}\n",[1137,1142,1143],{"type":1139},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Edge Compute & Dynamic Caching: Fast TTFB for Pages That Look Uncacheable\",\n  \"description\": \"How edge compute lets you cache pages that look uncacheable — personalised, logged-in, A\u002FB-tested — and serve them with a fast TTFB worldwide.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002F\"\n  }\n}\n",[1137,1145,1146],{"type":1139},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Advanced Caching Strategies & CDN Architecture\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Edge Compute & Dynamic Caching\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002F\"\n    }\n  ]\n}\n",[1148,1149,1150],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}",{"title":754,"searchDepth":767,"depth":767,"links":1152},[1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169],{"id":148,"depth":767,"text":149},{"id":281,"depth":767,"text":282},{"id":324,"depth":767,"text":325},{"id":344,"depth":767,"text":345},{"id":356,"depth":767,"text":357},{"id":430,"depth":767,"text":431},{"id":475,"depth":767,"text":476},{"id":705,"depth":767,"text":706},{"id":743,"depth":767,"text":744},{"id":933,"depth":767,"text":934},{"id":947,"depth":767,"text":948},{"id":963,"depth":767,"text":964},{"id":978,"depth":767,"text":979},{"id":988,"depth":767,"text":989},{"id":995,"depth":767,"text":996},{"id":1069,"depth":767,"text":1070},{"id":1101,"depth":767,"text":1102},"How edge compute lets you cache pages that look uncacheable — personalised, logged-in, A\u002FB-tested — and serve them with a fast TTFB worldwide.","md",{"slug":1173,"type":1174,"breadcrumb":1175,"datePublished":1182,"dateModified":1182},"edge-compute-and-dynamic-caching","topic",[1176,1179,1180],{"name":1177,"url":1178},"Home","\u002F",{"name":22,"url":21},{"name":5,"url":1181},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002F","2026-10-06",true,"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching",{"title":1186,"description":1187},"Edge Compute & Dynamic Caching for Fast TTFB","Cache dynamic and personalised HTML at the edge with Workers and edge functions: split cacheable shells from personal fragments and keep TTFB under 200ms.","advanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Findex","1Cl1t6NqzHbxzD_FAJ9qBagkeMOgjvqrGaFJX2co12I",[1191,1195],{"title":1192,"path":1193,"stem":1194,"children":-1},"Vary Header Pitfalls That Destroy CDN Hit Rate","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002Fvary-header-pitfalls-that-destroy-cdn-hit-rate","advanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002Fvary-header-pitfalls-that-destroy-cdn-hit-rate\u002Findex",{"title":1196,"path":1197,"stem":1198,"children":-1},"Caching HTML for Logged-In Users Safely","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fcaching-html-for-logged-in-users-safely","advanced-caching-strategies-cdn-architecture\u002Fedge-compute-and-dynamic-caching\u002Fcaching-html-for-logged-in-users-safely\u002Findex",1791308070326]