[{"data":1,"prerenderedAt":869},["ShallowReactive",2],{"navigation":3,"-guide-isr":57,"-guide-isr-surround":866},[4],{"title":5,"path":6,"stem":7,"children":8,"icon":16},"Guide","\u002Fguide","1.guide\u002F1.index",[9,12,17,22,27,32,37,42,47,52],{"title":10,"path":6,"stem":7,"icon":11},"Getting Started","lucide:rocket",{"title":13,"path":14,"stem":15,"icon":16},"API Reference","\u002Fguide\u002Fapi","1.guide\u002F10.api","lucide:book-open",{"title":18,"path":19,"stem":20,"icon":21},"Caching Functions","\u002Fguide\u002Ffunctions","1.guide\u002F2.functions","lucide:square-function",{"title":23,"path":24,"stem":25,"icon":26},"Invalidation & Expiration","\u002Fguide\u002Finvalidation","1.guide\u002F3.invalidation","lucide:trash-2",{"title":28,"path":29,"stem":30,"icon":31},"Storage","\u002Fguide\u002Fstorage","1.guide\u002F4.storage","lucide:database",{"title":33,"path":34,"stem":35,"icon":36},"Caching HTTP Handlers","\u002Fguide\u002Fhandler","1.guide\u002F5.handler","lucide:server",{"title":38,"path":39,"stem":40,"icon":41},"Query Parameters","\u002Fguide\u002Fquery-params","1.guide\u002F6.query-params","lucide:link-2",{"title":43,"path":44,"stem":45,"icon":46},"Cookies","\u002Fguide\u002Fcookies","1.guide\u002F7.cookies","lucide:cookie",{"title":48,"path":49,"stem":50,"icon":51},"Cache-Control & Eligibility","\u002Fguide\u002Fcache-control","1.guide\u002F8.cache-control","lucide:sliders-horizontal",{"title":53,"path":54,"stem":55,"icon":56},"Incremental Static Regeneration","\u002Fguide\u002Fisr","1.guide\u002F9.isr","lucide:refresh-cw",{"id":58,"title":53,"body":59,"description":77,"extension":861,"meta":862,"navigation":863,"path":54,"seo":864,"stem":55,"__hash__":865},"content\u002F1.guide\u002F9.isr.md",{"type":60,"value":61,"toc":856,"icon":56},"minimark",[62,71,257,260,299,313,326,331,359,515,527,553,557,582,793,797,852],[63,64,65,66,70],"p",{},"You can reproduce ISR-style behavior with ",[67,68,69],"code",{},"defineCachedHandler",": serve a cached page instantly, regenerate it in the background once it goes stale, and keep serving the last good version until the refresh lands.",[72,73,78],"pre",{"className":74,"code":75,"language":76,"meta":77,"style":77},"language-ts shiki shiki-themes github-light github-dark github-dark","const page = defineCachedHandler(\n  async (event) => {\n    const url = event.url ?? new URL(event.req.url);\n    const html = await renderPage(url);\n    return new Response(html, { headers: { \"content-type\": \"text\u002Fhtml\" } });\n  },\n  {\n    swr: true, \u002F\u002F serve stale instantly, refresh in the background\n    maxAge: 60, \u002F\u002F \"revalidate\" window: fresh for 60s, then refresh on next request\n    \u002F\u002F no staleMaxAge → the last good page is served until a refresh replaces it\n  },\n);\n","ts","",[67,79,80,104,126,152,171,198,204,210,226,240,246,251],{"__ignoreMap":77},[81,82,85,89,93,96,100],"span",{"class":83,"line":84},"line",1,[81,86,88],{"class":87},"so5gQ","const",[81,90,92],{"class":91},"suiK_"," page",[81,94,95],{"class":87}," =",[81,97,99],{"class":98},"shcOC"," defineCachedHandler",[81,101,103],{"class":102},"slsVL","(\n",[81,105,107,110,113,117,120,123],{"class":83,"line":106},2,[81,108,109],{"class":87},"  async",[81,111,112],{"class":102}," (",[81,114,116],{"class":115},"sQHwn","event",[81,118,119],{"class":102},") ",[81,121,122],{"class":87},"=>",[81,124,125],{"class":102}," {\n",[81,127,129,132,135,137,140,143,146,149],{"class":83,"line":128},3,[81,130,131],{"class":87},"    const",[81,133,134],{"class":91}," url",[81,136,95],{"class":87},[81,138,139],{"class":102}," event.url ",[81,141,142],{"class":87},"??",[81,144,145],{"class":87}," new",[81,147,148],{"class":98}," URL",[81,150,151],{"class":102},"(event.req.url);\n",[81,153,155,157,160,162,165,168],{"class":83,"line":154},4,[81,156,131],{"class":87},[81,158,159],{"class":91}," html",[81,161,95],{"class":87},[81,163,164],{"class":87}," await",[81,166,167],{"class":98}," renderPage",[81,169,170],{"class":102},"(url);\n",[81,172,174,177,179,182,185,189,192,195],{"class":83,"line":173},5,[81,175,176],{"class":87},"    return",[81,178,145],{"class":87},[81,180,181],{"class":98}," Response",[81,183,184],{"class":102},"(html, { headers: { ",[81,186,188],{"class":187},"sfrk1","\"content-type\"",[81,190,191],{"class":102},": ",[81,193,194],{"class":187},"\"text\u002Fhtml\"",[81,196,197],{"class":102}," } });\n",[81,199,201],{"class":83,"line":200},6,[81,202,203],{"class":102},"  },\n",[81,205,207],{"class":83,"line":206},7,[81,208,209],{"class":102},"  {\n",[81,211,213,216,219,222],{"class":83,"line":212},8,[81,214,215],{"class":102},"    swr: ",[81,217,218],{"class":91},"true",[81,220,221],{"class":102},", ",[81,223,225],{"class":224},"sCsY4","\u002F\u002F serve stale instantly, refresh in the background\n",[81,227,229,232,235,237],{"class":83,"line":228},9,[81,230,231],{"class":102},"    maxAge: ",[81,233,234],{"class":91},"60",[81,236,221],{"class":102},[81,238,239],{"class":224},"\u002F\u002F \"revalidate\" window: fresh for 60s, then refresh on next request\n",[81,241,243],{"class":83,"line":242},10,[81,244,245],{"class":224},"    \u002F\u002F no staleMaxAge → the last good page is served until a refresh replaces it\n",[81,247,249],{"class":83,"line":248},11,[81,250,203],{"class":102},[81,252,254],{"class":83,"line":253},12,[81,255,256],{"class":102},");\n",[63,258,259],{},"Two options make it ISR-like:",[261,262,263,277],"ul",{},[264,265,266,272,273,276],"li",{},[267,268,269],"strong",{},[67,270,271],{},"swr: true"," turns on stale-while-revalidate: once an entry is older than ",[67,274,275],{},"maxAge",", the next request gets the stale page immediately while a fresh render runs in the background.",[264,278,279,286,287,291,292,294,295,298],{},[267,280,281,282,285],{},"Omit ",[67,283,284],{},"staleMaxAge","."," This is the important part. Leaving it unset means there's no point at which an entry becomes \"too old to serve\" — the last successful render is served indefinitely until a refresh replaces it, exactly like ISR. If you instead ",[288,289,290],"em",{},"set"," ",[67,293,284],{},", you get a hard cutoff: after ",[67,296,297],{},"maxAge + staleMaxAge"," the entry is dropped and the next request blocks on a fresh render.",[63,300,301,302,305,306,308,309,312],{},"With this config the handler also emits ",[67,303,304],{},"Cache-Control: s-maxage=60, stale-while-revalidate",", so any shared\u002FCDN cache in front of it revalidates on the same schedule. (Set ",[67,307,284],{}," and the directive becomes ",[67,310,311],{},"s-maxage=60, stale-while-revalidate=\u003CstaleMaxAge>",".)",[314,315,316],"note",{},[63,317,318,319,322,323,285],{},"\nIf your handler sets its own ",[67,320,321],{},"Cache-Control",", ocache leaves it untouched and won't synthesize one. See ",[324,325,321],"a",{"href":49},[327,328,330],"h2",{"id":329},"on-demand-revalidation","On-demand revalidation",[63,332,333,334,336,337,221,340,343,344,347,348,351,352,355,356,358],{},"The handler returned by ",[67,335,69],{}," exposes ",[67,338,339],{},".expire()",[67,341,342],{},".invalidate()",", and ",[67,345,346],{},".resolveKeys()"," — the equivalent of Next's ",[67,349,350],{},"revalidatePath"," \u002F ",[67,353,354],{},"revalidateTag",". Each takes the request ",[67,357,116],{}," and derives the exact key the handler cached it under, so there's no key to reconstruct:",[72,360,362],{"className":74,"code":361,"language":76,"meta":77,"style":77},"const page = defineCachedHandler(\n  async (event) => {\n    const url = event.url ?? new URL(event.req.url);\n    return new Response(await renderPage(url), {\n      headers: { \"content-type\": \"text\u002Fhtml\" },\n    });\n  },\n  { swr: true, maxAge: 60 },\n);\n\n\u002F\u002F ISR-style: serve the stale page once more, refresh in the background\nawait page.expire(event);\n\n\u002F\u002F Hard purge: next request blocks on a fresh render\nawait page.invalidate(event);\n",[67,363,364,376,390,408,427,441,446,450,464,468,474,479,492,497,503],{"__ignoreMap":77},[81,365,366,368,370,372,374],{"class":83,"line":84},[81,367,88],{"class":87},[81,369,92],{"class":91},[81,371,95],{"class":87},[81,373,99],{"class":98},[81,375,103],{"class":102},[81,377,378,380,382,384,386,388],{"class":83,"line":106},[81,379,109],{"class":87},[81,381,112],{"class":102},[81,383,116],{"class":115},[81,385,119],{"class":102},[81,387,122],{"class":87},[81,389,125],{"class":102},[81,391,392,394,396,398,400,402,404,406],{"class":83,"line":128},[81,393,131],{"class":87},[81,395,134],{"class":91},[81,397,95],{"class":87},[81,399,139],{"class":102},[81,401,142],{"class":87},[81,403,145],{"class":87},[81,405,148],{"class":98},[81,407,151],{"class":102},[81,409,410,412,414,416,419,422,424],{"class":83,"line":154},[81,411,176],{"class":87},[81,413,145],{"class":87},[81,415,181],{"class":98},[81,417,418],{"class":102},"(",[81,420,421],{"class":87},"await",[81,423,167],{"class":98},[81,425,426],{"class":102},"(url), {\n",[81,428,429,432,434,436,438],{"class":83,"line":173},[81,430,431],{"class":102},"      headers: { ",[81,433,188],{"class":187},[81,435,191],{"class":102},[81,437,194],{"class":187},[81,439,440],{"class":102}," },\n",[81,442,443],{"class":83,"line":200},[81,444,445],{"class":102},"    });\n",[81,447,448],{"class":83,"line":206},[81,449,203],{"class":102},[81,451,452,455,457,460,462],{"class":83,"line":212},[81,453,454],{"class":102},"  { swr: ",[81,456,218],{"class":91},[81,458,459],{"class":102},", maxAge: ",[81,461,234],{"class":91},[81,463,440],{"class":102},[81,465,466],{"class":83,"line":228},[81,467,256],{"class":102},[81,469,470],{"class":83,"line":242},[81,471,473],{"emptyLinePlaceholder":472},true,"\n",[81,475,476],{"class":83,"line":248},[81,477,478],{"class":224},"\u002F\u002F ISR-style: serve the stale page once more, refresh in the background\n",[81,480,481,483,486,489],{"class":83,"line":253},[81,482,421],{"class":87},[81,484,485],{"class":102}," page.",[81,487,488],{"class":98},"expire",[81,490,491],{"class":102},"(event);\n",[81,493,495],{"class":83,"line":494},13,[81,496,473],{"emptyLinePlaceholder":472},[81,498,500],{"class":83,"line":499},14,[81,501,502],{"class":224},"\u002F\u002F Hard purge: next request blocks on a fresh render\n",[81,504,506,508,510,513],{"class":83,"line":505},15,[81,507,421],{"class":87},[81,509,485],{"class":102},[81,511,512],{"class":98},"invalidate",[81,514,491],{"class":102},[63,516,517,518,520,521,523,524,526],{},"Prefer ",[67,519,339],{}," for the ISR feel — there's no blocking gap for visitors. Reach for ",[67,522,342],{}," only when the next reader must get a guaranteed-fresh render. See ",[324,525,23],{"href":24}," for the underlying semantics.",[528,529,530],"tip",{},[63,531,532,533,536,537,351,540,543,544,552],{},"\nThese methods key off the event exactly as the handler does — including any ",[67,534,535],{},"varies"," headers and ",[67,538,539],{},"allowQuery",[67,541,542],{},"allowCookies"," values on the request. Pass an event that matches the variant you want to revalidate. For revalidation from somewhere without an event (a webhook, a cron job), the standalone ",[324,545,546,351,549],{"href":24},[67,547,548],{},"expireCache",[67,550,551],{},"invalidateCache"," helpers rebuild the key from options instead.",[327,554,556],{"id":555},"per-route-revalidate-windows","Per-route revalidate windows",[63,558,559,560,563,564,567,568,571,572,221,574,577,578,581],{},"If different pages need different refresh intervals (like Next's per-fetch ",[67,561,562],{},"revalidate","), use ",[67,565,566],{},"getMaxAge"," to derive the window from the response — for example an ",[67,569,570],{},"x-revalidate"," header your handler sets. Inside ",[67,573,566],{},[67,575,576],{},"entry.value"," is the standard ",[67,579,580],{},"Response",":",[72,583,585],{"className":74,"code":584,"language":76,"meta":77,"style":77},"const page = defineCachedHandler(\n  async (event) => {\n    const url = event.url ?? new URL(event.req.url);\n    const { html, revalidate } = await renderPage(url);\n    return new Response(html, {\n      headers: { \"content-type\": \"text\u002Fhtml\", \"x-revalidate\": String(revalidate) },\n    });\n  },\n  {\n    swr: true,\n    getMaxAge: (entry) => {\n      const revalidate = entry.value.headers.get(\"x-revalidate\");\n      return revalidate ? Number(revalidate) : 60; \u002F\u002F missing header → default window\n    },\n  },\n);\n",[67,586,587,599,613,631,657,668,691,695,699,703,712,729,751,779,784,788],{"__ignoreMap":77},[81,588,589,591,593,595,597],{"class":83,"line":84},[81,590,88],{"class":87},[81,592,92],{"class":91},[81,594,95],{"class":87},[81,596,99],{"class":98},[81,598,103],{"class":102},[81,600,601,603,605,607,609,611],{"class":83,"line":106},[81,602,109],{"class":87},[81,604,112],{"class":102},[81,606,116],{"class":115},[81,608,119],{"class":102},[81,610,122],{"class":87},[81,612,125],{"class":102},[81,614,615,617,619,621,623,625,627,629],{"class":83,"line":128},[81,616,131],{"class":87},[81,618,134],{"class":91},[81,620,95],{"class":87},[81,622,139],{"class":102},[81,624,142],{"class":87},[81,626,145],{"class":87},[81,628,148],{"class":98},[81,630,151],{"class":102},[81,632,633,635,638,641,643,645,648,651,653,655],{"class":83,"line":154},[81,634,131],{"class":87},[81,636,637],{"class":102}," { ",[81,639,640],{"class":91},"html",[81,642,221],{"class":102},[81,644,562],{"class":91},[81,646,647],{"class":102}," } ",[81,649,650],{"class":87},"=",[81,652,164],{"class":87},[81,654,167],{"class":98},[81,656,170],{"class":102},[81,658,659,661,663,665],{"class":83,"line":173},[81,660,176],{"class":87},[81,662,145],{"class":87},[81,664,181],{"class":98},[81,666,667],{"class":102},"(html, {\n",[81,669,670,672,674,676,678,680,683,685,688],{"class":83,"line":200},[81,671,431],{"class":102},[81,673,188],{"class":187},[81,675,191],{"class":102},[81,677,194],{"class":187},[81,679,221],{"class":102},[81,681,682],{"class":187},"\"x-revalidate\"",[81,684,191],{"class":102},[81,686,687],{"class":98},"String",[81,689,690],{"class":102},"(revalidate) },\n",[81,692,693],{"class":83,"line":206},[81,694,445],{"class":102},[81,696,697],{"class":83,"line":212},[81,698,203],{"class":102},[81,700,701],{"class":83,"line":228},[81,702,209],{"class":102},[81,704,705,707,709],{"class":83,"line":242},[81,706,215],{"class":102},[81,708,218],{"class":91},[81,710,711],{"class":102},",\n",[81,713,714,717,720,723,725,727],{"class":83,"line":248},[81,715,716],{"class":98},"    getMaxAge",[81,718,719],{"class":102},": (",[81,721,722],{"class":115},"entry",[81,724,119],{"class":102},[81,726,122],{"class":87},[81,728,125],{"class":102},[81,730,731,734,737,739,742,745,747,749],{"class":83,"line":253},[81,732,733],{"class":87},"      const",[81,735,736],{"class":91}," revalidate",[81,738,95],{"class":87},[81,740,741],{"class":102}," entry.value.headers.",[81,743,744],{"class":98},"get",[81,746,418],{"class":102},[81,748,682],{"class":187},[81,750,256],{"class":102},[81,752,753,756,759,762,765,768,770,773,776],{"class":83,"line":494},[81,754,755],{"class":87},"      return",[81,757,758],{"class":102}," revalidate ",[81,760,761],{"class":87},"?",[81,763,764],{"class":98}," Number",[81,766,767],{"class":102},"(revalidate) ",[81,769,581],{"class":87},[81,771,772],{"class":91}," 60",[81,774,775],{"class":102},"; ",[81,777,778],{"class":224},"\u002F\u002F missing header → default window\n",[81,780,781],{"class":83,"line":499},[81,782,783],{"class":102},"    },\n",[81,785,786],{"class":83,"line":505},[81,787,203],{"class":102},[81,789,791],{"class":83,"line":790},16,[81,792,256],{"class":102},[327,794,796],{"id":795},"differences-from-cdn-managed-isr","Differences from CDN-managed ISR",[314,798,799,802],{},[63,800,801],{},"\nThree things differ from CDN-managed ISR.",[803,804,805,815,830],"ol",{},[264,806,807,810,811,814],{},[267,808,809],{},"Background refresh is coalesced per instance",", not globally — across multiple servers \u002F serverless instances the origin can see one refresh per instance. Add a distributed lock in your ",[324,812,813],{"href":29},"custom storage"," if regeneration is expensive.",[264,816,817,820,821,823,824,826,827,829],{},[267,818,819],{},"Entries never auto-expire"," with ",[67,822,284],{}," omitted, so storage grows until you purge with ",[67,825,551],{}," — or set a large ",[67,828,284],{}," to trade exact ISR semantics for eventual cleanup.",[264,831,832,835,836,838,839,841,842,844,845,851],{},[267,833,834],{},"A browser\u002FCDN can still serve stale after a purge"," — ",[67,837,551],{},"\u002F",[67,840,548],{}," only clear server-side storage; a client or CDN that already cached the synthesized ",[67,843,321],{}," response keeps serving it, unaware of the purge. Set ",[324,846,848],{"href":847},"\u002Fguide\u002Fcache-control#server-only-caching-sendcachecontrol",[67,849,850],{},"sendCacheControl: false"," to keep caching decisions entirely server-side.",[853,854,855],"style",{},"html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}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);}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);}",{"title":77,"searchDepth":106,"depth":106,"links":857},[858,859,860],{"id":329,"depth":106,"text":330},{"id":555,"depth":106,"text":556},{"id":795,"depth":106,"text":796},"md",{"icon":56},{"icon":56},{"title":53,"description":77},"4bnpXAyhnLV_evjxWUcBJJXV0_TJXyfiikNk0lt8QJU",[867,868],{"title":48,"path":49,"stem":50,"description":77,"icon":51,"children":-1},null,1783601179227]