[{"data":1,"prerenderedAt":2047},["ShallowReactive",2],{"navigation":3,"\u002Fnuxt-module\u002Fpwa":393,"\u002Fnuxt-module\u002Fpwa-surround":2042},[4,14,36,69,144,376],{"title":5,"path":6,"stem":7,"children":8},"Introduction","\u002Fgetting-started","1.getting-started\u002F1.index",[9,10],{"title":5,"path":6,"stem":7},{"title":11,"path":12,"stem":13},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation",{"title":15,"path":16,"stem":17,"children":18,"page":35},"Guides","\u002Fguides","2.guides",[19,23,27,31],{"title":20,"path":21,"stem":22},"Your First Layout","\u002Fguides\u002Fyour-first-layout","2.guides\u002F1.your-first-layout",{"title":24,"path":25,"stem":26},"Your First Page Template","\u002Fguides\u002Fyour-first-page-template","2.guides\u002F2.your-first-page-template",{"title":28,"path":29,"stem":30},"Your First Component","\u002Fguides\u002Fyour-first-component","2.guides\u002F3.your-first-component",{"title":32,"path":33,"stem":34},"Alternative UI Variants","\u002Fguides\u002Falternative-ui-variants","2.guides\u002F4.alternative-ui-variants",false,{"title":37,"path":38,"stem":39,"children":40,"page":35},"Core Concepts","\u002Fcore-concepts","3.core-concepts",[41,45,49,53,57,61,65],{"title":42,"path":43,"stem":44},"How It All Works","\u002Fcore-concepts\u002Farchitecture","3.core-concepts\u002F1.architecture",{"title":46,"path":47,"stem":48},"The Data Model","\u002Fcore-concepts\u002Fthe-data-model","3.core-concepts\u002F2.the-data-model",{"title":50,"path":51,"stem":52},"Layouts & Pages","\u002Fcore-concepts\u002Flayouts-and-pages","3.core-concepts\u002F3.layouts-and-pages",{"title":54,"path":55,"stem":56},"Dynamic Pages","\u002Fcore-concepts\u002Fdynamic-pages","3.core-concepts\u002F4.dynamic-pages",{"title":58,"path":59,"stem":60},"Components","\u002Fcore-concepts\u002Fcomponents","3.core-concepts\u002F5.components",{"title":62,"path":63,"stem":64},"Draft & Publish Workflow","\u002Fcore-concepts\u002Fdraft-and-publish","3.core-concepts\u002F6.draft-and-publish",{"title":66,"path":67,"stem":68},"The Admin Panel","\u002Fcore-concepts\u002Fadmin-panel","3.core-concepts\u002F7.admin-panel",{"title":70,"path":71,"stem":72,"children":73,"page":35},"Api","\u002Fapi","4.api",[74,78,120,124,128,132,136,140],{"title":75,"path":76,"stem":77},"Bundle Setup","\u002Fapi\u002Fbundle-setup","4.api\u002F1.bundle-setup",{"title":58,"path":79,"stem":80,"children":81,"page":35},"\u002Fapi\u002Fcomponents","4.api\u002F2.components",[82,86,107],{"title":83,"path":84,"stem":85},"Creating Components","\u002Fapi\u002Fcomponents\u002Fcreating-components","4.api\u002F2.components\u002F1.creating-components",{"title":87,"path":88,"stem":89,"children":90,"page":35},"Annotations","\u002Fapi\u002Fcomponents\u002Fannotations","4.api\u002F2.components\u002F2.annotations",[91,95,99,103],{"title":92,"path":93,"stem":94},"Publishable","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fpublishable","4.api\u002F2.components\u002F2.annotations\u002F1.publishable",{"title":96,"path":97,"stem":98},"Uploadable","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fuploadable","4.api\u002F2.components\u002F2.annotations\u002F2.uploadable",{"title":100,"path":101,"stem":102},"Timestamped","\u002Fapi\u002Fcomponents\u002Fannotations\u002Ftimestamped","4.api\u002F2.components\u002F2.annotations\u002F3.timestamped",{"title":104,"path":105,"stem":106},"Explicit Allow Only","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fexplicit-allow-only","4.api\u002F2.components\u002F2.annotations\u002F4.explicit-allow-only",{"title":108,"path":109,"stem":110,"children":111,"page":35},"Built Ins","\u002Fapi\u002Fcomponents\u002Fbuilt-ins","4.api\u002F2.components\u002F3.built-ins",[112,116],{"title":113,"path":114,"stem":115},"Collection Component","\u002Fapi\u002Fcomponents\u002Fbuilt-ins\u002Fcollection-component","4.api\u002F2.components\u002F3.built-ins\u002F1.collection-component",{"title":117,"path":118,"stem":119},"Form Component","\u002Fapi\u002Fcomponents\u002Fbuilt-ins\u002Fform-component","4.api\u002F2.components\u002F3.built-ins\u002F2.form-component",{"title":121,"path":122,"stem":123},"Dynamic & Nested Pages","\u002Fapi\u002Fdynamic-pages","4.api\u002F3.dynamic-pages",{"title":125,"path":126,"stem":127},"Users & Security","\u002Fapi\u002Fusers-and-security","4.api\u002F4.users-and-security",{"title":129,"path":130,"stem":131},"Data Fixtures","\u002Fapi\u002Fdata-fixtures","4.api\u002F5.data-fixtures",{"title":133,"path":134,"stem":135},"Configuration Reference","\u002Fapi\u002Fconfiguration","4.api\u002F6.configuration",{"title":137,"path":138,"stem":139},"Console Commands","\u002Fapi\u002Fconsole-commands","4.api\u002F7.console-commands",{"title":141,"path":142,"stem":143},"Debugging & Profiler","\u002Fapi\u002Fdebugging","4.api\u002F8.debugging",{"title":145,"path":146,"stem":147,"children":148,"page":35},"Nuxt Module","\u002Fnuxt-module","5.nuxt-module",[149,153,157,170,190,211,244,328,352,356],{"title":150,"path":151,"stem":152},"Module Setup","\u002Fnuxt-module\u002Fmodule-setup","5.nuxt-module\u002F1.module-setup",{"title":154,"path":155,"stem":156},"PWA & Offline","\u002Fnuxt-module\u002Fpwa","5.nuxt-module\u002F10.pwa",{"title":158,"path":159,"stem":160,"children":161,"page":35},"Configuration","\u002Fnuxt-module\u002Fconfiguration","5.nuxt-module\u002F2.configuration",[162,166],{"title":163,"path":164,"stem":165},"Nuxt Config","\u002Fnuxt-module\u002Fconfiguration\u002Fnuxt-config","5.nuxt-module\u002F2.configuration\u002F1.nuxt-config",{"title":167,"path":168,"stem":169},"Site Config & SEO","\u002Fnuxt-module\u002Fconfiguration\u002Fsite-config-and-seo","5.nuxt-module\u002F2.configuration\u002F2.site-config-and-seo",{"title":171,"path":172,"stem":173,"children":174,"page":35},"Building Your Ui","\u002Fnuxt-module\u002Fbuilding-your-ui","5.nuxt-module\u002F3.building-your-ui",[175,179,183,186],{"title":176,"path":177,"stem":178},"Layouts","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-layouts","5.nuxt-module\u002F3.building-your-ui\u002F1.creating-layouts",{"title":180,"path":181,"stem":182},"Page Templates","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-page-templates","5.nuxt-module\u002F3.building-your-ui\u002F2.creating-page-templates",{"title":83,"path":184,"stem":185},"\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-components","5.nuxt-module\u002F3.building-your-ui\u002F3.creating-components",{"title":187,"path":188,"stem":189},"CLI Generator","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcwa-cli","5.nuxt-module\u002F3.building-your-ui\u002F4.cwa-cli",{"title":191,"path":192,"stem":193,"children":194,"page":35},"Cwa Components","\u002Fnuxt-module\u002Fcwa-components","5.nuxt-module\u002F4.cwa-components",[195,199,203,207],{"title":196,"path":197,"stem":198},"\u003CCwaComponentGroup \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-component-group","5.nuxt-module\u002F4.cwa-components\u002F1.cwa-component-group",{"title":200,"path":201,"stem":202},"\u003CCwaPage \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-page","5.nuxt-module\u002F4.cwa-components\u002F2.cwa-page",{"title":204,"path":205,"stem":206},"\u003CCwaLink \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-link","5.nuxt-module\u002F4.cwa-components\u002F3.cwa-link",{"title":208,"path":209,"stem":210},"\u003CCwaDefaultLayout \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-default-layout","5.nuxt-module\u002F4.cwa-components\u002F5.cwa-default-layout",{"title":212,"path":213,"stem":214,"children":215,"page":35},"Cwa Api","\u002Fnuxt-module\u002Fcwa-api","5.nuxt-module\u002F5.cwa-api",[216,220,224,228,232,236,240],{"title":217,"path":218,"stem":219},"Overview","\u002Fnuxt-module\u002Fcwa-api\u002Foverview","5.nuxt-module\u002F5.cwa-api\u002F1.overview",{"title":221,"path":222,"stem":223},"Resources","\u002Fnuxt-module\u002Fcwa-api\u002Fresources","5.nuxt-module\u002F5.cwa-api\u002F2.resources",{"title":225,"path":226,"stem":227},"Resources Manager","\u002Fnuxt-module\u002Fcwa-api\u002Fresources-manager","5.nuxt-module\u002F5.cwa-api\u002F3.resources-manager",{"title":229,"path":230,"stem":231},"Auth","\u002Fnuxt-module\u002Fcwa-api\u002Fauth","5.nuxt-module\u002F5.cwa-api\u002F4.auth",{"title":233,"path":234,"stem":235},"Forms","\u002Fnuxt-module\u002Fcwa-api\u002Fforms","5.nuxt-module\u002F5.cwa-api\u002F5.forms",{"title":237,"path":238,"stem":239},"Admin","\u002Fnuxt-module\u002Fcwa-api\u002Fadmin","5.nuxt-module\u002F5.cwa-api\u002F6.admin",{"title":241,"path":242,"stem":243},"Site Config","\u002Fnuxt-module\u002Fcwa-api\u002Fsite-config","5.nuxt-module\u002F5.cwa-api\u002F7.site-config",{"title":245,"path":246,"stem":247,"children":248,"page":35},"Composables","\u002Fnuxt-module\u002Fcomposables","5.nuxt-module\u002F6.composables",[249,257,294,311],{"title":250,"path":251,"stem":252,"children":253,"page":35},"Layout","\u002Fnuxt-module\u002Fcomposables\u002Flayout","5.nuxt-module\u002F6.composables\u002F0.layout",[254],{"title":250,"path":255,"stem":256},"\u002Fnuxt-module\u002Fcomposables\u002Flayout\u002Fuse-cwa-layout","5.nuxt-module\u002F6.composables\u002F0.layout\u002F1.use-cwa-layout",{"title":258,"path":259,"stem":260,"children":261,"page":35},"Component","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent","5.nuxt-module\u002F6.composables\u002F1.component",[262,266,270,274,278,282,286,290],{"title":263,"path":264,"stem":265},"Component (recommended)","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-component","5.nuxt-module\u002F6.composables\u002F1.component\u002F0.use-cwa-component",{"title":267,"path":268,"stem":269},"Resource","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-resource","5.nuxt-module\u002F6.composables\u002F1.component\u002F1.use-cwa-resource",{"title":271,"path":272,"stem":273},"Collection Resource","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-collection-resource","5.nuxt-module\u002F6.composables\u002F1.component\u002F2.use-cwa-collection-resource",{"title":275,"path":276,"stem":277},"File Field","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-file-field","5.nuxt-module\u002F6.composables\u002F1.component\u002F3.use-cwa-file-field",{"title":279,"path":280,"stem":281},"Form","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-form","5.nuxt-module\u002F6.composables\u002F1.component\u002F4.use-cwa-form",{"title":283,"path":284,"stem":285},"Form Input","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-form-input","5.nuxt-module\u002F6.composables\u002F1.component\u002F5.use-cwa-form-input",{"title":287,"path":288,"stem":289},"Form Repeated","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-form-repeated","5.nuxt-module\u002F6.composables\u002F1.component\u002F6.use-cwa-form-repeated",{"title":291,"path":292,"stem":293},"Form Collection","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-form-collection","5.nuxt-module\u002F6.composables\u002F1.component\u002F7.use-cwa-form-collection",{"title":295,"path":296,"stem":297,"children":298,"page":35},"Admin Manager","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager","5.nuxt-module\u002F6.composables\u002F2.admin-manager",[299,303,307],{"title":300,"path":301,"stem":302},"Manager Tab","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-manager-tab","5.nuxt-module\u002F6.composables\u002F2.admin-manager\u002F1.use-cwa-resource-manager-tab",{"title":304,"path":305,"stem":306},"Resource Model","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-model","5.nuxt-module\u002F6.composables\u002F2.admin-manager\u002F2.use-cwa-resource-model",{"title":308,"path":309,"stem":310},"Resource Upload","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-upload","5.nuxt-module\u002F6.composables\u002F2.admin-manager\u002F3.use-cwa-resource-upload",{"title":312,"path":313,"stem":314,"children":315,"page":35},"Utilities","\u002Fnuxt-module\u002Fcomposables\u002Futilities","5.nuxt-module\u002F6.composables\u002F3.utilities",[316,320,324],{"title":317,"path":318,"stem":319},"Resource Endpoint","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-cwa-resource-endpoint","5.nuxt-module\u002F6.composables\u002F3.utilities\u002F1.use-cwa-resource-endpoint",{"title":321,"path":322,"stem":323},"Query Model","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-query-bound-model","5.nuxt-module\u002F6.composables\u002F3.utilities\u002F2.use-query-bound-model",{"title":325,"path":326,"stem":327},"Resource Route","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-cwa-resource-route","5.nuxt-module\u002F6.composables\u002F3.utilities\u002F3.use-cwa-resource-route",{"title":329,"path":330,"stem":331,"children":332,"page":35},"Component Helpers","\u002Fnuxt-module\u002Fcomponent-helpers","5.nuxt-module\u002F7.component-helpers",[333,337,341,345,349],{"title":334,"path":335,"stem":336},"Images & Media","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fimages-and-uploads","5.nuxt-module\u002F7.component-helpers\u002F1.images-and-uploads",{"title":338,"path":339,"stem":340},"Collections & Pagination","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fcollections-and-pagination","5.nuxt-module\u002F7.component-helpers\u002F2.collections-and-pagination",{"title":342,"path":343,"stem":344},"HTML Content","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fhtml-content","5.nuxt-module\u002F7.component-helpers\u002F3.html-content",{"title":346,"path":347,"stem":348},"Real-Time Updates","\u002Fnuxt-module\u002Fcomponent-helpers\u002Freal-time-updates","5.nuxt-module\u002F7.component-helpers\u002F4.real-time-updates",{"title":233,"path":350,"stem":351},"\u002Fnuxt-module\u002Fcomponent-helpers\u002Fforms","5.nuxt-module\u002F7.component-helpers\u002F5.forms",{"title":353,"path":354,"stem":355},"Authentication","\u002Fnuxt-module\u002Fauthentication","5.nuxt-module\u002F8.authentication",{"title":357,"path":358,"stem":359,"children":360,"page":35},"Cwa Layer","\u002Fnuxt-module\u002Fcwa-layer","5.nuxt-module\u002F9.cwa-layer",[361,364,368,372],{"title":217,"path":362,"stem":363},"\u002Fnuxt-module\u002Fcwa-layer\u002Foverview","5.nuxt-module\u002F9.cwa-layer\u002F1.overview",{"title":365,"path":366,"stem":367},"Auth Pages","\u002Fnuxt-module\u002Fcwa-layer\u002Fauth-pages","5.nuxt-module\u002F9.cwa-layer\u002F2.auth-pages",{"title":369,"path":370,"stem":371},"Admin Panel","\u002Fnuxt-module\u002Fcwa-layer\u002Fadmin-panel","5.nuxt-module\u002F9.cwa-layer\u002F3.admin-panel",{"title":373,"path":374,"stem":375},"Styling","\u002Fnuxt-module\u002Fcwa-layer\u002Fstyling","5.nuxt-module\u002F9.cwa-layer\u002F4.styling",{"title":377,"path":378,"stem":379,"children":380,"page":35},"Deployment","\u002Fdeployment","6.deployment",[381,385,389],{"title":382,"path":383,"stem":384},"Docker","\u002Fdeployment\u002Fdocker","6.deployment\u002F1.docker",{"title":386,"path":387,"stem":388},"Kubernetes & Helm","\u002Fdeployment\u002Fkubernetes","6.deployment\u002F2.kubernetes",{"title":390,"path":391,"stem":392},"CI\u002FCD","\u002Fdeployment\u002Fci-cd","6.deployment\u002F3.ci-cd",{"id":394,"title":395,"badge":396,"body":399,"description":2035,"extension":2036,"links":2037,"meta":2038,"navigation":2039,"path":155,"seo":2040,"stem":156,"__hash__":2041},"docs\u002F5.nuxt-module\u002F10.pwa.md","Progressive Web App & Offline",{"label":397,"color":398},"Draft","amber",{"type":400,"value":401,"toc":2022},"minimark",[402,422,443,463,468,471,547,553,557,586,657,664,668,678,733,737,750,1341,1344,1402,1433,1446,1460,1476,1480,1499,1552,1667,1682,1694,1698,1720,1929,1948,1952,1962,1969,1973,1976,2007,2013,2018],[403,404,405,406,410,411,421],"p",{},"CWA has no built-in PWA. Offline support is ",[407,408,409],"strong",{},"opt-in"," and assembled in your own app with ",[412,413,417],"a",{"href":414,"rel":415},"https:\u002F\u002Fvite-pwa-org.netlify.app\u002Fframeworks\u002Fnuxt",[416],"nofollow",[418,419,420],"code",{},"@vite-pwa\u002Fnuxt"," — the module deliberately doesn't ship it as a dependency, so you stay in control of the service worker.",[403,423,424,425,428,429,433,434,437,438,442],{},"The subtlety, and the reason this needs its own guide, is that ",[407,426,427],{},"CWA API responses are auth-scoped",". A signed-in admin and a signed-out visitor request the ",[430,431,432],"em",{},"same URLs"," — ",[418,435,436],{},"\u002F_\u002Froutes\u002F{path}",", the resource manifest, a component — and the API returns the draft or the published view based on the auth cookie, not the URL (",[412,439,441],{"href":440},"\u002Fapi\u002Fconfiguration#http-cache-safety","see the API cache-safety design","). A naive service worker that caches \"the API\" by URL pattern will eventually store an admin's draft and serve it to the public. Everything below exists to make caching safe in the face of that.",[444,445,448],"callout",{"icon":446,"color":447},"i-heroicons-exclamation-triangle","warning",[403,449,450,451,454,455,458,459,462],{},"Do ",[407,452,453],{},"not"," cache CWA API responses by URL denylist (e.g. \"skip anything under ",[418,456,457],{},"\u002Fadmin","\"). Draft and published share a URL, so no pattern can tell them apart. Safety comes from the ",[407,460,461],{},"API marking each response"," and the service worker honouring that mark — never from the front-end guessing.",[464,465,467],"h2",{"id":466},"the-shape","The shape",[403,469,470],{},"Three tiers, each caching a different kind of thing:",[472,473,474,490],"table",{},[475,476,477],"thead",{},[478,479,480,484,487],"tr",{},[481,482,483],"th",{},"Tier",[481,485,486],{},"Caches",[481,488,489],{},"Where",[491,492,493,511,530],"tbody",{},[478,494,495,501,504],{},[496,497,498],"td",{},[407,499,500],{},"App-shell precache",[496,502,503],{},"Your built JS\u002FCSS\u002Ffonts\u002Ficons",[496,505,506,507,510],{},"Service worker (",[418,508,509],{},"globPatterns",")",[478,512,513,518,524],{},[496,514,515],{},[407,516,517],{},"CWA API runtime cache",[496,519,520,521],{},"Safe (public) API responses, read ",[407,522,523],{},"offline only",[496,525,506,526,529],{},[418,527,528],{},"runtimeCaching"," + a safety gate)",[478,531,532,541,544],{},[496,533,534,537,538],{},[407,535,536],{},"Page-side persistence"," ",[430,539,540],{},"(advanced)",[496,542,543],{},"Auth-aware data the SW must not hold",[496,545,546],{},"Your app (IndexedDB)",[403,548,549,550,552],{},"The first two are the ",[418,551,420],{}," config below. The third is an optional pattern you build yourself — covered at the end.",[464,554,556],{"id":555},"install","Install",[558,559,564],"pre",{"className":560,"code":561,"language":562,"meta":563,"style":563},"language-bash shiki shiki-themes github-light github-dark material-theme-palenight","pnpm add -D @vite-pwa\u002Fnuxt\n","bash","",[418,565,566],{"__ignoreMap":563},[567,568,571,575,579,583],"span",{"class":569,"line":570},"line",1,[567,572,574],{"class":573},"sRCss","pnpm",[567,576,578],{"class":577},"sLL54"," add",[567,580,582],{"class":581},"szhYu"," -D",[567,584,585],{"class":577}," @vite-pwa\u002Fnuxt\n",[558,587,591],{"className":588,"code":589,"language":590,"meta":563,"style":563},"language-ts shiki shiki-themes github-light github-dark material-theme-palenight","\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@vite-pwa\u002Fnuxt'],\n})\n","ts",[418,592,593,599,621,648],{"__ignoreMap":563},[567,594,595],{"class":569,"line":570},[567,596,598],{"class":597},"sTBSN","\u002F\u002F nuxt.config.ts\n",[567,600,602,606,609,613,617],{"class":569,"line":601},2,[567,603,605],{"class":604},"sm4w6","export",[567,607,608],{"class":604}," default",[567,610,612],{"class":611},"sKpYG"," defineNuxtConfig",[567,614,616],{"class":615},"sPB8G","(",[567,618,620],{"class":619},"sOvfz","{\n",[567,622,624,628,631,634,638,640,642,645],{"class":569,"line":623},3,[567,625,627],{"class":626},"sDHlG","  modules",[567,629,630],{"class":619},":",[567,632,633],{"class":615}," [",[567,635,637],{"class":636},"seSrl","'",[567,639,420],{"class":577},[567,641,637],{"class":636},[567,643,644],{"class":615},"]",[567,646,647],{"class":619},",\n",[567,649,651,654],{"class":569,"line":650},4,[567,652,653],{"class":619},"}",[567,655,656],{"class":615},")\n",[403,658,659,660,663],{},"Add it ",[430,661,662],{},"alongside"," the CWA module — it is app-level, never a CWA dependency.",[464,665,667],{"id":666},"tier-1-app-shell-precache","Tier 1 — App-shell precache",[403,669,670,671,673,674,677],{},"Precaching your own build output is always safe — it has no auth dimension. This is the ",[418,672,509],{}," in the ",[418,675,676],{},"workbox"," block:",[558,679,681],{"className":588,"code":680,"language":590,"meta":563,"style":563},"pwa: {\n  workbox: {\n    globPatterns: ['**\u002F*.{js,mjs,ts,json,css,html,png,svg,ico,jpg,jpeg,webp}'],\n  },\n}\n",[418,682,683,693,702,722,727],{"__ignoreMap":563},[567,684,685,688,690],{"class":569,"line":570},[567,686,687],{"class":573},"pwa",[567,689,630],{"class":619},[567,691,692],{"class":619}," {\n",[567,694,695,698,700],{"class":569,"line":601},[567,696,697],{"class":573},"  workbox",[567,699,630],{"class":619},[567,701,692],{"class":619},[567,703,704,707,709,711,713,716,718,720],{"class":569,"line":623},[567,705,706],{"class":573},"    globPatterns",[567,708,630],{"class":619},[567,710,633],{"class":626},[567,712,637],{"class":636},[567,714,715],{"class":577},"**\u002F*.{js,mjs,ts,json,css,html,png,svg,ico,jpg,jpeg,webp}",[567,717,637],{"class":636},[567,719,644],{"class":626},[567,721,647],{"class":619},[567,723,724],{"class":569,"line":650},[567,725,726],{"class":619},"  },\n",[567,728,730],{"class":569,"line":729},5,[567,731,732],{"class":619},"}\n",[464,734,736],{"id":735},"tier-2-cwa-api-runtime-cache","Tier 2 — CWA API runtime cache",[403,738,739,740,745,746,749],{},"This is the load-bearing part. Cache CWA's content endpoints with ",[407,741,742],{},[418,743,744],{},"NetworkFirst",", gated by a ",[418,747,748],{},"cacheWillUpdate"," plugin that drops any response the API marked non-cacheable:",[558,751,753],{"className":588,"code":752,"language":590,"meta":563,"style":563},"\u002F\u002F nuxt.config.ts\npwa: {\n  registerType: 'prompt',\n  workbox: {\n    \u002F\u002F MUST be present — see the callout below\n    navigateFallback: null,\n    globPatterns: ['**\u002F*.{js,mjs,ts,json,css,html,png,svg,ico,jpg,jpeg,webp}'],\n    runtimeCaching: [\n      {\n        \u002F\u002F Anchored to CWA's content paths only — NOT a broad API-origin match,\n        \u002F\u002F so the Mercure SSE stream is never swallowed (that would break real-time updates).\n        urlPattern: ({ url }) =>\n          \u002F\\\u002F_api\\\u002F(?:_\\\u002F(?:routes|resource_manifest|pages|layouts|component_groups|component_positions)|page_data|component)\\b\u002F.test(url.pathname),\n        handler: 'NetworkFirst',\n        options: {\n          cacheName: 'cwa-api',\n          networkTimeoutSeconds: 3,\n          cacheableResponse: { statuses: [200], headers: {} },\n          plugins: [\n            {\n              \u002F\u002F The safety gate. The API marks an authenticated admin's draft view\n              \u002F\u002F `Cache-Control: private, no-store`; returning null here means \"don't store it\",\n              \u002F\u002F so the cache only ever holds public, published responses.\n              cacheWillUpdate: async ({ response }) => {\n                const cc = response.headers.get('cache-control') || ''\n                if (\u002Fno-store|private\u002F.test(cc)) {\n                  return null\n                }\n                return response.status === 200 ? response : null\n              },\n            },\n          ],\n          expiration: { maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 },\n        },\n      },\n    ],\n  },\n}\n",[418,754,755,759,767,784,792,797,811,830,841,847,853,859,881,978,994,1004,1021,1035,1072,1082,1088,1094,1100,1106,1129,1172,1207,1216,1222,1251,1257,1263,1271,1311,1317,1323,1331,1336],{"__ignoreMap":563},[567,756,757],{"class":569,"line":570},[567,758,598],{"class":597},[567,760,761,763,765],{"class":569,"line":601},[567,762,687],{"class":573},[567,764,630],{"class":619},[567,766,692],{"class":619},[567,768,769,772,774,777,780,782],{"class":569,"line":623},[567,770,771],{"class":573},"  registerType",[567,773,630],{"class":619},[567,775,776],{"class":636}," '",[567,778,779],{"class":577},"prompt",[567,781,637],{"class":636},[567,783,647],{"class":619},[567,785,786,788,790],{"class":569,"line":650},[567,787,697],{"class":573},[567,789,630],{"class":619},[567,791,692],{"class":619},[567,793,794],{"class":569,"line":729},[567,795,796],{"class":597},"    \u002F\u002F MUST be present — see the callout below\n",[567,798,800,803,805,809],{"class":569,"line":799},6,[567,801,802],{"class":573},"    navigateFallback",[567,804,630],{"class":619},[567,806,808],{"class":807},"sBtbT"," null",[567,810,647],{"class":619},[567,812,814,816,818,820,822,824,826,828],{"class":569,"line":813},7,[567,815,706],{"class":573},[567,817,630],{"class":619},[567,819,633],{"class":626},[567,821,637],{"class":636},[567,823,715],{"class":577},[567,825,637],{"class":636},[567,827,644],{"class":626},[567,829,647],{"class":619},[567,831,833,836,838],{"class":569,"line":832},8,[567,834,835],{"class":573},"    runtimeCaching",[567,837,630],{"class":619},[567,839,840],{"class":626}," [\n",[567,842,844],{"class":569,"line":843},9,[567,845,846],{"class":619},"      {\n",[567,848,850],{"class":569,"line":849},10,[567,851,852],{"class":597},"        \u002F\u002F Anchored to CWA's content paths only — NOT a broad API-origin match,\n",[567,854,856],{"class":569,"line":855},11,[567,857,858],{"class":597},"        \u002F\u002F so the Mercure SSE stream is never swallowed (that would break real-time updates).\n",[567,860,862,865,867,870,874,877],{"class":569,"line":861},12,[567,863,864],{"class":611},"        urlPattern",[567,866,630],{"class":619},[567,868,869],{"class":619}," ({",[567,871,873],{"class":872},"sNTR6"," url",[567,875,876],{"class":619}," })",[567,878,880],{"class":879},"swB56"," =>\n",[567,882,884,887,891,895,897,901,904,906,908,911,915,918,920,923,925,928,930,933,935,938,940,942,945,947,950,952,955,958,961,964,966,969,971,974,976],{"class":569,"line":883},13,[567,885,886],{"class":636},"          \u002F",[567,888,890],{"class":889},"sx-5U","\\\u002F",[567,892,894],{"class":893},"sM1SQ","_api",[567,896,890],{"class":889},[567,898,900],{"class":899},"stFFh","(?:",[567,902,903],{"class":893},"_",[567,905,890],{"class":889},[567,907,900],{"class":899},[567,909,910],{"class":893},"routes",[567,912,914],{"class":913},"sVlFx","|",[567,916,917],{"class":893},"resource_manifest",[567,919,914],{"class":913},[567,921,922],{"class":893},"pages",[567,924,914],{"class":913},[567,926,927],{"class":893},"layouts",[567,929,914],{"class":913},[567,931,932],{"class":893},"component_groups",[567,934,914],{"class":913},[567,936,937],{"class":893},"component_positions",[567,939,510],{"class":899},[567,941,914],{"class":913},[567,943,944],{"class":893},"page_data",[567,946,914],{"class":913},[567,948,949],{"class":893},"component",[567,951,510],{"class":899},[567,953,954],{"class":604},"\\b",[567,956,957],{"class":636},"\u002F",[567,959,960],{"class":619},".",[567,962,963],{"class":611},"test",[567,965,616],{"class":626},[567,967,968],{"class":615},"url",[567,970,960],{"class":619},[567,972,973],{"class":615},"pathname",[567,975,510],{"class":626},[567,977,647],{"class":619},[567,979,981,984,986,988,990,992],{"class":569,"line":980},14,[567,982,983],{"class":626},"        handler",[567,985,630],{"class":619},[567,987,776],{"class":636},[567,989,744],{"class":577},[567,991,637],{"class":636},[567,993,647],{"class":619},[567,995,997,1000,1002],{"class":569,"line":996},15,[567,998,999],{"class":626},"        options",[567,1001,630],{"class":619},[567,1003,692],{"class":619},[567,1005,1007,1010,1012,1014,1017,1019],{"class":569,"line":1006},16,[567,1008,1009],{"class":626},"          cacheName",[567,1011,630],{"class":619},[567,1013,776],{"class":636},[567,1015,1016],{"class":577},"cwa-api",[567,1018,637],{"class":636},[567,1020,647],{"class":619},[567,1022,1024,1027,1029,1033],{"class":569,"line":1023},17,[567,1025,1026],{"class":626},"          networkTimeoutSeconds",[567,1028,630],{"class":619},[567,1030,1032],{"class":1031},"scSvc"," 3",[567,1034,647],{"class":619},[567,1036,1038,1041,1043,1046,1049,1051,1053,1056,1058,1061,1064,1066,1069],{"class":569,"line":1037},18,[567,1039,1040],{"class":626},"          cacheableResponse",[567,1042,630],{"class":619},[567,1044,1045],{"class":619}," {",[567,1047,1048],{"class":626}," statuses",[567,1050,630],{"class":619},[567,1052,633],{"class":626},[567,1054,1055],{"class":1031},"200",[567,1057,644],{"class":626},[567,1059,1060],{"class":619},",",[567,1062,1063],{"class":626}," headers",[567,1065,630],{"class":619},[567,1067,1068],{"class":619}," {}",[567,1070,1071],{"class":619}," },\n",[567,1073,1075,1078,1080],{"class":569,"line":1074},19,[567,1076,1077],{"class":626},"          plugins",[567,1079,630],{"class":619},[567,1081,840],{"class":626},[567,1083,1085],{"class":569,"line":1084},20,[567,1086,1087],{"class":619},"            {\n",[567,1089,1091],{"class":569,"line":1090},21,[567,1092,1093],{"class":597},"              \u002F\u002F The safety gate. The API marks an authenticated admin's draft view\n",[567,1095,1097],{"class":569,"line":1096},22,[567,1098,1099],{"class":597},"              \u002F\u002F `Cache-Control: private, no-store`; returning null here means \"don't store it\",\n",[567,1101,1103],{"class":569,"line":1102},23,[567,1104,1105],{"class":597},"              \u002F\u002F so the cache only ever holds public, published responses.\n",[567,1107,1109,1112,1114,1117,1119,1122,1124,1127],{"class":569,"line":1108},24,[567,1110,1111],{"class":611},"              cacheWillUpdate",[567,1113,630],{"class":619},[567,1115,1116],{"class":879}," async",[567,1118,869],{"class":619},[567,1120,1121],{"class":872}," response",[567,1123,876],{"class":619},[567,1125,1126],{"class":879}," =>",[567,1128,692],{"class":619},[567,1130,1132,1135,1139,1142,1144,1146,1149,1151,1154,1156,1158,1161,1163,1166,1169],{"class":569,"line":1131},25,[567,1133,1134],{"class":879},"                const",[567,1136,1138],{"class":1137},"sc2zw"," cc",[567,1140,1141],{"class":913}," =",[567,1143,1121],{"class":615},[567,1145,960],{"class":619},[567,1147,1148],{"class":615},"headers",[567,1150,960],{"class":619},[567,1152,1153],{"class":611},"get",[567,1155,616],{"class":626},[567,1157,637],{"class":636},[567,1159,1160],{"class":577},"cache-control",[567,1162,637],{"class":636},[567,1164,1165],{"class":626},") ",[567,1167,1168],{"class":913},"||",[567,1170,1171],{"class":636}," ''\n",[567,1173,1175,1178,1181,1183,1186,1188,1191,1193,1195,1197,1199,1202,1205],{"class":569,"line":1174},26,[567,1176,1177],{"class":604},"                if",[567,1179,1180],{"class":626}," (",[567,1182,957],{"class":636},[567,1184,1185],{"class":893},"no-store",[567,1187,914],{"class":913},[567,1189,1190],{"class":893},"private",[567,1192,957],{"class":636},[567,1194,960],{"class":619},[567,1196,963],{"class":611},[567,1198,616],{"class":626},[567,1200,1201],{"class":615},"cc",[567,1203,1204],{"class":626},")) ",[567,1206,620],{"class":619},[567,1208,1210,1213],{"class":569,"line":1209},27,[567,1211,1212],{"class":604},"                  return",[567,1214,1215],{"class":807}," null\n",[567,1217,1219],{"class":569,"line":1218},28,[567,1220,1221],{"class":619},"                }\n",[567,1223,1225,1228,1230,1232,1235,1238,1241,1244,1246,1249],{"class":569,"line":1224},29,[567,1226,1227],{"class":604},"                return",[567,1229,1121],{"class":615},[567,1231,960],{"class":619},[567,1233,1234],{"class":615},"status",[567,1236,1237],{"class":913}," ===",[567,1239,1240],{"class":1031}," 200",[567,1242,1243],{"class":913}," ?",[567,1245,1121],{"class":615},[567,1247,1248],{"class":913}," :",[567,1250,1215],{"class":807},[567,1252,1254],{"class":569,"line":1253},30,[567,1255,1256],{"class":619},"              },\n",[567,1258,1260],{"class":569,"line":1259},31,[567,1261,1262],{"class":619},"            },\n",[567,1264,1266,1269],{"class":569,"line":1265},32,[567,1267,1268],{"class":626},"          ]",[567,1270,647],{"class":619},[567,1272,1274,1277,1279,1281,1284,1286,1289,1291,1294,1296,1299,1302,1304,1306,1309],{"class":569,"line":1273},33,[567,1275,1276],{"class":626},"          expiration",[567,1278,630],{"class":619},[567,1280,1045],{"class":619},[567,1282,1283],{"class":626}," maxEntries",[567,1285,630],{"class":619},[567,1287,1288],{"class":1031}," 100",[567,1290,1060],{"class":619},[567,1292,1293],{"class":626}," maxAgeSeconds",[567,1295,630],{"class":619},[567,1297,1298],{"class":1031}," 60",[567,1300,1301],{"class":913}," *",[567,1303,1298],{"class":1031},[567,1305,1301],{"class":913},[567,1307,1308],{"class":1031}," 24",[567,1310,1071],{"class":619},[567,1312,1314],{"class":569,"line":1313},34,[567,1315,1316],{"class":619},"        },\n",[567,1318,1320],{"class":569,"line":1319},35,[567,1321,1322],{"class":619},"      },\n",[567,1324,1326,1329],{"class":569,"line":1325},36,[567,1327,1328],{"class":626},"    ]",[567,1330,647],{"class":619},[567,1332,1334],{"class":569,"line":1333},37,[567,1335,726],{"class":619},[567,1337,1339],{"class":569,"line":1338},38,[567,1340,732],{"class":619},[403,1342,1343],{},"Three things make this safe rather than dangerous:",[1345,1346,1347,1363,1390],"ul",{},[1348,1349,1350,1358,1359,1362],"li",{},[407,1351,1352,1354,1355,960],{},[418,1353,744],{},", not ",[418,1356,1357],{},"StaleWhileRevalidate"," The cache is only ever ",[430,1360,1361],{},"read"," when the network fails — i.e. offline. An online visitor always gets a fresh response, so no one is served a stale (or wrongly-scoped) page while connected.",[1348,1364,1365,1371,1372,1375,1376,1378,1379,1381,1382,1385,1386,1389],{},[407,1366,1367,1368,1370],{},"The ",[418,1369,748],{}," gate."," It reads ",[418,1373,1374],{},"Cache-Control"," and refuses to store anything marked ",[418,1377,1185],{}," or ",[418,1380,1190],{},". That mark comes from the API (",[412,1383,1384],{"href":440},"cache-safety headers",") — an authenticated admin's draft view is ",[418,1387,1388],{},"private, no-store",", so it never enters the cache. Only public, published responses are stored.",[1348,1391,1392,1398,1399,1401],{},[407,1393,1394,1395,960],{},"The narrow ",[418,1396,1397],{},"urlPattern"," It matches CWA's content resource paths, deliberately ",[430,1400,453],{}," the whole API origin, so the Mercure Server-Sent-Events stream is left alone. A broad pattern would intercept the SSE connection and kill real-time updates.",[444,1403,1404],{"icon":446,"color":447},[403,1405,1406,1413,1414,1417,1418,1420,1421,1424,1425,1428,1429,1432],{},[407,1407,1408,1409,1412],{},"Always set ",[418,1410,1411],{},"navigateFallback"," explicitly"," — write ",[418,1415,1416],{},"navigateFallback: null"," if you don't want one. ",[418,1419,420],{}," checks for the key's ",[430,1422,1423],{},"presence",", not its value: omit it entirely and it silently defaults to ",[418,1426,1427],{},"'\u002F'",", serving your app shell for ",[407,1430,1431],{},"every"," SSR navigation. The presence of the key is what disables the fallback.",[1434,1435,1437,1438,1441,1442,1445],"h3",{"id":1436},"cache-layering-the-s-maxage-max-age-split","Cache layering: the ",[418,1439,1440],{},"s-maxage"," \u002F ",[418,1443,1444],{},"max-age"," split",[403,1447,1448,1449,1453,1454,1459],{},"For this to work, the API must set a long ",[407,1450,1451],{},[418,1452,1440],{}," (for the shared edge cache, e.g. Souin — purgeable) together with ",[407,1455,1456],{},[418,1457,1458],{},"max-age: 0"," (for the browser). The CWA template's production API config does exactly this.",[403,1461,1462,1463,1465,1466,1469,1470,1472,1473,1475],{},"Why it matters: if the API instead sent a long ",[418,1464,1444],{},", the browser's own HTTP cache would keep an un-purgeable copy, and a Workbox ",[418,1467,1468],{},"fetch()"," would be served that stale copy without ever reaching the shared cache — so ",[418,1471,744],{}," would hand back stale content it never had a chance to revalidate. Keep ",[418,1474,1458],{},"; let the shared cache hold the long-lived, purgeable copy.",[1434,1477,1479],{"id":1478},"purge-on-sign-out-and-on-401","Purge on sign-out and on 401",[403,1481,1482,1484,1485,1488,1489,1492,1493,1495,1496,630],{},[418,1483,744],{}," closes most of the risk, but not one window: a cached public response can outlive a ",[407,1486,1487],{},"sign-out"," or a ",[407,1490,1491],{},"session expiry"," on a shared device, and be read offline afterwards. Close it by having the page tell the service worker to drop the ",[418,1494,1016],{}," cache on sign-out and on any ",[418,1497,1498],{},"401",[558,1500,1502],{"className":588,"code":1501,"language":590,"meta":563,"style":563},"\u002F\u002F after sign-out, or on a 401 from the API\nnavigator.serviceWorker?.controller?.postMessage({ type: 'CWA_PURGE_API_CACHE' })\n",[418,1503,1504,1509],{"__ignoreMap":563},[567,1505,1506],{"class":569,"line":570},[567,1507,1508],{"class":597},"\u002F\u002F after sign-out, or on a 401 from the API\n",[567,1510,1511,1514,1516,1519,1522,1525,1527,1530,1532,1535,1538,1540,1542,1545,1547,1550],{"class":569,"line":601},[567,1512,1513],{"class":615},"navigator",[567,1515,960],{"class":619},[567,1517,1518],{"class":615},"serviceWorker",[567,1520,1521],{"class":619},"?.",[567,1523,1524],{"class":615},"controller",[567,1526,1521],{"class":619},[567,1528,1529],{"class":611},"postMessage",[567,1531,616],{"class":615},[567,1533,1534],{"class":619},"{",[567,1536,1537],{"class":626}," type",[567,1539,630],{"class":619},[567,1541,776],{"class":636},[567,1543,1544],{"class":577},"CWA_PURGE_API_CACHE",[567,1546,637],{"class":636},[567,1548,1549],{"class":619}," }",[567,1551,656],{"class":615},[558,1553,1555],{"className":588,"code":1554,"language":590,"meta":563,"style":563},"\u002F\u002F in your service worker\nself.addEventListener('message', (event) => {\n  if (event.data?.type === 'CWA_PURGE_API_CACHE') {\n    event.waitUntil(caches.delete('cwa-api'))\n  }\n})\n",[418,1556,1557,1562,1594,1625,1656,1661],{"__ignoreMap":563},[567,1558,1559],{"class":569,"line":570},[567,1560,1561],{"class":597},"\u002F\u002F in your service worker\n",[567,1563,1564,1567,1569,1572,1574,1576,1579,1581,1583,1585,1588,1590,1592],{"class":569,"line":601},[567,1565,1566],{"class":615},"self",[567,1568,960],{"class":619},[567,1570,1571],{"class":611},"addEventListener",[567,1573,616],{"class":615},[567,1575,637],{"class":636},[567,1577,1578],{"class":577},"message",[567,1580,637],{"class":636},[567,1582,1060],{"class":619},[567,1584,1180],{"class":619},[567,1586,1587],{"class":872},"event",[567,1589,510],{"class":619},[567,1591,1126],{"class":879},[567,1593,692],{"class":619},[567,1595,1596,1599,1601,1603,1605,1608,1610,1613,1615,1617,1619,1621,1623],{"class":569,"line":623},[567,1597,1598],{"class":604},"  if",[567,1600,1180],{"class":626},[567,1602,1587],{"class":615},[567,1604,960],{"class":619},[567,1606,1607],{"class":615},"data",[567,1609,1521],{"class":619},[567,1611,1612],{"class":615},"type",[567,1614,1237],{"class":913},[567,1616,776],{"class":636},[567,1618,1544],{"class":577},[567,1620,637],{"class":636},[567,1622,1165],{"class":626},[567,1624,620],{"class":619},[567,1626,1627,1630,1632,1635,1637,1640,1642,1645,1647,1649,1651,1653],{"class":569,"line":650},[567,1628,1629],{"class":615},"    event",[567,1631,960],{"class":619},[567,1633,1634],{"class":611},"waitUntil",[567,1636,616],{"class":626},[567,1638,1639],{"class":615},"caches",[567,1641,960],{"class":619},[567,1643,1644],{"class":611},"delete",[567,1646,616],{"class":626},[567,1648,637],{"class":636},[567,1650,1016],{"class":577},[567,1652,637],{"class":636},[567,1654,1655],{"class":626},"))\n",[567,1657,1658],{"class":569,"line":729},[567,1659,1660],{"class":619},"  }\n",[567,1662,1663,1665],{"class":569,"line":799},[567,1664,653],{"class":619},[567,1666,656],{"class":615},[403,1668,1669,1670,1673,1674,1677,1678,1681],{},"Do this from the ",[407,1671,1672],{},"page",", not the service worker — the page can read ",[418,1675,1676],{},"$cwa.auth.signedIn",", whereas an auth flag held inside the SW fails ",[430,1679,1680],{},"open"," when the worker restarts.",[444,1683,1685],{"icon":1684},"i-heroicons-photo",[403,1686,1687,1688,1690,1691,1693],{},"Diagram: request flow through the three tiers — browser → SW (",[418,1689,748],{}," gate) → shared edge cache (Souin) → API — showing where a ",[418,1692,1388],{}," response is dropped.",[464,1695,1697],{"id":1696},"update-prompt","Update prompt",[403,1699,1700,1701,1704,1705,1707,1708,1711,1712,1715,1716,1719],{},"Use ",[418,1702,1703],{},"registerType: 'prompt'"," (above) so a new deploy doesn't silently swap the app out from under an admin mid-edit. ",[418,1706,420],{}," auto-imports a ",[418,1709,1710],{},"usePWA()"," composable exposing ",[418,1713,1714],{},"$pwa.needRefresh"," and ",[418,1717,1718],{},"$pwa.updateServiceWorker()","; gate the prompt on edit state:",[558,1721,1725],{"className":1722,"code":1723,"language":1724,"meta":563,"style":563},"language-vue shiki shiki-themes github-light github-dark material-theme-palenight","\u003Cscript setup lang=\"ts\">\nconst { $pwa } = usePWA()\nconst cwa = useCwa()\n\n\u002F\u002F Only offer to reload when it won't interrupt an edit\nconst canPrompt = computed(() => $pwa?.needRefresh && !cwa.admin.isEditing.value)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUButton v-if=\"canPrompt\" @click=\"$pwa.updateServiceWorker(true)\">\n    Update available — reload\n  \u003C\u002FUButton>\n\u003C\u002Ftemplate>\n","vue",[418,1726,1727,1756,1776,1790,1796,1801,1851,1860,1864,1873,1907,1912,1921],{"__ignoreMap":563},[567,1728,1729,1732,1736,1740,1743,1746,1749,1751,1753],{"class":569,"line":570},[567,1730,1731],{"class":619},"\u003C",[567,1733,1735],{"class":1734},"s-h7I","script",[567,1737,1739],{"class":1738},"sGtlX"," setup",[567,1741,1742],{"class":1738}," lang",[567,1744,1745],{"class":619},"=",[567,1747,1748],{"class":636},"\"",[567,1750,590],{"class":577},[567,1752,1748],{"class":636},[567,1754,1755],{"class":619},">\n",[567,1757,1758,1761,1763,1766,1768,1770,1773],{"class":569,"line":601},[567,1759,1760],{"class":879},"const",[567,1762,1045],{"class":619},[567,1764,1765],{"class":1137}," $pwa",[567,1767,1549],{"class":619},[567,1769,1141],{"class":913},[567,1771,1772],{"class":611}," usePWA",[567,1774,1775],{"class":615},"()\n",[567,1777,1778,1780,1783,1785,1788],{"class":569,"line":623},[567,1779,1760],{"class":879},[567,1781,1782],{"class":1137}," cwa",[567,1784,1141],{"class":913},[567,1786,1787],{"class":611}," useCwa",[567,1789,1775],{"class":615},[567,1791,1792],{"class":569,"line":650},[567,1793,1795],{"emptyLinePlaceholder":1794},true,"\n",[567,1797,1798],{"class":569,"line":729},[567,1799,1800],{"class":597},"\u002F\u002F Only offer to reload when it won't interrupt an edit\n",[567,1802,1803,1805,1808,1810,1813,1815,1818,1820,1822,1824,1827,1830,1833,1836,1838,1841,1843,1846,1848],{"class":569,"line":799},[567,1804,1760],{"class":879},[567,1806,1807],{"class":1137}," canPrompt",[567,1809,1141],{"class":913},[567,1811,1812],{"class":611}," computed",[567,1814,616],{"class":615},[567,1816,1817],{"class":619},"()",[567,1819,1126],{"class":879},[567,1821,1765],{"class":615},[567,1823,1521],{"class":619},[567,1825,1826],{"class":615},"needRefresh ",[567,1828,1829],{"class":913},"&&",[567,1831,1832],{"class":913}," !",[567,1834,1835],{"class":615},"cwa",[567,1837,960],{"class":619},[567,1839,1840],{"class":615},"admin",[567,1842,960],{"class":619},[567,1844,1845],{"class":615},"isEditing",[567,1847,960],{"class":619},[567,1849,1850],{"class":615},"value)\n",[567,1852,1853,1856,1858],{"class":569,"line":813},[567,1854,1855],{"class":619},"\u003C\u002F",[567,1857,1735],{"class":1734},[567,1859,1755],{"class":619},[567,1861,1862],{"class":569,"line":832},[567,1863,1795],{"emptyLinePlaceholder":1794},[567,1865,1866,1868,1871],{"class":569,"line":843},[567,1867,1731],{"class":619},[567,1869,1870],{"class":1734},"template",[567,1872,1755],{"class":619},[567,1874,1875,1878,1881,1884,1886,1888,1891,1893,1896,1898,1900,1903,1905],{"class":569,"line":849},[567,1876,1877],{"class":619},"  \u003C",[567,1879,1880],{"class":1734},"UButton",[567,1882,1883],{"class":1738}," v-if",[567,1885,1745],{"class":619},[567,1887,1748],{"class":636},[567,1889,1890],{"class":577},"canPrompt",[567,1892,1748],{"class":636},[567,1894,1895],{"class":1738}," @click",[567,1897,1745],{"class":619},[567,1899,1748],{"class":636},[567,1901,1902],{"class":577},"$pwa.updateServiceWorker(true)",[567,1904,1748],{"class":636},[567,1906,1755],{"class":619},[567,1908,1909],{"class":569,"line":855},[567,1910,1911],{"class":615},"    Update available — reload\n",[567,1913,1914,1917,1919],{"class":569,"line":861},[567,1915,1916],{"class":619},"  \u003C\u002F",[567,1918,1880],{"class":1734},[567,1920,1755],{"class":619},[567,1922,1923,1925,1927],{"class":569,"line":883},[567,1924,1855],{"class":619},[567,1926,1870],{"class":1734},[567,1928,1755],{"class":619},[444,1930,1932],{"icon":1931},"i-heroicons-information-circle",[403,1933,1934,1936,1937,1939,1940,1943,1944,1947],{},[418,1935,1710],{}," is provided by ",[418,1938,420],{},", not CWA — the snippet above is a pattern to adapt, not a component the module ships. ",[418,1941,1942],{},"$pwa"," is client-only, so guard for ",[418,1945,1946],{},"undefined"," during SSR.",[464,1949,1951],{"id":1950},"tier-3-page-side-persistence-advanced","Tier 3 — Page-side persistence (advanced)",[403,1953,1954,1955,1958,1959,1961],{},"The service-worker cache holds only public responses. For ",[407,1956,1957],{},"auth-aware"," data that must survive offline but must never sit in a shared SW cache — a signed-in user's own view — persist it from the page instead, where you can read ",[418,1960,1676],{}," and decide whether it's safe to store.",[403,1963,1964,1965,1968],{},"The module's in-memory ",[412,1966,1967],{"href":347},"route cache"," (route-keyed, bounded, non-reactive) is the natural thing to persist to IndexedDB for this. This is a pattern you build yourself — the module keeps the cache in memory only and ships no persistence layer. Treat it as complementary to the SW cache, not a replacement.",[464,1970,1972],{"id":1971},"offline-and-real-time-updates","Offline and real-time updates",[403,1974,1975],{},"Be clear-eyed about the limits when a client goes offline and comes back:",[1345,1977,1978,1992],{},[1348,1979,1980,1981,1984,1985,1988,1989,1991],{},"CWA's Mercure client opens an ",[418,1982,1983],{},"EventSource"," and handles incoming messages, but it has ",[407,1986,1987],{},"no reconnect, error, or online\u002Foffline handling"," of its own — it relies on the browser's native ",[418,1990,1983],{}," reconnection.",[1348,1993,1994,1995,1998,1999,2002,2003,2006],{},"That native reconnect only ",[430,1996,1997],{},"backfills"," missed events if your Mercure hub runs an event store (replaying via ",[418,2000,2001],{},"Last-Event-ID","). Without one, ",[407,2004,2005],{},"updates that happened while offline are lost",", and a cached resource can stay stale until the next full fetch of that route.",[403,2008,2009,2010,2012],{},"So treat SW\u002Foffline caching as \"the app still opens and shows last-known content offline\", ",[407,2011,453],{}," \"the app self-heals to live state on reconnect\". If you need guaranteed freshness after reconnection, trigger a re-fetch of the current route yourself.",[444,2014,2015],{"icon":1684},[403,2016,2017],{},"Screenshot: the app loaded offline (DevTools → Network → Offline) showing last-known content and an offline indicator.",[2019,2020,2021],"style",{},"html pre.shiki code .sRCss, html code.shiki .sRCss{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#FFCB6B}html pre.shiki code .sLL54, html code.shiki .sLL54{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#C3E88D}html pre.shiki code .szhYu, html code.shiki .szhYu{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#C3E88D}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);}html pre.shiki code .sTBSN, html code.shiki .sTBSN{--shiki-light:#6A737D;--shiki-light-font-style:inherit;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sm4w6, html code.shiki .sm4w6{--shiki-light:#D73A49;--shiki-light-font-style:inherit;--shiki-default:#F97583;--shiki-default-font-style:inherit;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sKpYG, html code.shiki .sKpYG{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#82AAFF}html pre.shiki code .sPB8G, html code.shiki .sPB8G{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#BABED8}html pre.shiki code .sOvfz, html code.shiki .sOvfz{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#89DDFF}html pre.shiki code .sDHlG, html code.shiki .sDHlG{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#F07178}html pre.shiki code .seSrl, html code.shiki .seSrl{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#89DDFF}html pre.shiki code .sBtbT, html code.shiki .sBtbT{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#89DDFF}html pre.shiki code .sNTR6, html code.shiki .sNTR6{--shiki-light:#E36209;--shiki-light-font-style:inherit;--shiki-default:#FFAB70;--shiki-default-font-style:inherit;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .swB56, html code.shiki .swB56{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#C792EA}html pre.shiki code .sx-5U, html code.shiki .sx-5U{--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-default:#85E89D;--shiki-default-font-weight:bold;--shiki-dark:#BABED8;--shiki-dark-font-weight:inherit}html pre.shiki code .sM1SQ, html code.shiki .sM1SQ{--shiki-light:#032F62;--shiki-default:#DBEDFF;--shiki-dark:#C3E88D}html pre.shiki code .stFFh, html code.shiki .stFFh{--shiki-light:#032F62;--shiki-default:#DBEDFF;--shiki-dark:#89DDFF}html pre.shiki code .sVlFx, html code.shiki .sVlFx{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#89DDFF}html pre.shiki code .scSvc, html code.shiki .scSvc{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#F78C6C}html pre.shiki code .sc2zw, html code.shiki .sc2zw{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#BABED8}html pre.shiki code .s-h7I, html code.shiki .s-h7I{--shiki-light:#22863A;--shiki-default:#85E89D;--shiki-dark:#F07178}html pre.shiki code .sGtlX, html code.shiki .sGtlX{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#C792EA}",{"title":563,"searchDepth":601,"depth":601,"links":2023},[2024,2025,2026,2027,2032,2033,2034],{"id":466,"depth":601,"text":467},{"id":555,"depth":601,"text":556},{"id":666,"depth":601,"text":667},{"id":735,"depth":601,"text":736,"children":2028},[2029,2031],{"id":1436,"depth":623,"text":2030},"Cache layering: the s-maxage \u002F max-age split",{"id":1478,"depth":623,"text":1479},{"id":1696,"depth":601,"text":1697},{"id":1950,"depth":601,"text":1951},{"id":1971,"depth":601,"text":1972},"Add safe service-worker caching and offline support to a CWA app — without ever serving a cached draft to the public.","md",null,{},{"title":154},{"title":395,"description":2035},"r1UP4QjkuCZdsMJBfmeENtNGrgzXr_htTh3ulw8XvY8",[2043,2045],{"title":150,"path":151,"stem":152,"description":2044,"children":-1},"Installing the CWA Nuxt module, configuring peer dependencies, and wiring up the nuxt.config options.",{"title":163,"path":164,"stem":165,"description":2046,"children":-1},"[object Object]",1784323346142]