[{"data":1,"prerenderedAt":1770},["ShallowReactive",2],{"blog-post:workbox-astro-project":3},{"id":4,"title":5,"body":6,"date":1759,"description":1760,"draft":1761,"extension":1762,"lastModified":1763,"meta":1764,"navigation":149,"path":1765,"readingTime":1766,"seo":1767,"stem":1768,"__hash__":1769},"posts\u002Fblog\u002Fworkbox-astro-project.md","Integrate Workbox Service Worker into Astro Project",{"type":7,"value":8,"toc":1754},"minimark",[9,27,32,64,70,99,109,115,269,297,310,315,367,390,396,406,413,613,620,635,638,647,651,692,718,726,795,821,940,952,994,1003,1011,1113,1122,1137,1159,1300,1303,1347,1353,1360,1368,1699,1702,1710,1714,1732,1735,1750],[10,11,12,13,20,21,26],"p",{},"Integrating a\n",[14,15,19],"a",{"href":16,"rel":17},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FService_Worker_API",[18],"nofollow","Service worker","\nto your website is a great way to progressively enhance the sites performance as\nwell as supporting features like offline content viewing, background push etc.\nService Workers even though as simple as they are can be a bit daunting at first\nand a bit tricky to deal with especially when it comes to things like\npre-caching, pruning old caches or even handling events across the service\nworker and web app. ",[14,22,25],{"href":23,"rel":24},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fworkbox\u002F",[18],"Workbox"," is a\nset of well tested production ready tools\u002Flibraries that helps simplify these\nthings.",[28,29,31],"h2",{"id":30},"installing-workbox","Installing Workbox",[10,33,34,35,43,44,51,52,55,56,63],{},"Workbox can be setup in various ways like using\n",[14,36,39],{"href":37,"rel":38},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fworkbox\u002Fmodules\u002Fworkbox-cli\u002F",[18],[40,41,42],"code",{},"workbox-cli",",\n",[14,45,48],{"href":46,"rel":47},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fworkbox\u002Fmodules\u002Fworkbox-build\u002F",[18],[40,49,50],{},"workbox-build","\nnode package or just using a bundler plugin. All of the methods more or less do\none thing that is to generate the final ",[40,53,54],{},"sw.js"," service worker file which can be\nregistered in your HTML document using\n",[14,57,60],{"href":58,"rel":59},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FServiceWorkerContainer\u002Fregister",[18],[40,61,62],{},"navigator.serviceWorker.register","\nmethod.",[10,65,66,67,69],{},"Here i'll be using the ",[40,68,42],{}," to generate my service worker file. Let's\ninstall it using package manager.",[71,72,77],"pre",{"className":73,"code":74,"language":75,"meta":76,"style":76},"language-sh shiki shiki-themes github-dark","npm install -D workbox-cli\n","sh","",[40,78,79],{"__ignoreMap":76},[80,81,84,88,92,96],"span",{"class":82,"line":83},"line",1,[80,85,87],{"class":86},"svObZ","npm",[80,89,91],{"class":90},"sU2Wk"," install",[80,93,95],{"class":94},"sDLfK"," -D",[80,97,98],{"class":90}," workbox-cli\n",[10,100,101,102,105,106,108],{},"Create a ",[40,103,104],{},"workbox.config.cjs"," file in the root directory of your project. This\nis the config file ",[40,107,42],{}," will use to generate our service worker file.",[10,110,111,112,114],{},"Let's add some config options to ",[40,113,104],{},":",[71,116,120],{"className":117,"code":118,"language":119,"meta":76,"style":76},"language-js shiki shiki-themes github-dark","const globPath = process.env.SW_DIST_PATH;\n\nif (!globPath) {\n  console.error(\"SW_DIST_PATH not specified. Check your npm build script\");\n  return;\n}\n\nmodule.exports = {\n  globDirectory: globPath,\n  globPatterns: [\n    \"**\u002F*.{css,png,webp,avif,mp4,html,ico,woff2,json,js,svg,xml,txt}\",\n  ],\n  swDest: `${globPath}\u002Fsw.js`,\n};\n","js",[40,121,122,144,151,166,184,192,198,203,220,226,232,240,246,263],{"__ignoreMap":76},[80,123,124,128,131,134,138,141],{"class":82,"line":83},[80,125,127],{"class":126},"snl16","const",[80,129,130],{"class":94}," globPath",[80,132,133],{"class":126}," =",[80,135,137],{"class":136},"s95oV"," process.env.",[80,139,140],{"class":94},"SW_DIST_PATH",[80,142,143],{"class":136},";\n",[80,145,147],{"class":82,"line":146},2,[80,148,150],{"emptyLinePlaceholder":149},true,"\n",[80,152,154,157,160,163],{"class":82,"line":153},3,[80,155,156],{"class":126},"if",[80,158,159],{"class":136}," (",[80,161,162],{"class":126},"!",[80,164,165],{"class":136},"globPath) {\n",[80,167,169,172,175,178,181],{"class":82,"line":168},4,[80,170,171],{"class":136},"  console.",[80,173,174],{"class":86},"error",[80,176,177],{"class":136},"(",[80,179,180],{"class":90},"\"SW_DIST_PATH not specified. Check your npm build script\"",[80,182,183],{"class":136},");\n",[80,185,187,190],{"class":82,"line":186},5,[80,188,189],{"class":126},"  return",[80,191,143],{"class":136},[80,193,195],{"class":82,"line":194},6,[80,196,197],{"class":136},"}\n",[80,199,201],{"class":82,"line":200},7,[80,202,150],{"emptyLinePlaceholder":149},[80,204,206,209,212,215,217],{"class":82,"line":205},8,[80,207,208],{"class":94},"module",[80,210,211],{"class":136},".",[80,213,214],{"class":94},"exports",[80,216,133],{"class":126},[80,218,219],{"class":136}," {\n",[80,221,223],{"class":82,"line":222},9,[80,224,225],{"class":136},"  globDirectory: globPath,\n",[80,227,229],{"class":82,"line":228},10,[80,230,231],{"class":136},"  globPatterns: [\n",[80,233,235,238],{"class":82,"line":234},11,[80,236,237],{"class":90},"    \"**\u002F*.{css,png,webp,avif,mp4,html,ico,woff2,json,js,svg,xml,txt}\"",[80,239,43],{"class":136},[80,241,243],{"class":82,"line":242},12,[80,244,245],{"class":136},"  ],\n",[80,247,249,252,255,258,261],{"class":82,"line":248},13,[80,250,251],{"class":136},"  swDest: ",[80,253,254],{"class":90},"`${",[80,256,257],{"class":136},"globPath",[80,259,260],{"class":90},"}\u002Fsw.js`",[80,262,43],{"class":136},[80,264,266],{"class":82,"line":265},14,[80,267,268],{"class":136},"};\n",[10,270,271,272,275,276,279,280,282,283,285,286,289,290,292,293,296],{},"The ",[40,273,274],{},"globPatterns"," option specifies all the file types we want our service\nworker to pre-cache when it installs and ",[40,277,278],{},"globDirectory"," is the path where the\n",[40,281,42],{}," should look for to match the file type patterns, So the\n",[40,284,278],{}," is basically the build output path of our ",[40,287,288],{},"astro build"," command.\nSince we can target different adapters to build for in our Astro project I have\nmoved out the ",[40,291,278],{}," value into the environment variable for some extra\nflexibility. The ",[40,294,295],{},"swDest"," directory is where the service worker file will be\nwritten to.",[10,298,299,300,302,303,306,307,309],{},"Let's use the above config file in ",[40,301,42],{}," to generate out service worker\nfile. First we'll need to update our build scripts in ",[40,304,305],{},"package.json"," file so we\ncan specify our ",[40,308,140],{}," environment variable value.",[10,311,312,313,114],{},"In our ",[40,314,305],{},[71,316,320],{"className":317,"code":318,"language":319,"meta":76,"style":76},"language-json shiki shiki-themes github-dark","{\n  \"scripts\": {\n    \"build\": \"astro build && npm run generateSW\",\n    \"generateSW\": \"SW_DIST_PATH=dist\u002Fclient workbox generateSW workbox.config.cjs\"\n  }\n}\n","json",[40,321,322,327,335,348,358,363],{"__ignoreMap":76},[80,323,324],{"class":82,"line":83},[80,325,326],{"class":136},"{\n",[80,328,329,332],{"class":82,"line":146},[80,330,331],{"class":94},"  \"scripts\"",[80,333,334],{"class":136},": {\n",[80,336,337,340,343,346],{"class":82,"line":153},[80,338,339],{"class":94},"    \"build\"",[80,341,342],{"class":136},": ",[80,344,345],{"class":90},"\"astro build && npm run generateSW\"",[80,347,43],{"class":136},[80,349,350,353,355],{"class":82,"line":168},[80,351,352],{"class":94},"    \"generateSW\"",[80,354,342],{"class":136},[80,356,357],{"class":90},"\"SW_DIST_PATH=dist\u002Fclient workbox generateSW workbox.config.cjs\"\n",[80,359,360],{"class":82,"line":186},[80,361,362],{"class":136},"  }\n",[80,364,365],{"class":82,"line":194},[80,366,197],{"class":136},[10,368,369,370,375,376,379,380,383,384,386,387,389],{},"I'm using\n",[14,371,374],{"href":372,"rel":373},"https:\u002F\u002Fdocs.astro.build\u002Fen\u002Fguides\u002Fintegrations-guide\u002Fnode\u002F",[18],"Astro Node Adapter","\nin this example that outputs build files into ",[40,377,378],{},"dist\u002Fclient"," directory. So when\nwe run ",[40,381,382],{},"generateSW"," command the workbox cli will look for static assets in the\ncorrect directory and include the file paths in the ",[40,385,54],{}," file. Now we can\ninclude this ",[40,388,54],{}," file in our Astro project and register it.",[10,391,392,393,395],{},"Generated ",[40,394,54],{}," file in the build directory:",[10,397,398],{},[399,400],"img",{"alt":401,"dataZoomable":76,"height":402,"loading":403,"src":404,"width":405},"Pre-cache urls in the generated sw.js file",1224,"lazy","\u002Fcontent\u002Fprecache-urls.png",2034,[10,407,408,409,412],{},"In your ",[40,410,411],{},"Layout.astro"," or common layout file we can register this service worker\nlike this:",[71,414,418],{"className":415,"code":416,"language":417,"meta":76,"style":76},"language-html shiki shiki-themes github-dark","\u003Chead>\n  \u003Cscript is:inline>\n    addEventListener(\"load\", () => {\n      if (\"serviceWorker\" in navigator) {\n        navigator.serviceWorker\n          .register(\"\u002Fsw.js\")\n          .then(() => {\n            console.log(\"Service Worker Registered\");\n          })\n          .catch((err) => {\n            console.log(\"Some error occurred while registering service worker\");\n            console.log(err);\n          });\n      }\n    });\n  \u003C\u002Fscript>\n\u003C\u002Fhead>\n","html",[40,419,420,432,445,463,479,484,500,514,529,534,555,568,577,582,587,593,603],{"__ignoreMap":76},[80,421,422,425,429],{"class":82,"line":83},[80,423,424],{"class":136},"\u003C",[80,426,428],{"class":427},"s4JwU","head",[80,430,431],{"class":136},">\n",[80,433,434,437,440,443],{"class":82,"line":146},[80,435,436],{"class":136},"  \u003C",[80,438,439],{"class":427},"script",[80,441,442],{"class":86}," is:inline",[80,444,431],{"class":136},[80,446,447,450,452,455,458,461],{"class":82,"line":153},[80,448,449],{"class":86},"    addEventListener",[80,451,177],{"class":136},[80,453,454],{"class":90},"\"load\"",[80,456,457],{"class":136},", () ",[80,459,460],{"class":126},"=>",[80,462,219],{"class":136},[80,464,465,468,470,473,476],{"class":82,"line":168},[80,466,467],{"class":126},"      if",[80,469,159],{"class":136},[80,471,472],{"class":90},"\"serviceWorker\"",[80,474,475],{"class":126}," in",[80,477,478],{"class":136}," navigator) {\n",[80,480,481],{"class":82,"line":186},[80,482,483],{"class":136},"        navigator.serviceWorker\n",[80,485,486,489,492,494,497],{"class":82,"line":194},[80,487,488],{"class":136},"          .",[80,490,491],{"class":86},"register",[80,493,177],{"class":136},[80,495,496],{"class":90},"\"\u002Fsw.js\"",[80,498,499],{"class":136},")\n",[80,501,502,504,507,510,512],{"class":82,"line":200},[80,503,488],{"class":136},[80,505,506],{"class":86},"then",[80,508,509],{"class":136},"(() ",[80,511,460],{"class":126},[80,513,219],{"class":136},[80,515,516,519,522,524,527],{"class":82,"line":205},[80,517,518],{"class":136},"            console.",[80,520,521],{"class":86},"log",[80,523,177],{"class":136},[80,525,526],{"class":90},"\"Service Worker Registered\"",[80,528,183],{"class":136},[80,530,531],{"class":82,"line":222},[80,532,533],{"class":136},"          })\n",[80,535,536,538,541,544,548,551,553],{"class":82,"line":228},[80,537,488],{"class":136},[80,539,540],{"class":86},"catch",[80,542,543],{"class":136},"((",[80,545,547],{"class":546},"s9osk","err",[80,549,550],{"class":136},") ",[80,552,460],{"class":126},[80,554,219],{"class":136},[80,556,557,559,561,563,566],{"class":82,"line":234},[80,558,518],{"class":136},[80,560,521],{"class":86},[80,562,177],{"class":136},[80,564,565],{"class":90},"\"Some error occurred while registering service worker\"",[80,567,183],{"class":136},[80,569,570,572,574],{"class":82,"line":242},[80,571,518],{"class":136},[80,573,521],{"class":86},[80,575,576],{"class":136},"(err);\n",[80,578,579],{"class":82,"line":248},[80,580,581],{"class":136},"          });\n",[80,583,584],{"class":82,"line":265},[80,585,586],{"class":136},"      }\n",[80,588,590],{"class":82,"line":589},15,[80,591,592],{"class":136},"    });\n",[80,594,596,599,601],{"class":82,"line":595},16,[80,597,598],{"class":136},"  \u003C\u002F",[80,600,439],{"class":427},[80,602,431],{"class":136},[80,604,606,609,611],{"class":82,"line":605},17,[80,607,608],{"class":136},"\u003C\u002F",[80,610,428],{"class":427},[80,612,431],{"class":136},[10,614,615,616,619],{},"If we run the preview server (",[40,617,618],{},"npm run preview",") and check our Dev Tools we can\nsee that our service worker is registered and installing.",[10,621,622,628,629],{},[399,623],{"alt":624,"dataZoomable":76,"height":625,"loading":403,"src":626,"width":627},"Service Worker installing",686,"\u002Fcontent\u002Fsw-installing.png",1634,"\nService Worker Pre-caching:\n",[399,630],{"alt":631,"dataZoomable":76,"height":632,"loading":403,"src":633,"width":634},"Service Worker Precaching",702,"\u002Fcontent\u002Fsw-precaching.png",3076,[10,636,637],{},"Once the service worker is done pre-caching all the assets it will be activated\nand running.",[10,639,640,646],{},[399,641],{"alt":642,"dataZoomable":76,"height":643,"loading":403,"src":644,"width":645},"Service Worker Activated",670,"\u002Fcontent\u002Fsw-activated.png",1324,"\nNow any requests that are pre-cached will be returned by our service worker. Workbox\nhandles all the pruning of old caches, revisions, updating sw.js etc for us.",[28,648,650],{"id":649},"customising-our-service-worker","Customising our service worker",[10,652,653,654,659,660,43,662,668,669,676,677,679,680,682,683,685,686,688,689,691],{},"There are mainly\n",[14,655,658],{"href":656,"rel":657},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fworkbox\u002Fthe-ways-of-workbox\u002F",[18],"two ways of generating","\nour service worker using ",[40,661,42],{},[14,663,666],{"href":664,"rel":665},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fworkbox\u002Fmodules\u002Fworkbox-cli\u002F#generatesw",[18],[40,667,382],{},"\nand\n",[14,670,673],{"href":671,"rel":672},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fworkbox\u002Fmodules\u002Fworkbox-cli\u002F#injectmanifest",[18],[40,674,675],{},"injectManifest",".\nSince we use ",[40,678,382],{}," command from the ",[40,681,42],{}," we don't have much\ncontrol over the final ",[40,684,54],{}," file. If we want to add some custom logic to our\nservice worker along with workbox tools we can use ",[40,687,675],{}," command.\nThis let's us pass in our custom logic into the final ",[40,690,54],{}," file",[10,693,694,695,697,698,701,702,707,708,710,711,713,714,717],{},"Let's create a custom service worker using ",[40,696,42],{}," but still keep the\npre-caching capabilities of workbox. That's where ",[40,699,700],{},"workbox-modules"," come into\nplay. Pre-caching routes is actually a\n",[14,703,706],{"href":704,"rel":705},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fworkbox\u002Fmodules\u002F",[18],"workbox module"," that we get\nfor free while using ",[40,709,382],{}," command from workbox. First let's create a\n",[40,712,54],{}," file in ",[40,715,716],{},"public"," directory of our astro project and add pre-caching\nmodule from workbox to it.",[10,719,720,722,723,725],{},[40,721,54],{}," in ",[40,724,716],{}," directory:",[71,727,729],{"className":117,"code":728,"language":119,"meta":76,"style":76},"import { precacheAndRoute, cleanupOutdatedCaches } from \"workbox-precaching\";\n\ncleanupOutdatedCaches();\n\n\u002F\u002F Precache the manifest\nprecacheAndRoute(self.__WB_MANIFEST);\n\nconsole.log(\"Hello. Some custom logic here...\");\n",[40,730,731,747,751,759,763,769,777,781],{"__ignoreMap":76},[80,732,733,736,739,742,745],{"class":82,"line":83},[80,734,735],{"class":126},"import",[80,737,738],{"class":136}," { precacheAndRoute, cleanupOutdatedCaches } ",[80,740,741],{"class":126},"from",[80,743,744],{"class":90}," \"workbox-precaching\"",[80,746,143],{"class":136},[80,748,749],{"class":82,"line":146},[80,750,150],{"emptyLinePlaceholder":149},[80,752,753,756],{"class":82,"line":153},[80,754,755],{"class":86},"cleanupOutdatedCaches",[80,757,758],{"class":136},"();\n",[80,760,761],{"class":82,"line":168},[80,762,150],{"emptyLinePlaceholder":149},[80,764,765],{"class":82,"line":186},[80,766,768],{"class":767},"sAwPA","\u002F\u002F Precache the manifest\n",[80,770,771,774],{"class":82,"line":194},[80,772,773],{"class":86},"precacheAndRoute",[80,775,776],{"class":136},"(self.__WB_MANIFEST);\n",[80,778,779],{"class":82,"line":200},[80,780,150],{"emptyLinePlaceholder":149},[80,782,783,786,788,790,793],{"class":82,"line":205},[80,784,785],{"class":136},"console.",[80,787,521],{"class":86},[80,789,177],{"class":136},[80,791,792],{"class":90},"\"Hello. Some custom logic here...\"",[80,794,183],{"class":136},[10,796,797,798,801,802,804,805,807,808,814,815,817,818,820],{},"Here ",[40,799,800],{},"self.__WB_MANIFEST"," is a special value where workbox will inject the list\nof URLs that are needed to be pre-cached using the ",[40,803,278],{}," and\n",[40,806,274],{}," config.\n",[14,809,812],{"href":810,"rel":811},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fworkbox\u002Freference\u002Fworkbox-precaching\u002F#method-cleanupOutdatedCaches",[18],[40,813,755],{},"\nmethod will clean-up old and incompatible caches created previously by older\nversions of workbox. Let's add this ",[40,816,54],{}," file as input in our\n",[40,819,104],{}," file:",[71,822,824],{"className":117,"code":823,"language":119,"meta":76,"style":76},"const globPath = process.env.SW_DIST_PATH;\n\nif (!globPath) {\n  console.error(\"SW_DIST_PATH not specified. Check your npm build script\");\n  return;\n}\n\nmodule.exports = {\n  globDirectory: globPath,\n  globPatterns: [\n    \"**\u002F*.{css,png,webp,avif,mp4,html,ico,woff2,json,js,svg,xml,txt}\",\n  ],\n  swDest: `${globPath}\u002Fsw.js`,\n  swSrc: \".\u002Fpublic\u002Fsw.js\", \u002F\u002F Our custom sw.js file\n};\n",[40,825,826,840,844,854,866,872,876,880,892,896,900,906,910,922,936],{"__ignoreMap":76},[80,827,828,830,832,834,836,838],{"class":82,"line":83},[80,829,127],{"class":126},[80,831,130],{"class":94},[80,833,133],{"class":126},[80,835,137],{"class":136},[80,837,140],{"class":94},[80,839,143],{"class":136},[80,841,842],{"class":82,"line":146},[80,843,150],{"emptyLinePlaceholder":149},[80,845,846,848,850,852],{"class":82,"line":153},[80,847,156],{"class":126},[80,849,159],{"class":136},[80,851,162],{"class":126},[80,853,165],{"class":136},[80,855,856,858,860,862,864],{"class":82,"line":168},[80,857,171],{"class":136},[80,859,174],{"class":86},[80,861,177],{"class":136},[80,863,180],{"class":90},[80,865,183],{"class":136},[80,867,868,870],{"class":82,"line":186},[80,869,189],{"class":126},[80,871,143],{"class":136},[80,873,874],{"class":82,"line":194},[80,875,197],{"class":136},[80,877,878],{"class":82,"line":200},[80,879,150],{"emptyLinePlaceholder":149},[80,881,882,884,886,888,890],{"class":82,"line":205},[80,883,208],{"class":94},[80,885,211],{"class":136},[80,887,214],{"class":94},[80,889,133],{"class":126},[80,891,219],{"class":136},[80,893,894],{"class":82,"line":222},[80,895,225],{"class":136},[80,897,898],{"class":82,"line":228},[80,899,231],{"class":136},[80,901,902,904],{"class":82,"line":234},[80,903,237],{"class":90},[80,905,43],{"class":136},[80,907,908],{"class":82,"line":242},[80,909,245],{"class":136},[80,911,912,914,916,918,920],{"class":82,"line":248},[80,913,251],{"class":136},[80,915,254],{"class":90},[80,917,257],{"class":136},[80,919,260],{"class":90},[80,921,43],{"class":136},[80,923,924,927,930,933],{"class":82,"line":265},[80,925,926],{"class":136},"  swSrc: ",[80,928,929],{"class":90},"\".\u002Fpublic\u002Fsw.js\"",[80,931,932],{"class":136},", ",[80,934,935],{"class":767},"\u002F\u002F Our custom sw.js file\n",[80,937,938],{"class":82,"line":589},[80,939,268],{"class":136},[10,941,942,943,945,946,948,949,951],{},"Also let's change the npm package scripts from ",[40,944,382],{}," to ",[40,947,675],{},"\nin our ",[40,950,42],{}," command:",[71,953,955],{"className":317,"code":954,"language":319,"meta":76,"style":76},"{\n  \"scripts\": {\n    \"build\": \"astro build && npm run generateSW\",\n    \"generateSW\": \"SW_DIST_PATH=dist\u002Fclient workbox injectManifest workbox.config.cjs\"\n  }\n}\n",[40,956,957,961,967,977,986,990],{"__ignoreMap":76},[80,958,959],{"class":82,"line":83},[80,960,326],{"class":136},[80,962,963,965],{"class":82,"line":146},[80,964,331],{"class":94},[80,966,334],{"class":136},[80,968,969,971,973,975],{"class":82,"line":153},[80,970,339],{"class":94},[80,972,342],{"class":136},[80,974,345],{"class":90},[80,976,43],{"class":136},[80,978,979,981,983],{"class":82,"line":168},[80,980,352],{"class":94},[80,982,342],{"class":136},[80,984,985],{"class":90},"\"SW_DIST_PATH=dist\u002Fclient workbox injectManifest workbox.config.cjs\"\n",[80,987,988],{"class":82,"line":186},[80,989,362],{"class":136},[80,991,992],{"class":82,"line":194},[80,993,197],{"class":136},[10,995,996,997,999,1000,1002],{},"Now if we build the astro project using ",[40,998,288],{}," we should get an ",[40,1001,54],{},"\nfile in the build output directory with our custom service worker logic and\nworkbox pre-caching capabilities.",[10,1004,1005,1006,1008,1009,114],{},"Built ",[40,1007,54],{}," file from ",[40,1010,378],{},[71,1012,1014],{"className":117,"code":1013,"language":119,"meta":76,"style":76},"import { precacheAndRoute } from \"workbox-precaching\";\n\u002F\u002F Precache the manifest\nprecacheAndRoute([\n  {\n    revision: \"6f5efcdd674ddc792168251976c87ca1\",\n    url: \"_astro\u002F_slug_.320abdb5.css\",\n  },\n  { revision: \"307aca520857f32f274358d4e881e381\", url: \"uses\u002Findex.html\" },\n  \u002F\u002F Some more Pre-cache urls...\n]);\n\nconsole.log(\"Hello. Some custom logic here...\");\n",[40,1015,1016,1029,1033,1040,1045,1055,1065,1070,1087,1092,1097,1101],{"__ignoreMap":76},[80,1017,1018,1020,1023,1025,1027],{"class":82,"line":83},[80,1019,735],{"class":126},[80,1021,1022],{"class":136}," { precacheAndRoute } ",[80,1024,741],{"class":126},[80,1026,744],{"class":90},[80,1028,143],{"class":136},[80,1030,1031],{"class":82,"line":146},[80,1032,768],{"class":767},[80,1034,1035,1037],{"class":82,"line":153},[80,1036,773],{"class":86},[80,1038,1039],{"class":136},"([\n",[80,1041,1042],{"class":82,"line":168},[80,1043,1044],{"class":136},"  {\n",[80,1046,1047,1050,1053],{"class":82,"line":186},[80,1048,1049],{"class":136},"    revision: ",[80,1051,1052],{"class":90},"\"6f5efcdd674ddc792168251976c87ca1\"",[80,1054,43],{"class":136},[80,1056,1057,1060,1063],{"class":82,"line":194},[80,1058,1059],{"class":136},"    url: ",[80,1061,1062],{"class":90},"\"_astro\u002F_slug_.320abdb5.css\"",[80,1064,43],{"class":136},[80,1066,1067],{"class":82,"line":200},[80,1068,1069],{"class":136},"  },\n",[80,1071,1072,1075,1078,1081,1084],{"class":82,"line":205},[80,1073,1074],{"class":136},"  { revision: ",[80,1076,1077],{"class":90},"\"307aca520857f32f274358d4e881e381\"",[80,1079,1080],{"class":136},", url: ",[80,1082,1083],{"class":90},"\"uses\u002Findex.html\"",[80,1085,1086],{"class":136}," },\n",[80,1088,1089],{"class":82,"line":222},[80,1090,1091],{"class":767},"  \u002F\u002F Some more Pre-cache urls...\n",[80,1093,1094],{"class":82,"line":228},[80,1095,1096],{"class":136},"]);\n",[80,1098,1099],{"class":82,"line":234},[80,1100,150],{"emptyLinePlaceholder":149},[80,1102,1103,1105,1107,1109,1111],{"class":82,"line":242},[80,1104,785],{"class":136},[80,1106,521],{"class":86},[80,1108,177],{"class":136},[80,1110,792],{"class":90},[80,1112,183],{"class":136},[10,1114,1115,1116,1118,1119,211],{},"If we start the preview server (",[40,1117,618],{},") we can see that our service\nworker doesn't register and throws out an error:\n",[40,1120,1121],{},"Uncaught SyntaxError: Cannot use import statement outside a module (at sw.js:1:1)",[10,1123,1124,1130,1131,1133,1134,1136],{},[399,1125],{"alt":1126,"dataZoomable":76,"height":1127,"loading":403,"src":1128,"width":1129},"Error registering the service worker",90,"\u002Fcontent\u002FinjectManifest-error.png",2668,"\nThat is because when we use ",[40,1132,675],{}," workbox doesn't bundle the dependencies\nso we need to bundle the dependencies and compile the final ",[40,1135,54],{}," ourselves.",[10,1138,1139,1140,1147,1148,1151,1152,1155,1156,1158],{},"We can use any bundler that we like, I'll be using\n",[14,1141,1144],{"href":1142,"rel":1143},"https:\u002F\u002Fesbuild.github.io",[18],[40,1145,1146],{},"esbuild"," for this. Install esbuild using\n",[40,1149,1150],{},"npm install --save-exact esbuild"," and create a ",[40,1153,1154],{},"bundle-sw.mjs"," file and\nconfigure it to bundle our final ",[40,1157,54],{}," file like so:",[71,1160,1162],{"className":117,"code":1161,"language":119,"meta":76,"style":76},"import * as esbuild from \"esbuild\";\nconst globPath = process.env.SW_DIST_PATH;\n\n\u002F\u002F bundle the sw file for browser use\nawait esbuild.build({\n  entryPoints: [`${globPath}\u002Fsw.js`],\n  outfile: `${globPath}\u002Fsw.js`,\n  target: [\"es2020\"],\n  bundle: true,\n  minify: true,\n  allowOverwrite: true,\n  sourcemap: true,\n});\n",[40,1163,1164,1184,1198,1202,1207,1221,1235,1248,1258,1268,1277,1286,1295],{"__ignoreMap":76},[80,1165,1166,1168,1171,1174,1177,1179,1182],{"class":82,"line":83},[80,1167,735],{"class":126},[80,1169,1170],{"class":94}," *",[80,1172,1173],{"class":126}," as",[80,1175,1176],{"class":136}," esbuild ",[80,1178,741],{"class":126},[80,1180,1181],{"class":90}," \"esbuild\"",[80,1183,143],{"class":136},[80,1185,1186,1188,1190,1192,1194,1196],{"class":82,"line":146},[80,1187,127],{"class":126},[80,1189,130],{"class":94},[80,1191,133],{"class":126},[80,1193,137],{"class":136},[80,1195,140],{"class":94},[80,1197,143],{"class":136},[80,1199,1200],{"class":82,"line":153},[80,1201,150],{"emptyLinePlaceholder":149},[80,1203,1204],{"class":82,"line":168},[80,1205,1206],{"class":767},"\u002F\u002F bundle the sw file for browser use\n",[80,1208,1209,1212,1215,1218],{"class":82,"line":186},[80,1210,1211],{"class":126},"await",[80,1213,1214],{"class":136}," esbuild.",[80,1216,1217],{"class":86},"build",[80,1219,1220],{"class":136},"({\n",[80,1222,1223,1226,1228,1230,1232],{"class":82,"line":194},[80,1224,1225],{"class":136},"  entryPoints: [",[80,1227,254],{"class":90},[80,1229,257],{"class":136},[80,1231,260],{"class":90},[80,1233,1234],{"class":136},"],\n",[80,1236,1237,1240,1242,1244,1246],{"class":82,"line":200},[80,1238,1239],{"class":136},"  outfile: ",[80,1241,254],{"class":90},[80,1243,257],{"class":136},[80,1245,260],{"class":90},[80,1247,43],{"class":136},[80,1249,1250,1253,1256],{"class":82,"line":205},[80,1251,1252],{"class":136},"  target: [",[80,1254,1255],{"class":90},"\"es2020\"",[80,1257,1234],{"class":136},[80,1259,1260,1263,1266],{"class":82,"line":222},[80,1261,1262],{"class":136},"  bundle: ",[80,1264,1265],{"class":94},"true",[80,1267,43],{"class":136},[80,1269,1270,1273,1275],{"class":82,"line":228},[80,1271,1272],{"class":136},"  minify: ",[80,1274,1265],{"class":94},[80,1276,43],{"class":136},[80,1278,1279,1282,1284],{"class":82,"line":234},[80,1280,1281],{"class":136},"  allowOverwrite: ",[80,1283,1265],{"class":94},[80,1285,43],{"class":136},[80,1287,1288,1291,1293],{"class":82,"line":242},[80,1289,1290],{"class":136},"  sourcemap: ",[80,1292,1265],{"class":94},[80,1294,43],{"class":136},[80,1296,1297],{"class":82,"line":248},[80,1298,1299],{"class":136},"});\n",[10,1301,1302],{},"Let's now modify our npm package script to add esbuild bundle stage.",[71,1304,1306],{"className":317,"code":1305,"language":319,"meta":76,"style":76},"{\n  \"scripts\": {\n    \"build\": \"astro build && npm run generateAndBundleSW\",\n    \"generateAndBundleSW\": \"SW_DIST_PATH=dist\u002Fclient workbox injectManifest workbox.config.cjs && node bundle-sw.mjs\"\n  }\n}\n",[40,1307,1308,1312,1318,1329,1339,1343],{"__ignoreMap":76},[80,1309,1310],{"class":82,"line":83},[80,1311,326],{"class":136},[80,1313,1314,1316],{"class":82,"line":146},[80,1315,331],{"class":94},[80,1317,334],{"class":136},[80,1319,1320,1322,1324,1327],{"class":82,"line":153},[80,1321,339],{"class":94},[80,1323,342],{"class":136},[80,1325,1326],{"class":90},"\"astro build && npm run generateAndBundleSW\"",[80,1328,43],{"class":136},[80,1330,1331,1334,1336],{"class":82,"line":168},[80,1332,1333],{"class":94},"    \"generateAndBundleSW\"",[80,1335,342],{"class":136},[80,1337,1338],{"class":90},"\"SW_DIST_PATH=dist\u002Fclient workbox injectManifest workbox.config.cjs && node bundle-sw.mjs\"\n",[80,1340,1341],{"class":82,"line":186},[80,1342,362],{"class":136},[80,1344,1345],{"class":82,"line":194},[80,1346,197],{"class":136},[10,1348,1349,1350,1352],{},"Now if we ",[40,1351,288],{}," and preview the build we should have our custom service\nworker working.",[10,1354,1355,1356,1359],{},"Since we now can add custom logic to our service worker let's add another\nworkbox module that adds Navigation Preload capabilities to our site with the\nhelp of ",[40,1357,1358],{},"workbox-navigation-preload"," module. Navigation Preload fixes the delay\nin navigation requests caused by service worker boot time.",[10,1361,1362,1363,114],{},"The following example is from\n",[14,1364,1367],{"href":1365,"rel":1366},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fworkbox\u002Fnavigation-preload\u002F",[18],"Navigation Preload for Network-first HTML",[71,1369,1371],{"className":117,"code":1370,"language":119,"meta":76,"style":76},"\u002F\u002F public\u002Fsw.js\nimport * as navigationPreload from \"workbox-navigation-preload\";\nimport { NetworkFirst, StaleWhileRevalidate } from \"workbox-strategies\";\nimport { registerRoute, NavigationRoute, Route } from \"workbox-routing\";\nimport { precacheAndRoute } from \"workbox-precaching\";\n\n\u002F\u002F Precache the manifest\nprecacheAndRoute(self.__WB_MANIFEST);\n\n\u002F\u002F Enable navigation preload\nnavigationPreload.enable();\n\n\u002F\u002F Create a new navigation route that uses the Network-first, falling back to\n\u002F\u002F cache strategy for navigation requests with its own cache. This route will be\n\u002F\u002F handled by navigation preload. The NetworkOnly strategy will work as well.\nconst navigationRoute = new NavigationRoute(\n  new NetworkFirst({\n    cacheName: \"navigations\",\n  })\n);\n\n\u002F\u002F Register the navigation route\nregisterRoute(navigationRoute);\n\n\u002F\u002F Create a route for image, script, or style requests that use a\n\u002F\u002F stale-while-revalidate strategy. This route will be unaffected\n\u002F\u002F by navigation preload.\nconst staticAssetsRoute = new Route(\n  ({ request }) => {\n    return [\"image\", \"script\", \"style\"].includes(request.destination);\n  },\n  new StaleWhileRevalidate({\n    cacheName: \"static-assets\",\n  })\n);\n\n\u002F\u002F Register the route handling static assets\nregisterRoute(staticAssetsRoute);\n",[40,1372,1373,1378,1396,1410,1424,1436,1440,1444,1450,1454,1459,1469,1473,1478,1483,1488,1506,1516,1527,1533,1538,1543,1549,1558,1563,1569,1575,1581,1598,1614,1645,1650,1660,1670,1675,1680,1685,1691],{"__ignoreMap":76},[80,1374,1375],{"class":82,"line":83},[80,1376,1377],{"class":767},"\u002F\u002F public\u002Fsw.js\n",[80,1379,1380,1382,1384,1386,1389,1391,1394],{"class":82,"line":146},[80,1381,735],{"class":126},[80,1383,1170],{"class":94},[80,1385,1173],{"class":126},[80,1387,1388],{"class":136}," navigationPreload ",[80,1390,741],{"class":126},[80,1392,1393],{"class":90}," \"workbox-navigation-preload\"",[80,1395,143],{"class":136},[80,1397,1398,1400,1403,1405,1408],{"class":82,"line":153},[80,1399,735],{"class":126},[80,1401,1402],{"class":136}," { NetworkFirst, StaleWhileRevalidate } ",[80,1404,741],{"class":126},[80,1406,1407],{"class":90}," \"workbox-strategies\"",[80,1409,143],{"class":136},[80,1411,1412,1414,1417,1419,1422],{"class":82,"line":168},[80,1413,735],{"class":126},[80,1415,1416],{"class":136}," { registerRoute, NavigationRoute, Route } ",[80,1418,741],{"class":126},[80,1420,1421],{"class":90}," \"workbox-routing\"",[80,1423,143],{"class":136},[80,1425,1426,1428,1430,1432,1434],{"class":82,"line":186},[80,1427,735],{"class":126},[80,1429,1022],{"class":136},[80,1431,741],{"class":126},[80,1433,744],{"class":90},[80,1435,143],{"class":136},[80,1437,1438],{"class":82,"line":194},[80,1439,150],{"emptyLinePlaceholder":149},[80,1441,1442],{"class":82,"line":200},[80,1443,768],{"class":767},[80,1445,1446,1448],{"class":82,"line":205},[80,1447,773],{"class":86},[80,1449,776],{"class":136},[80,1451,1452],{"class":82,"line":222},[80,1453,150],{"emptyLinePlaceholder":149},[80,1455,1456],{"class":82,"line":228},[80,1457,1458],{"class":767},"\u002F\u002F Enable navigation preload\n",[80,1460,1461,1464,1467],{"class":82,"line":234},[80,1462,1463],{"class":136},"navigationPreload.",[80,1465,1466],{"class":86},"enable",[80,1468,758],{"class":136},[80,1470,1471],{"class":82,"line":242},[80,1472,150],{"emptyLinePlaceholder":149},[80,1474,1475],{"class":82,"line":248},[80,1476,1477],{"class":767},"\u002F\u002F Create a new navigation route that uses the Network-first, falling back to\n",[80,1479,1480],{"class":82,"line":265},[80,1481,1482],{"class":767},"\u002F\u002F cache strategy for navigation requests with its own cache. This route will be\n",[80,1484,1485],{"class":82,"line":589},[80,1486,1487],{"class":767},"\u002F\u002F handled by navigation preload. The NetworkOnly strategy will work as well.\n",[80,1489,1490,1492,1495,1497,1500,1503],{"class":82,"line":595},[80,1491,127],{"class":126},[80,1493,1494],{"class":94}," navigationRoute",[80,1496,133],{"class":126},[80,1498,1499],{"class":126}," new",[80,1501,1502],{"class":86}," NavigationRoute",[80,1504,1505],{"class":136},"(\n",[80,1507,1508,1511,1514],{"class":82,"line":605},[80,1509,1510],{"class":126},"  new",[80,1512,1513],{"class":86}," NetworkFirst",[80,1515,1220],{"class":136},[80,1517,1519,1522,1525],{"class":82,"line":1518},18,[80,1520,1521],{"class":136},"    cacheName: ",[80,1523,1524],{"class":90},"\"navigations\"",[80,1526,43],{"class":136},[80,1528,1530],{"class":82,"line":1529},19,[80,1531,1532],{"class":136},"  })\n",[80,1534,1536],{"class":82,"line":1535},20,[80,1537,183],{"class":136},[80,1539,1541],{"class":82,"line":1540},21,[80,1542,150],{"emptyLinePlaceholder":149},[80,1544,1546],{"class":82,"line":1545},22,[80,1547,1548],{"class":767},"\u002F\u002F Register the navigation route\n",[80,1550,1552,1555],{"class":82,"line":1551},23,[80,1553,1554],{"class":86},"registerRoute",[80,1556,1557],{"class":136},"(navigationRoute);\n",[80,1559,1561],{"class":82,"line":1560},24,[80,1562,150],{"emptyLinePlaceholder":149},[80,1564,1566],{"class":82,"line":1565},25,[80,1567,1568],{"class":767},"\u002F\u002F Create a route for image, script, or style requests that use a\n",[80,1570,1572],{"class":82,"line":1571},26,[80,1573,1574],{"class":767},"\u002F\u002F stale-while-revalidate strategy. This route will be unaffected\n",[80,1576,1578],{"class":82,"line":1577},27,[80,1579,1580],{"class":767},"\u002F\u002F by navigation preload.\n",[80,1582,1584,1586,1589,1591,1593,1596],{"class":82,"line":1583},28,[80,1585,127],{"class":126},[80,1587,1588],{"class":94}," staticAssetsRoute",[80,1590,133],{"class":126},[80,1592,1499],{"class":126},[80,1594,1595],{"class":86}," Route",[80,1597,1505],{"class":136},[80,1599,1601,1604,1607,1610,1612],{"class":82,"line":1600},29,[80,1602,1603],{"class":136},"  ({ ",[80,1605,1606],{"class":546},"request",[80,1608,1609],{"class":136}," }) ",[80,1611,460],{"class":126},[80,1613,219],{"class":136},[80,1615,1617,1620,1623,1626,1628,1631,1633,1636,1639,1642],{"class":82,"line":1616},30,[80,1618,1619],{"class":126},"    return",[80,1621,1622],{"class":136}," [",[80,1624,1625],{"class":90},"\"image\"",[80,1627,932],{"class":136},[80,1629,1630],{"class":90},"\"script\"",[80,1632,932],{"class":136},[80,1634,1635],{"class":90},"\"style\"",[80,1637,1638],{"class":136},"].",[80,1640,1641],{"class":86},"includes",[80,1643,1644],{"class":136},"(request.destination);\n",[80,1646,1648],{"class":82,"line":1647},31,[80,1649,1069],{"class":136},[80,1651,1653,1655,1658],{"class":82,"line":1652},32,[80,1654,1510],{"class":126},[80,1656,1657],{"class":86}," StaleWhileRevalidate",[80,1659,1220],{"class":136},[80,1661,1663,1665,1668],{"class":82,"line":1662},33,[80,1664,1521],{"class":136},[80,1666,1667],{"class":90},"\"static-assets\"",[80,1669,43],{"class":136},[80,1671,1673],{"class":82,"line":1672},34,[80,1674,1532],{"class":136},[80,1676,1678],{"class":82,"line":1677},35,[80,1679,183],{"class":136},[80,1681,1683],{"class":82,"line":1682},36,[80,1684,150],{"emptyLinePlaceholder":149},[80,1686,1688],{"class":82,"line":1687},37,[80,1689,1690],{"class":767},"\u002F\u002F Register the route handling static assets\n",[80,1692,1694,1696],{"class":82,"line":1693},38,[80,1695,1554],{"class":86},[80,1697,1698],{"class":136},"(staticAssetsRoute);\n",[10,1700,1701],{},"Now once this service worker is activated the navigation requests should start\nin parallel to service worker boot rather than waiting for service worker to\nboot up first and thus fixing the slight delay.",[10,1703,1704,1705,1709],{},"There are many more workbox modules that can be integrated into our service\nworker. You can check out\n",[14,1706,1708],{"href":704,"rel":1707},[18],"Workbox Modules"," for a\nwhole list of amazing workbox modules.",[28,1711,1713],{"id":1712},"alternate-way-to-install-workbox-sw-pwa-in-vite","Alternate Way to install Workbox SW \u002F PWA in Vite",[10,1715,1716,1717,1719,1720,1725,1726,1731],{},"Instead of using ",[40,1718,382],{}," workbox command we can also use a custom library\ncalled ",[14,1721,1724],{"href":1722,"rel":1723},"https:\u002F\u002Fvite-pwa-org.netlify.app",[18],"PWA Vite Plugin"," that handles most of\nthe things regarding PWA \u002F installing Service Workers for us, assuming your\nproject uses Vite for build step. I highly recommend this if you just want to\nadd a SW and forget about it since this plugin requires almost zero-config to\nsetup. It comes with great integration for\n",[14,1727,1730],{"href":1728,"rel":1729},"https:\u002F\u002Fvite-pwa-org.netlify.app\u002Fframeworks\u002Fastro.html",[18],"Astro"," as well.",[10,1733,1734],{},"Further Reading \u002F References:",[1736,1737,1738,1744],"ul",{},[1739,1740,1741],"li",{},[14,1742,25],{"href":23,"rel":1743},[18],[1739,1745,1746],{},[14,1747,1749],{"href":16,"rel":1748},[18],"Service Worker MDN",[1751,1752,1753],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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 pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":76,"searchDepth":146,"depth":146,"links":1755},[1756,1757,1758],{"id":30,"depth":146,"text":31},{"id":649,"depth":146,"text":650},{"id":1712,"depth":146,"text":1713},"2023-02-17","Add a Workbox service worker to an Astro project. Generate the worker in the build, precache the static assets, pick runtime caching rules, and register it.",false,"md","2026-08-29T17:42:32+07:00",{},"\u002Fblog\u002Fworkbox-astro-project","8 min read",{"title":5,"description":1760},"blog\u002Fworkbox-astro-project","dQrXBH4_LCLeGaQBHb-hIjrzh-IQK4VnjgkMPq-bxh0",1788073289422]