[{"data":1,"prerenderedAt":1117},["ShallowReactive",2],{"content:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fenabling-navigation-preload-in-service-workers":3,"surroundings:\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fenabling-navigation-preload-in-service-workers":1108},{"id":4,"title":5,"body":6,"description":1088,"extension":1089,"meta":1090,"navigation":1102,"path":1103,"seo":1104,"stem":1106,"__hash__":1107},"content\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fenabling-navigation-preload-in-service-workers\u002Findex.md","Enabling Navigation Preload in Service Workers",{"type":7,"value":8,"toc":1070},"minimark",[9,14,34,41,182,187,230,234,240,246,252,258,331,335,340,459,463,658,661,665,680,684,702,845,849,855,859,865,869,886,890,893,897,928,932,942,948,954,963,967,979,988,1001,1010,1019,1028,1032,1055,1060,1063,1066],[10,11,13],"h1",{"id":12},"how-to-enable-navigation-preload-in-service-workers","How to Enable Navigation Preload in Service Workers",[15,16,17,18,23,24,28,29,33],"p",{},"This guide addresses a hidden cost in ",[19,20,22],"a",{"href":21},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002F","Service Worker Caching Strategies",", part of ",[19,25,27],{"href":26},"\u002Fadvanced-caching-strategies-cdn-architecture\u002F","Advanced Caching Strategies & CDN Architecture",". When a page is controlled by a service worker, every navigation request is routed through the worker's ",[30,31,32],"code",{},"fetch"," handler. If the worker is not already running, the browser must start it first — load its script, evaluate it, run it — before the handler can even begin the network request. On mobile devices this boot commonly takes 50–200ms, and several hundred milliseconds on slow devices or after the browser has been idle.",[15,35,36,37,40],{},"For cache-first navigations that cost is offset by skipping the network. For network-first navigations — the right strategy for HTML that must stay fresh — it is pure delay added to TTFB and therefore to LCP. Navigation preload fixes it: the browser starts the navigation request immediately, in parallel with booting the worker, and the handler receives the in-flight response via ",[30,38,39],{},"event.preloadResponse",".",[15,42,43],{},[44,45,51,52,51,59,51,63,51,66,51,75,51,81,51,86,51,96,51,103,51,109,51,113,51,117,51,121,51,124,51,128,51,131,51,136,51,143,51,149,51,153,51,157,51,161,51,165,51,169,51,173,51,177,51],"svg",{"viewBox":46,"width":47,"role":48,"ariaLabel":49,"style":50},"0 0 760 242","100%","img","Two timelines comparing a navigation where the network request waits for service worker boot with one where navigation preload starts the request in parallel.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[53,54],"rect",{"className":55,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],"svg-canvas","0","#ffffff",[60,61,62],"title",{},"Network-first navigation with and without navigation preload",[64,65,49],"desc",{},[53,67],{"x":68,"y":68,"width":69,"height":70,"rx":71,"fill":72,"stroke":73,"style":74},"1","758","240","10","none","currentColor","stroke-opacity:0.18",[76,77,62],"text",{"x":78,"y":79,"fill":73,"style":80},"28.0","34.0","font-size:16px;font-weight:700",[76,82,85],{"x":78,"y":83,"fill":73,"style":84},"74.0","font-size:12.5px;font-weight:700","Without",[53,87],{"x":88,"y":89,"width":90,"height":91,"rx":92,"fill":93,"stroke":94,"style":95},"134.9","56.0","150.7","26.0","3","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[76,97,102],{"x":98,"y":99,"fill":73,"style":100,"textAnchor":101},"210.3","73.5","font-size:11.5px","middle","SW boot",[53,104],{"x":105,"y":89,"width":106,"height":91,"rx":92,"fill":107,"stroke":107,"style":108},"287.1","319.8","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[76,110,112],{"x":111,"y":99,"fill":73,"style":100,"textAnchor":101},"447.0","network request",[76,114,116],{"x":78,"y":115,"fill":73,"style":84},"112.0","With preload",[53,118],{"x":88,"y":119,"width":90,"height":91,"rx":92,"fill":73,"stroke":73,"style":120},"94.0","fill-opacity:0.06;stroke-opacity:0.4",[76,122,102],{"x":98,"y":123,"fill":73,"style":100,"textAnchor":101},"111.5",[76,125,127],{"x":78,"y":126,"fill":73,"style":84},"150.0","Preload request",[53,129],{"x":88,"y":130,"width":106,"height":91,"rx":92,"fill":107,"stroke":107,"style":108},"132.0",[76,132,135],{"x":133,"y":134,"fill":73,"style":100,"textAnchor":101},"294.8","149.5","request starts at once",[137,138],"line",{"x1":139,"y1":140,"x2":141,"y2":140,"stroke":73,"style":142},"134.2","172.0","726.0","stroke-opacity:0.4",[76,144,148],{"x":145,"y":146,"fill":147,"style":100,"textAnchor":101},"144.9","189.0","#51617a","0ms",[76,150,152],{"x":151,"y":146,"fill":147,"style":100,"textAnchor":101},"218.7","100ms",[76,154,156],{"x":155,"y":146,"fill":147,"style":100,"textAnchor":101},"303.3","200ms",[76,158,160],{"x":159,"y":146,"fill":147,"style":100,"textAnchor":101},"387.8","300ms",[76,162,164],{"x":163,"y":146,"fill":147,"style":100,"textAnchor":101},"472.4","400ms",[76,166,168],{"x":167,"y":146,"fill":147,"style":100,"textAnchor":101},"556.9","500ms",[76,170,172],{"x":171,"y":146,"fill":147,"style":100,"textAnchor":101},"641.5","600ms",[76,174,176],{"x":175,"y":146,"fill":147,"style":100,"textAnchor":101},"715.3","700ms",[76,178,181],{"x":78,"y":179,"fill":147,"style":180},"220.0","font-size:12.5px","With preload, boot and the network request overlap, so TTFB no longer includes the worker's startup time.",[183,184,186],"h2",{"id":185},"rapid-diagnosis","Rapid Diagnosis",[188,189,190,202,215,221],"ul",{},[191,192,193,197,198,201],"li",{},[194,195,196],"strong",{},"Check whether navigations are network-first."," If the fetch handler calls ",[30,199,200],{},"fetch(event.request)"," for navigations, they pay boot time.",[191,203,204,207,208,211,212,40],{},[194,205,206],{},"Measure worker startup."," In DevTools → Application → Service Workers, or in a trace, look for \"ServiceWorker startup\" before navigation requests. Navigation Timing's ",[30,209,210],{},"workerStart"," shows when the worker began; compare it with ",[30,213,214],{},"fetchStart",[191,216,217,220],{},[194,218,219],{},"Compare TTFB with and without the worker."," Test with \"Bypass for network\" enabled: if TTFB improves noticeably, the worker is adding delay.",[191,222,223,226,227,40],{},[194,224,225],{},"Check for preload support code."," Search for ",[30,228,229],{},"navigationPreload.enable()",[183,231,233],{"id":232},"root-cause-analysis","Root Cause Analysis",[15,235,236,239],{},[194,237,238],{},"1. Worker boot on the critical path."," A stopped worker must start before handling the navigation; the browser stops idle workers after short periods.",[15,241,242,245],{},[194,243,244],{},"2. Network-first strategies."," They need the network anyway, so waiting for the worker before starting the request is wasted time.",[15,247,248,251],{},[194,249,250],{},"3. Heavy worker scripts."," Large worker bundles (importing big libraries, large precache manifests) take longer to evaluate.",[15,253,254,257],{},[194,255,256],{},"4. Slow devices."," Boot time scales with device CPU and storage speed.",[15,259,260],{},[44,261,51,264,51,267,51,270,51,272,51,275,51,277,51,284,51,290,51,295,51,299,51,304,51,308,51,311,51,315,51,319,51,323,51,327,51],{"viewBox":262,"width":47,"role":48,"ariaLabel":263,"style":50},"0 0 760 194","Bar chart of time added to navigation TTFB by service worker startup on three device classes, and with navigation preload.",[53,265],{"className":266,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,268,269],{},"TTFB added by service worker boot (p75)",[64,271,263],{},[53,273],{"x":68,"y":68,"width":69,"height":274,"rx":71,"fill":72,"stroke":73,"style":74},"192",[76,276,269],{"x":78,"y":79,"fill":73,"style":80},[76,278,283],{"x":279,"y":280,"fill":73,"style":281,"textAnchor":282},"169.7","70.0","font-size:13px","end","Desktop",[53,285],{"x":286,"y":89,"width":287,"height":288,"rx":92,"fill":107,"stroke":107,"style":289},"181.7","49.9","19","fill-opacity:0.55;stroke-opacity:0.9",[76,291,294],{"x":292,"y":280,"fill":73,"style":293},"237.5","font-size:12px;font-weight:600","40ms",[76,296,298],{"x":279,"y":297,"fill":73,"style":281,"textAnchor":282},"101.0","Mid-tier Android",[53,300],{"x":286,"y":301,"width":302,"height":288,"rx":92,"fill":93,"stroke":94,"style":303},"87.0","199.5","fill-opacity:0.7;stroke-opacity:0.9",[76,305,307],{"x":306,"y":297,"fill":73,"style":293},"387.2","160ms",[76,309,310],{"x":279,"y":130,"fill":73,"style":281,"textAnchor":282},"Low-end Android",[53,312],{"x":286,"y":313,"width":314,"height":288,"rx":92,"fill":93,"stroke":94,"style":303},"118.0","486.3",[76,316,318],{"x":317,"y":130,"fill":73,"style":293},"674.0","390ms",[76,320,322],{"x":279,"y":321,"fill":73,"style":281,"textAnchor":282},"163.0","Any device, with preload",[53,324],{"x":286,"y":325,"width":326,"height":288,"rx":92,"fill":107,"stroke":107,"style":289},"149.0","6.2",[76,328,330],{"x":329,"y":321,"fill":73,"style":293},"193.9","5ms",[183,332,334],{"id":333},"step-by-step-resolution","Step-by-Step Resolution",[336,337,339],"h3",{"id":338},"_1-enable-navigation-preload-on-activate","1. Enable navigation preload on activate",[341,342,347],"pre",{"className":343,"code":344,"language":345,"meta":346,"style":346},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","self.addEventListener('activate', (event) => {\n  event.waitUntil((async () => {\n    if (self.registration.navigationPreload) await self.registration.navigationPreload.enable();\n  })());\n});\n\u002F\u002F trade-off: once enabled, the browser sends a preload request for EVERY\n\u002F\u002F navigation. If your handler serves some navigations from cache without\n\u002F\u002F using preloadResponse, those preload requests are wasted traffic.\n","javascript","",[30,348,349,385,407,428,434,440,447,453],{"__ignoreMap":346},[350,351,353,357,361,364,368,371,375,378,382],"span",{"class":137,"line":352},1,[350,354,356],{"class":355},"saISM","self.",[350,358,360],{"class":359},"smZ65","addEventListener",[350,362,363],{"class":355},"(",[350,365,367],{"class":366},"sZ8jY","'activate'",[350,369,370],{"class":355},", (",[350,372,374],{"class":373},"sQw3B","event",[350,376,377],{"class":355},") ",[350,379,381],{"class":380},"sPARh","=>",[350,383,384],{"class":355}," {\n",[350,386,388,391,394,397,400,403,405],{"class":137,"line":387},2,[350,389,390],{"class":355},"  event.",[350,392,393],{"class":359},"waitUntil",[350,395,396],{"class":355},"((",[350,398,399],{"class":380},"async",[350,401,402],{"class":355}," () ",[350,404,381],{"class":380},[350,406,384],{"class":355},[350,408,410,413,416,419,422,425],{"class":137,"line":409},3,[350,411,412],{"class":380},"    if",[350,414,415],{"class":355}," (self.registration.navigationPreload) ",[350,417,418],{"class":380},"await",[350,420,421],{"class":355}," self.registration.navigationPreload.",[350,423,424],{"class":359},"enable",[350,426,427],{"class":355},"();\n",[350,429,431],{"class":137,"line":430},4,[350,432,433],{"class":355},"  })());\n",[350,435,437],{"class":137,"line":436},5,[350,438,439],{"class":355},"});\n",[350,441,443],{"class":137,"line":442},6,[350,444,446],{"class":445},"sjfSM","\u002F\u002F trade-off: once enabled, the browser sends a preload request for EVERY\n",[350,448,450],{"class":137,"line":449},7,[350,451,452],{"class":445},"\u002F\u002F navigation. If your handler serves some navigations from cache without\n",[350,454,456],{"class":137,"line":455},8,[350,457,458],{"class":445},"\u002F\u002F using preloadResponse, those preload requests are wasted traffic.\n",[336,460,462],{"id":461},"_2-use-the-preload-response-in-the-fetch-handler","2. Use the preload response in the fetch handler",[341,464,466],{"className":343,"code":465,"language":345,"meta":346,"style":346},"self.addEventListener('fetch', (event) => {\n  if (event.request.mode !== 'navigate') return;\n  event.respondWith((async () => {\n    try {\n      const preloaded = await event.preloadResponse;\n      if (preloaded) return preloaded;\n      return await fetch(event.request);\n    } catch {\n      return (await caches.match('\u002Foffline.html')) ?? Response.error();\n    }\n  })());\n});\n\u002F\u002F trade-off: if you forget to await event.preloadResponse, the browser logs a\n\u002F\u002F warning and the preload is cancelled — you get the extra request without the\n\u002F\u002F benefit. Always consume it, even if you end up using the cache.\n",[30,467,468,489,511,528,535,553,566,579,589,624,630,635,640,646,652],{"__ignoreMap":346},[350,469,470,472,474,476,479,481,483,485,487],{"class":137,"line":352},[350,471,356],{"class":355},[350,473,360],{"class":359},[350,475,363],{"class":355},[350,477,478],{"class":366},"'fetch'",[350,480,370],{"class":355},[350,482,374],{"class":373},[350,484,377],{"class":355},[350,486,381],{"class":380},[350,488,384],{"class":355},[350,490,491,494,497,500,503,505,508],{"class":137,"line":387},[350,492,493],{"class":380},"  if",[350,495,496],{"class":355}," (event.request.mode ",[350,498,499],{"class":380},"!==",[350,501,502],{"class":366}," 'navigate'",[350,504,377],{"class":355},[350,506,507],{"class":380},"return",[350,509,510],{"class":355},";\n",[350,512,513,515,518,520,522,524,526],{"class":137,"line":409},[350,514,390],{"class":355},[350,516,517],{"class":359},"respondWith",[350,519,396],{"class":355},[350,521,399],{"class":380},[350,523,402],{"class":355},[350,525,381],{"class":380},[350,527,384],{"class":355},[350,529,530,533],{"class":137,"line":430},[350,531,532],{"class":380},"    try",[350,534,384],{"class":355},[350,536,537,540,544,547,550],{"class":137,"line":436},[350,538,539],{"class":380},"      const",[350,541,543],{"class":542},"sPXB4"," preloaded",[350,545,546],{"class":380}," =",[350,548,549],{"class":380}," await",[350,551,552],{"class":355}," event.preloadResponse;\n",[350,554,555,558,561,563],{"class":137,"line":442},[350,556,557],{"class":380},"      if",[350,559,560],{"class":355}," (preloaded) ",[350,562,507],{"class":380},[350,564,565],{"class":355}," preloaded;\n",[350,567,568,571,573,576],{"class":137,"line":449},[350,569,570],{"class":380},"      return",[350,572,549],{"class":380},[350,574,575],{"class":359}," fetch",[350,577,578],{"class":355},"(event.request);\n",[350,580,581,584,587],{"class":137,"line":455},[350,582,583],{"class":355},"    } ",[350,585,586],{"class":380},"catch",[350,588,384],{"class":355},[350,590,592,594,597,599,602,605,607,610,613,616,619,622],{"class":137,"line":591},9,[350,593,570],{"class":380},[350,595,596],{"class":355}," (",[350,598,418],{"class":380},[350,600,601],{"class":355}," caches.",[350,603,604],{"class":359},"match",[350,606,363],{"class":355},[350,608,609],{"class":366},"'\u002Foffline.html'",[350,611,612],{"class":355},")) ",[350,614,615],{"class":380},"??",[350,617,618],{"class":355}," Response.",[350,620,621],{"class":359},"error",[350,623,427],{"class":355},[350,625,627],{"class":137,"line":626},10,[350,628,629],{"class":355},"    }\n",[350,631,633],{"class":137,"line":632},11,[350,634,433],{"class":355},[350,636,638],{"class":137,"line":637},12,[350,639,439],{"class":355},[350,641,643],{"class":137,"line":642},13,[350,644,645],{"class":445},"\u002F\u002F trade-off: if you forget to await event.preloadResponse, the browser logs a\n",[350,647,649],{"class":137,"line":648},14,[350,650,651],{"class":445},"\u002F\u002F warning and the preload is cancelled — you get the extra request without the\n",[350,653,655],{"class":137,"line":654},15,[350,656,657],{"class":445},"\u002F\u002F benefit. Always consume it, even if you end up using the cache.\n",[15,659,660],{},"Expected outcome: navigation TTFB matches the uncontrolled network path; offline fallback still works.",[336,662,664],{"id":663},"_3-tell-the-server-it-is-a-preload-optional","3. Tell the server it is a preload (optional)",[15,666,667,668,671,672,675,676,679],{},"Preload requests carry a ",[30,669,670],{},"Service-Worker-Navigation-Preload"," header (default value ",[30,673,674],{},"true",", configurable with ",[30,677,678],{},"setHeaderValue","). The server can use it to return a lighter response (for example, only the content portion for app-shell architectures) — but be careful with caching: vary on the header if responses differ.",[336,681,683],{"id":682},"_4-with-workbox-use-its-built-in-support","4. With Workbox, use its built-in support",[15,685,686,687,689,690,693,694,697,698,701],{},"Workbox's ",[30,688,229],{}," plus ",[30,691,692],{},"NetworkFirst"," or ",[30,695,696],{},"NetworkOnly"," for navigations uses ",[30,699,700],{},"preloadResponse"," automatically.",[15,703,704],{},[44,705,51,708,51,711,51,714,51,716,51,733,51,736,51,738,51,743,51,749,51,753,51,756,51,761,51,769,51,775,51,779,51,784,51,787,51,791,51,795,51,797,51,801,51,804,51,806,51,809,51,812,51,816,51,820,51,822,51,825,51,828,51,831,51,835,51,838,51,841,51],{"viewBox":706,"width":47,"role":48,"ariaLabel":707,"style":50},"0 0 760 359","Decision sequence for whether a service worker benefits from navigation preload.",[53,709],{"className":710,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,712,713],{},"Do you need navigation preload?",[64,715,707],{},[717,718,719],"defs",{},[720,721,728],"marker",{"id":722,"viewBox":723,"refX":724,"refY":725,"markerWidth":726,"markerHeight":726,"orient":727},"fa1289c027","0 0 10 10","9","5","7","auto-start-reverse",[729,730],"path",{"d":731,"fill":73,"style":732},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[53,734],{"x":68,"y":68,"width":69,"height":735,"rx":71,"fill":72,"stroke":73,"style":74},"357",[76,737,713],{"x":78,"y":79,"fill":73,"style":80},[53,739],{"x":78,"y":89,"width":740,"height":741,"rx":742,"fill":73,"stroke":73,"style":120},"320.0","52.0","6",[76,744,748],{"x":745,"y":746,"fill":73,"style":747,"textAnchor":101},"188.0","78.0","font-size:13px;font-weight:700","Navigations are served network-first or",[76,750,752],{"x":745,"y":751,"fill":73,"style":747,"textAnchor":101},"95.0","network-only?",[53,754],{"x":755,"y":89,"width":740,"height":741,"rx":742,"fill":107,"stroke":107,"style":108},"412.0",[76,757,760],{"x":758,"y":759,"fill":73,"style":180,"textAnchor":101},"572.0","86.5","Yes — enable preload",[137,762],{"x1":763,"y1":764,"x2":765,"y2":764,"stroke":73,"strokeWidth":766,"style":767,"markerEnd":768},"348.0","82.0","410.0","1.5","stroke-opacity:0.6","url(#fa1289c027)",[76,770,774],{"x":771,"y":772,"fill":107,"style":773,"textAnchor":101},"380.0","75.0","font-size:11.5px;font-weight:700","yes",[137,776],{"x1":745,"y1":777,"x2":745,"y2":778,"stroke":73,"strokeWidth":766,"style":767,"markerEnd":768},"108.0","142.0",[76,780,783],{"x":781,"y":782,"fill":147,"style":773},"196.0","130.0","no",[53,785],{"x":78,"y":786,"width":740,"height":741,"rx":742,"fill":73,"stroke":73,"style":120},"144.0",[76,788,790],{"x":745,"y":789,"fill":73,"style":747,"textAnchor":101},"166.0","Navigations are cache-first or",[76,792,794],{"x":745,"y":793,"fill":73,"style":747,"textAnchor":101},"183.0","stale-while-revalidate?",[53,796],{"x":755,"y":786,"width":740,"height":741,"rx":742,"fill":107,"stroke":107,"style":108},[76,798,800],{"x":758,"y":799,"fill":73,"style":180,"textAnchor":101},"174.5","Usually no, unless you also revalidate from network",[137,802],{"x1":763,"y1":803,"x2":765,"y2":803,"stroke":73,"strokeWidth":766,"style":767,"markerEnd":768},"170.0",[76,805,774],{"x":771,"y":321,"fill":107,"style":773,"textAnchor":101},[137,807],{"x1":745,"y1":781,"x2":745,"y2":808,"stroke":73,"strokeWidth":766,"style":767,"markerEnd":768},"230.0",[76,810,783],{"x":781,"y":811,"fill":147,"style":773},"218.0",[53,813],{"x":78,"y":814,"width":740,"height":815,"rx":742,"fill":73,"stroke":73,"style":120},"232.0","35.0",[76,817,819],{"x":745,"y":818,"fill":73,"style":747,"textAnchor":101},"254.0","No fetch handler for navigations at all?",[53,821],{"x":755,"y":814,"width":740,"height":815,"rx":742,"fill":107,"stroke":107,"style":108},[76,823,824],{"x":758,"y":818,"fill":73,"style":180,"textAnchor":101},"No — no boot delay (browser skips the worker)",[137,826],{"x1":763,"y1":827,"x2":765,"y2":827,"stroke":73,"strokeWidth":766,"style":767,"markerEnd":768},"249.5",[76,829,774],{"x":771,"y":830,"fill":107,"style":773,"textAnchor":101},"242.5",[137,832],{"x1":745,"y1":833,"x2":745,"y2":834,"stroke":73,"strokeWidth":766,"style":767,"markerEnd":768},"267.0","301.0",[76,836,783],{"x":781,"y":837,"fill":147,"style":773},"289.0",[53,839],{"x":78,"y":840,"width":740,"height":79,"rx":742,"fill":93,"stroke":94,"style":95},"303.0",[76,842,844],{"x":745,"y":843,"fill":73,"style":180,"textAnchor":101},"324.5","Measure workerStart vs fetchStart to decide",[183,846,848],{"id":847},"verification","Verification",[15,850,851,852,854],{},"In DevTools, navigation requests should show \"(navigation preload)\" or appear as preload requests in the Network panel, starting before the worker's fetch handler runs. Compare TTFB in RUM for controlled navigations before and after: the gap between controlled and uncontrolled navigations should shrink to near zero. Check server logs for the ",[30,853,670],{}," header to confirm preloads are sent.",[183,856,858],{"id":857},"worked-example-a-news-pwa","Worked Example: A News PWA",[15,860,861,862,864],{},"A news PWA used a network-first strategy for article navigations to keep content fresh, with an offline fallback. RUM showed that navigations controlled by the worker had TTFB 210ms higher at p75 on Android than uncontrolled ones — almost entirely worker boot. Enabling navigation preload and consuming ",[30,863,700],{}," eliminated the gap; LCP p75 for returning visitors improved by about 190ms, and offline behaviour was unchanged.",[183,866,868],{"id":867},"monitoring-worker-overhead-in-the-field","Monitoring Worker Overhead in the Field",[15,870,871,872,874,875,878,879,882,883,885],{},"Navigation Timing exposes ",[30,873,210],{},", which records when the service worker began handling the navigation (or started booting). Send ",[30,876,877],{},"fetchStart - workerStart"," and ",[30,880,881],{},"responseStart - fetchStart"," with your RUM beacon for controlled navigations, segmented by device class. The first measures boot overhead; the second network time. After enabling preload, boot overhead should no longer add to TTFB — if it still does, check that the handler awaits ",[30,884,700],{}," and that preload is enabled for the active registration.",[183,887,889],{"id":888},"combining-preload-with-cached-fallbacks","Combining Preload With Cached Fallbacks",[15,891,892],{},"Navigation preload pairs naturally with a race between network and cache. For pages where a slightly stale version is acceptable, start from the preload response but fall back to the cached copy if the network has not answered within a short timeout; when the preload response does arrive, store it in the cache for next time. That gives fresh content on good connections, fast cached content on slow ones, and an offline fallback when there is no connection at all — all without waiting for the worker to boot before the network request starts.",[183,894,896],{"id":895},"common-mistakes","Common Mistakes",[188,898,899,907,913,922],{},[191,900,901,906],{},[194,902,903,904,40],{},"Enabling preload but not using ",[30,905,700],{}," Doubles requests for no gain.",[191,908,909,912],{},[194,910,911],{},"Using preload with cache-first navigations."," Wasted network requests for responses you never use.",[191,914,915,918,919,40],{},[194,916,917],{},"Ignoring the header in caching."," If the server returns different content for preload requests, add ",[30,920,921],{},"Vary: Service-Worker-Navigation-Preload",[191,923,924,927],{},[194,925,926],{},"A huge worker script."," Preload hides boot time for navigations, but a heavy worker still costs CPU and delays other intercepted requests.",[183,929,931],{"id":930},"edge-cases","Edge Cases",[15,933,934,937,938,941],{},[194,935,936],{},"Static Routing API."," Newer Chromium versions support declaring routes the worker should not intercept (",[30,939,940],{},"registerRouter"," in install), letting navigations bypass the worker entirely when it adds nothing.",[15,943,944,947],{},[194,945,946],{},"Redirects."," Preload responses follow redirects like normal navigations; check that redirect handling in the worker does not discard the preload.",[15,949,950,953],{},[194,951,952],{},"POST navigations."," Preload only applies to GET navigations.",[15,955,956,959,960,40],{},[194,957,958],{},"Browser support."," Navigation preload is supported in Chromium, Firefox and Safari 15.4+; feature-detect with ",[30,961,962],{},"self.registration.navigationPreload",[183,964,966],{"id":965},"faq","FAQ",[968,969,972,976],"details",{"className":970},[971],"faq-item",[973,974,975],"summary",{},"Why not just keep the service worker running?",[15,977,978],{},"Browsers stop idle workers to save memory and battery; you cannot keep them alive. Navigation preload is the designed solution for the boot cost.",[968,980,982,985],{"className":981},[971],[973,983,984],{},"Does preload help subresource requests?",[15,986,987],{},"No — it applies only to navigation requests. Subresources requested after the page loads are handled by an already-running worker, so boot time is not an issue for them.",[968,989,991,994],{"className":990},[971],[973,992,993],{},"Is the preload request cached by the HTTP cache?",[15,995,996,997,1000],{},"It behaves like a normal navigation request with respect to HTTP caching, so ",[30,998,999],{},"Cache-Control"," on HTML still applies. Most HTML policies revalidate, so it usually reaches the CDN.",[968,1002,1004,1007],{"className":1003},[971],[973,1005,1006],{},"Should cache-first apps disable preload?",[15,1008,1009],{},"Yes, if no navigation uses the network response. If you use stale-while-revalidate for navigations (serve cache, update from network), preload can supply the network half.",[968,1011,1013,1016],{"className":1012},[971],[973,1014,1015],{},"Can the server respond faster to preload requests?",[15,1017,1018],{},"It can return a partial response (content only) when the header is present, which app-shell architectures use to save bytes. Ensure caches distinguish the two response types.",[968,1020,1022,1025],{"className":1021},[971],[973,1023,1024],{},"How do I test boot time locally?",[15,1026,1027],{},"Stop the worker in DevTools (Application → Service Workers → stop) before navigating, with CPU throttling enabled. The navigation then pays the full boot cost, as it often does for real users.",[183,1029,1031],{"id":1030},"related","Related",[188,1033,1034,1041,1048],{},[191,1035,1036,1040],{},[19,1037,1039],{"href":1038},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Favoiding-the-service-worker-startup-penalty\u002F","Avoiding the service worker startup penalty"," — the broader startup problem.",[191,1042,1043,1047],{},[19,1044,1046],{"href":1045},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Foffline-fallback-pages-with-workbox\u002F","Offline fallback pages with Workbox"," — keeping the offline path with network-first.",[191,1049,1050,1054],{},[19,1051,1053],{"href":1052},"\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Fdiagnosing-slow-ttfb-with-server-timing\u002F","Diagnosing slow TTFB with Server-Timing"," — separating worker delay from server delay.",[1056,1057,1059],"script",{"type":1058},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Enable Navigation Preload in Service Workers\",\n  \"description\": \"How service worker boot time delays network-first navigations, and how navigation preload removes that delay without giving up offline support.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Enable navigation preload on activate\",\n      \"text\": \"Enable navigation preload on activate\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Use the preload response in the fetch handler\",\n      \"text\": \"Expected outcome: navigation TTFB matches the uncontrolled network path; offline fallback still works.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Tell the server it is a preload (optional)\",\n      \"text\": \"Preload requests carry a Service-Worker-Navigation-Preload header (default value true, configurable with setHeaderValue).\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"With Workbox, use its built-in support\",\n      \"text\": \"Workbox's navigationPreload.enable() plus NetworkFirst or NetworkOnly for navigations uses preloadResponse automatically.\"\n    }\n  ]\n}\n",[1056,1061,1062],{"type":1058},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Enable Navigation Preload in Service Workers\",\n  \"description\": \"How service worker boot time delays network-first navigations, and how navigation preload removes that delay without giving up offline support.\",\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\u002Fservice-worker-caching-strategies\u002Fenabling-navigation-preload-in-service-workers\u002F\"\n  }\n}\n",[1056,1064,1065],{"type":1058},"\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\": \"Service Worker Caching Strategies\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Enabling Navigation Preload in Service Workers\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fenabling-navigation-preload-in-service-workers\u002F\"\n    }\n  ]\n}\n",[1067,1068,1069],"style",{},"html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}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 pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}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);}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}",{"title":346,"searchDepth":387,"depth":387,"links":1071},[1072,1073,1074,1080,1081,1082,1083,1084,1085,1086,1087],{"id":185,"depth":387,"text":186},{"id":232,"depth":387,"text":233},{"id":333,"depth":387,"text":334,"children":1075},[1076,1077,1078,1079],{"id":338,"depth":409,"text":339},{"id":461,"depth":409,"text":462},{"id":663,"depth":409,"text":664},{"id":682,"depth":409,"text":683},{"id":847,"depth":387,"text":848},{"id":857,"depth":387,"text":858},{"id":867,"depth":387,"text":868},{"id":888,"depth":387,"text":889},{"id":895,"depth":387,"text":896},{"id":930,"depth":387,"text":931},{"id":965,"depth":387,"text":966},{"id":1030,"depth":387,"text":1031},"How service worker boot time delays network-first navigations, and how navigation preload removes that delay without giving up offline support.","md",{"slug":1091,"type":1092,"breadcrumb":1093,"datePublished":1101,"dateModified":1101},"enabling-navigation-preload-in-service-workers","article",[1094,1097,1098,1099],{"name":1095,"url":1096},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1100},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fenabling-navigation-preload-in-service-workers\u002F","2026-10-06",true,"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fenabling-navigation-preload-in-service-workers",{"title":5,"description":1105},"Service worker startup can delay every navigation by 50-500ms. Navigation preload starts the network request in parallel with boot, protecting TTFB and LCP.","advanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fenabling-navigation-preload-in-service-workers\u002Findex","Ywf-BnP4CROsAzW_UUgvIAFb3HG7VSJSL_mI-cQfwT4",[1109,1113],{"title":1110,"path":1111,"stem":1112},"Debugging Service Worker Cache Misses in Production","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fdebugging-service-worker-cache-misses-in-production","advanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fdebugging-service-worker-cache-misses-in-production\u002Findex",{"title":1114,"path":1115,"stem":1116},"Handling Service Worker Updates with skipWaiting","\u002Fadvanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fhandling-service-worker-updates-with-skipwaiting","advanced-caching-strategies-cdn-architecture\u002Fservice-worker-caching-strategies\u002Fhandling-service-worker-updates-with-skipwaiting\u002Findex",1791308075302]