Architecture¶
A Progressive Web App's architecture is two decisions made together: where and when your HTML is produced (client-side rendering, server-side rendering, static generation, incremental regeneration, streaming, islands) and what your service worker does with it (serve a cached shell, cache each page, stream a composition, or step aside). The two are inseparable, because the service worker's navigation strategy only works if it matches how the documents are built. This section covers both halves in depth: the rendering strategies and their caching consequences on this page, and then SPA versus MPA navigation, offline-first data synchronization and streaming responses on the pages it links to.
Key takeaways
- Every rendering strategy implies a navigation strategy in the service worker: CSR pairs with a cached app shell, SSR with network-first plus navigation preload, SSG with stale-while-revalidate or network-first per page, and streaming SSR with pass-through or composed streams.
- A service worker adds a caching layer in front of all the others (CDN, ISR cache, HTTP cache). Staleness compounds: a stale-while-revalidate worker in front of an ISR page with
revalidate = 3600can show content more than an hour old on a visit where both layers serve stale. respondWith(fetch(request))preserves streaming, but anything that awaitsresponse.text()or buffers the body in the worker destroys it. Cache streamed pages withresponse.clone()inwaitUntil(), never before responding.- Islands and partial prerendering split one page into a cacheable static shell and dynamic holes. The service worker can cache the shell per page and treat each island endpoint as data.
- Choose from the constraints: content versus app, need for deep-link offline, personalization, SEO, team skills and hosting. Most real PWAs end up hybrid, with a different navigation route per section of the URL space.
- The fastest navigation is still the one that never reaches your worker: the back/forward cache and speculative prerendering both bypass or pre-run it, and both favor multi-page architectures.
The two layers of a PWA architecture¶
A conventional website has one architectural axis: the rendering strategy. A PWA adds a second one, because the service worker intercepts every navigation within its scope before the browser's HTTP cache or the network sees it. The layers stack like this:
| Layer | What it decides | Who controls it | Where it is covered |
|---|---|---|---|
| Rendering | Whether HTML is produced at build time, at request time on the server, at the edge, or in the browser | Framework and hosting | This page |
| CDN and server caches | How long generated HTML is reused (s-maxage, ISR, edge caches) | Hosting configuration | HTTP Caching & Service Workers |
| Browser HTTP cache | Whether fetch() from the page or the worker reuses a stored response | Cache-Control, ETag | HTTP Caching & Service Workers |
| Service worker | Which response a navigation or subresource receives: cached, network, composed or synthesized | Your sw.js | Handling Fetch Events |
| Client runtime | Whether later navigations are new documents (MPA) or in-page route changes (SPA) | Your router, or none | SPA vs MPA PWAs |
| Data layer | Where application state lives offline and how it syncs | IndexedDB, OPFS, sync code | Offline-First Data & Sync |
The rendering strategy determines what a "page" is. The service worker determines which copy of that page the user gets. The client runtime determines how often the worker is asked. An SPA asks the worker for one document per session and then only for data and chunks; an MPA asks it for a document on every click. Every section below is about one of those combinations.
Rendering strategies compared¶
The table summarizes the six strategies on the dimensions that matter for a PWA. "Offline" describes what the strategy gives you once a service worker is in place; none of them work offline without one.
| Strategy | HTML produced | First-visit FCP / LCP | JavaScript required to show content | Natural service worker navigation strategy | Offline deep links |
|---|---|---|---|---|---|
| Client-side rendering (CSR) | In the browser, from an empty shell | Slow LCP: shell, then JS, then data | Yes | Precached shell for every navigation | Yes, if data is available locally |
| Server-side rendering (SSR) | On the server, per request | Fast FCP and LCP if the server is fast | No (hydration adds interactivity) | Network-first with navigation preload, cached fallback | Only for pages cached earlier |
| Static site generation (SSG) | At build time, one file per URL | Fastest: static files from a CDN | No | Stale-while-revalidate or network-first per page; precache key pages | For precached and visited pages |
| Incremental static regeneration (ISR) | At build time, then regenerated on the server after a revalidation interval or on demand | Same as SSG for cached pages | No | Network-first (let the server layer own freshness) | For visited pages |
| Streaming SSR | On the server, flushed in chunks as data resolves | Earliest FCP of any server strategy | No, except for out-of-order chunks that use inline scripts | Pass-through with navigation preload, or a worker-composed stream | Cached header and footer plus an offline body |
| Islands / partial prerendering | Static shell per page plus independently rendered components | Static-fast shell; islands fill in | Only inside islands | Cache the shell per page, treat island endpoints as data | Shell yes, islands only from cache |
The rest of this page goes through each strategy, what breaks when you pair it with the wrong worker strategy, and the code shape that fits.
Client-side rendering and the app shell¶
In client-side rendering, the server returns the same minimal HTML document for every URL, and a JavaScript bundle renders the route. This is the classic single-page application (SPA), and it is the architecture the app shell model was designed for: precache the shell and its assets at install, answer every in-scope navigation with the cached shell, and let the client router render the requested URL.
The service worker consequences are:
- Every navigation is a cache hit. After the first visit, launches do not depend on the network for HTML at all. FCP on repeat visits is excellent, and the app opens offline at any deep link, provided the route's data can come from IndexedDB or a cache.
- LCP depends on data, not on the shell. The shell paints fast, but the largest contentful element is usually route content that arrives only after JavaScript runs and an API call completes. The App Shell Model page quantifies this trade-off.
- The shell is frozen until the worker updates. Changes to
index.htmlreach users only when a new service worker activates. Treat shell updates as deploys of a native app, with an update prompt. See Updating Service Workers. - Server-only paths need exclusions. OAuth callbacks, API URLs opened in a tab, file downloads and server-rendered admin areas must be excluded from the fallback, or the worker answers them with the shell. Precaching & Runtime Caching lists twelve ways this fallback breaks production apps.
Do not enable navigation preload for a cache-first shell: every navigation would download HTML the worker never uses.
Server-side rendering and network-first navigations¶
In server-side rendering (SSR), the server produces complete HTML per request, often personalized with the user's session. The page is useful before JavaScript runs, which gives good first-visit LCP and makes the page robust to script failures. Most SSR frameworks then hydrate the page to attach interactivity.
For a service worker, SSR HTML is dynamic content: it changes per user and per request, so serving it from a cache by default risks showing stale or someone else's content. The fitting navigation strategy is network-first with navigation preload, with a cached copy or an offline page as the fallback:
- Navigation preload starts the HTML request in parallel with service worker start-up, so the worker adds almost no latency to the navigation. Without it, a cold worker delays the request by its start-up time, which the article that introduced navigation preload describes as usually around 50 ms, more like 250 ms on mobile, and over 500 ms in extreme cases (slow devices, a busy CPU).
- A timeout on the network keeps "lie-fi" (a connection that is up but not delivering) from hanging the navigation. After a few seconds, fall back to the cached copy of the page or to an offline page.
- Cache per page, carefully. Storing each successfully fetched page lets users reopen pages they visited while offline. Only do this for responses that are safe to replay: never for pages with CSRF tokens bound to a session that may expire, never for responses carrying
Cache-Control: no-storeorprivatecontent in shared-device scenarios unless you clear the cache at sign-out. Offline UX & Fallbacks covers per-user data clean-up.
The Static Routing API offers an alternative for SSR: a race-network-and-fetch-handler rule starts the network request and the worker at the same time and uses whichever answers first, and a plain network source skips the worker entirely for server-only paths. It ships in Chromium 123 and Safari 27, not in Firefox.
Static site generation and per-page caching¶
Static site generation (SSG) renders every URL to an HTML file at build time. The output deploys to a CDN, has no per-request server cost and is identical for every visitor, which makes it the easiest HTML to cache in a service worker.
The service worker options, in order of increasing freshness:
- Precache everything. For small sites (documentation, a handbook, a conference schedule), precache every page with a revisioned manifest. The whole site works offline after the first visit, and updates arrive atomically with each new worker. The cost is the first-visit download and the precache budget; see Precaching & Runtime Caching.
- Precache key pages, stale-while-revalidate the rest. Serve visited pages from the cache instantly and refresh them in the background. Content is one visit stale. Pair it with a "new version available" notice or a short maximum age if staleness matters.
- Network-first with a cached fallback. Always fresh when online; offline users get the last version they saw. With navigation preload, the cost over no worker at all is negligible.
Because generated file names for pages do not change between builds (the URL is the file), the revision information has to come from the build: a content hash per page in the precache manifest, or the HTTP ETag that stale-while-revalidate revalidates against. Hashed assets (/assets/app.3f9a1c.js) can be cached forever with Cache-Control: max-age=31536000, immutable and cache-first in the worker, while HTML itself should be Cache-Control: no-cache so the worker's network requests revalidate. See HTTP Caching & Service Workers.
Incremental static regeneration and compounding staleness¶
Incremental static regeneration (ISR), popularized by Next.js, serves prerendered pages from a server-side cache and regenerates them after a revalidation interval or when the application calls an on-demand revalidation function. The Next.js documentation describes the time-based behavior precisely: with export const revalidate = 60, requests within the interval get the cached page; the first request after 60 seconds still receives the stale page, and triggers regeneration in the background; later requests receive the new page once it has been generated. On-demand invalidation uses revalidatePath() or revalidateTag(), and the x-nextjs-cache response header reports HIT, STALE, MISS or REVALIDATED.
ISR is stale-while-revalidate implemented on the server. Add a stale-while-revalidate service worker in front of it and the two layers compound:
| Layer | Serves stale for up to | Refreshes when |
|---|---|---|
| Service worker, stale-while-revalidate | One visit to the page | The worker's background fetch completes |
CDN, stale-while-revalidate directive | The directive's window | The CDN's background revalidation completes |
| ISR server cache | The revalidate interval, plus one request | Regeneration succeeds (a failed regeneration keeps serving the last good page) |
In the worst case, a user sees a page from the service worker cache that was itself a stale ISR response. Content can then be older than the revalidate interval by the time between the user's visits. For content where freshness matters (prices, availability, news), use network-first in the worker and let ISR own freshness; the server layer is already fast because it serves from its own cache. Keep stale-while-revalidate in the worker for content where a visit's lag is acceptable.
Two ISR details affect the worker directly:
- On-demand revalidation is lazy and, by default, per instance. The Next.js documentation notes that
revalidatePath()invalidates cache entries but regeneration happens on the next request, and that with the default file-system cache on multiple instances, a revalidation call only invalidates the instance that receives it (a shared custom cache handler fixes that). A network-first worker therefore still sees the old page from other instances until they revalidate. - On-demand revalidation does not reach the browser.
revalidatePath("/pricing")invalidates the server's cache, but a copy in Cache Storage is untouched. If you need to push invalidations to installed clients, send a message through push or a version endpoint, and have the worker delete the matching cache entries. See Messaging & the Clients API. - Framework caching headers must not be cached forever in the worker. Treat HTML responses as revalidatable even when the CDN sends a long
s-maxage, becauses-maxageapplies to shared caches only and says nothing about how long a service worker should keep the page.
Streaming SSR and the service worker¶
Streaming SSR sends the HTML as it is generated: the server flushes the <head> and the page frame immediately, then streams the rest as data resolves. React's renderToReadableStream() with <Suspense> boundaries, SvelteKit's streamed promises, Astro's default response streaming and plain template engines that flush early all produce this pattern. Browsers parse and render HTML incrementally, so FCP happens while the server is still working.
A service worker preserves streaming only if it passes the body through:
self.addEventListener("fetch", (event) => {
if (event.request.mode !== "navigate") return;
event.respondWith(
(async () => {
// Use the preload response when navigation preload is enabled: it started
// in parallel with worker start-up, so streaming begins as early as possible.
const response = (await event.preloadResponse) || (await fetch(event.request));
if (response.ok) {
// clone() tees the body: one branch streams to the page, the other is
// read into Cache Storage in the background. Never await the cache write
// before returning, or the page waits for the whole body.
const copy = response.clone();
event.waitUntil(
caches.open("pages-v1").then((cache) => cache.put(event.request, copy)),
);
}
return response; // The body is still a stream: the browser renders chunks as they arrive.
})().catch(async () => {
// Network failure: last cached copy of this page, then the offline page.
return (
(await caches.match(event.request, { cacheName: "pages-v1" })) ||
(await caches.match("/offline.html")) ||
Response.error()
);
}),
);
});
Three rules keep streaming intact:
- Never read the body before responding.
await response.text(),await response.arrayBuffer()or rewriting the HTML with string replacement buffers the entire document in the worker; the browser receives nothing until the server has finished. If you must transform HTML, do it with aTransformStreamthat works chunk by chunk. - Clone, then cache in
waitUntil(). A tee holds unread chunks in memory for the slower branch, so an extremely slow cache write buffers data, but the page still receives chunks immediately. - Be careful caching out-of-order streams. Frameworks that stream Suspense content out of order append the resolved chunks at the end of the document with inline scripts that move them into place. A cached copy replays correctly only if those inline scripts and their assets are also available offline, and only if a Content Security Policy nonce on them is still valid; hash-based CSP avoids the nonce problem. See Content Security Policy.
A service worker can also produce a stream: it can respond with a ReadableStream that concatenates a cached header, a network (or cached) body and a cached footer, so the page frame paints from the cache while the content streams from the network. This "streaming composition" gives a multi-page site app-shell-like first paint without becoming an SPA. The Streaming Responses page covers composition, error handling mid-stream, and server support in depth.
Islands architecture and partial prerendering¶
The islands architecture renders a page as static or server-rendered HTML and hydrates only the interactive components ("islands") on it. The rest of the page ships no JavaScript. Astro is the best-known implementation: components hydrate with directives such as client:load (immediately), client:idle (when the main thread is idle) and client:visible (when the component enters the viewport).
Astro also has server islands: a component marked server:defer is left out of the page's main render and fetched separately after the page loads, with a fallback slot shown in the meantime. According to Astro's documentation, the island is fetched with a GET request to a path such as /_server-islands/Avatar, with the props encrypted into the query string; if the URL would exceed 2048 bytes, Astro sends a POST with the props in the body instead, and only the GET form can be cached with Cache-Control headers.
Next.js follows a similar idea with Cache Components (the cacheComponents option, introduced in Next.js 16), which implements partial prerendering: a static HTML shell is prerendered and served immediately, and dynamic parts wrapped in <Suspense> stream in at request time.
Both variants split a page into two cache classes, and the service worker should treat them differently:
| Part | Cache character | Service worker strategy |
|---|---|---|
| The page shell (static HTML with fallbacks) | Changes on deploy; same for every user | Stale-while-revalidate or network-first per page; cacheable offline |
| Island or dynamic-hole responses | Per user or per request | Network-first with a short timeout; cached copy only for non-personal data |
| Island JavaScript (hydration bundles) | Content-hashed, immutable | Cache-first, precached for key islands |
Offline, the shell renders and each island shows either its cached content or its fallback. That degrades well: a product page without the personalized "recommended for you" island is still a product page. Design the fallback markup so it states the offline condition rather than showing an eternal skeleton; see Offline UX & Fallbacks.
How strategies map to service worker navigation handling¶
The mapping is the single most important decision in a PWA's service worker, because navigations are the requests that determine FCP, LCP and offline coverage.
flowchart TD
A["Navigation request in scope"] --> B{"Same HTML for every URL?"}
B -- "yes: CSR shell" --> C["Serve precached shell, no navigation preload"]
B -- no --> D{"HTML personalized or fast-changing?"}
D -- yes --> E["Network-first with navigation preload and a timeout"]
E --> E2["Fallback: cached copy of this page, then offline page"]
D -- no --> F{"Small, finite set of pages?"}
F -- yes --> G["Precache all pages with a revisioned manifest"]
F -- no --> H{"Is one visit of staleness acceptable?"}
H -- yes --> I["Stale-while-revalidate per page, capped entries"]
H -- no --> E Two rules generalize across architectures:
- The worker's navigation strategy must match the server's routing. An SPA shell fallback on a site where each URL has its own document serves the wrong page. A network-first per-page strategy on an SPA wastes a round trip on every launch.
- Different parts of the URL space can use different strategies. A marketing site at
/rendered with SSG, an app at/app/rendered client-side and a blog at/blog/rendered with ISR can share one worker with three navigation routes. The next section shows that router.
Choosing an architecture¶
Start from the product, not the framework. The following questions eliminate most options quickly:
- Is it primarily content or primarily an application? Content (articles, docs, product pages, listings) is read, shared and indexed; it wants a document per URL, fast first-visit LCP and deep linking from search. Applications (editors, dashboards, messaging, tools) are used repeatedly by signed-in users; they want instant launch, rich interaction and offline data. Content leans MPA with SSG, ISR or SSR; applications lean SPA with an app shell.
- Must every deep link open offline? An app shell gives offline access to every route whose data is local. A per-page cache only covers pages that were visited or precached.
- How personalized is the HTML? Personalized HTML is hard to cache safely in the worker and impossible to cache in a shared CDN. Either render a generic shell and fetch personal data client-side (CSR, islands), or keep HTML network-first.
- How much does first-visit performance matter? Search and social traffic is mostly first visits, which have no service worker. Server-rendered HTML wins those; see Core Web Vitals.
- What does the team already run? A server-rendering framework with a CDN is a valid PWA base; so is a static SPA on object storage. The Migrating an Existing Site guide shows how to add a worker to what you have.
| Product type | Typical rendering | Navigation handling | Data layer |
|---|---|---|---|
| Documentation, handbook, reference | SSG | Precache all or stale-while-revalidate | None needed |
| News, blog, magazine | SSG or ISR, streaming SSR for fresh pages | Network-first with preload; save-for-offline articles | Reading list in IndexedDB |
| E-commerce storefront | ISR or SSR with islands | Network-first; islands for cart and personalization | Cart and queued actions in IndexedDB |
| SaaS dashboard, admin tool | CSR (SPA) or SSR plus client router | App shell | Offline-first store with sync |
| Messaging, email, notes | CSR (SPA) | App shell with streaming content | Offline-first store with sync |
| Hybrid product (marketing plus app) | SSG for marketing, CSR for the app | Per-section navigation routes | Only in the app section |
SPA vs MPA PWAs goes deeper into the navigation half of this choice, including cross-document View Transitions, Speculation Rules and the back/forward cache, which make multi-page architectures feel like apps in Chromium and Safari. When to Build a PWA covers the product-level decision of whether to build a PWA at all.
A reference architecture¶
The diagram shows a production PWA with a hybrid rendering setup, the caching layers between the user and the origin, and where application data lives.
flowchart LR
subgraph Device["User's device"]
direction TB
P["Page: document plus client runtime"]
SW["Service worker"]
CS[("Cache Storage: precache, pages, runtime")]
IDB[("IndexedDB: app data and outbox")]
HC[("HTTP cache")]
P -- "navigations and fetches" --> SW
SW --> CS
SW -- "network requests" --> HC
P --> IDB
SW --> IDB
end
subgraph Edge["CDN or edge"]
EC[("Edge cache: static files, ISR pages")]
end
subgraph Origin["Origin"]
R["Renderer: SSR, ISR, islands"]
API["JSON API and sync endpoint"]
PUSH["Push sender"]
DB[("Database")]
R --> DB
API --> DB
end
HC --> EC
EC --> R
EC --> API
PUSH -. "push service" .-> SW Each component has one responsibility:
- Build output: content-hashed assets, a precache manifest listing the shell or key pages with revisions, and a service worker file that embeds the manifest so that any change to the app changes the worker's bytes and triggers an update.
- Service worker: routes requests by type and URL. Hashed assets are cache-first. Navigations follow the strategy that matches each section's rendering. API reads are network-first or stale-while-revalidate; API writes go through an outbox when offline. It never caches authenticated responses in a way that outlives sign-out.
- Cache Storage: holds versioned caches (
precache-v42,pages-v3,images-v1) so that activation of a new worker can delete old caches atomically. Size-bounded runtime caches keep storage under control; see Storage Quotas & Persistence. - IndexedDB: holds application data and the queue of pending mutations, which is what makes the app useful offline. Offline-First Data & Sync describes the sync protocol.
- Edge and origin: the edge caches static files and ISR pages; the origin renders dynamic HTML, serves the API and sends push messages through the browser vendor's push service.
The core of that service worker is its router. The excerpt below shows only the routing decision for a hybrid site: an app shell for /app/, stale-while-revalidate for static documentation, network-first with navigation preload for the other server-rendered sections, and cache-first for hashed assets. The strategy functions it calls (networkFirstPage() with a 4-second timeout, staleWhileRevalidate(), cacheFirst()), precaching and cache cleanup are implemented in full on Caching Strategies; a complete worker you can run end to end is built step by step in Your first PWA, and a full MPA worker is on SPA vs MPA PWAs.
const PRECACHE = "precache-2026-09-25.1"; // /app/index.html, /offline.html, hashed assets
const DOCS = "docs-v1";
self.addEventListener("fetch", (event) => {
const { request } = event;
const url = new URL(request.url);
if (request.method !== "GET" || url.origin !== self.location.origin) return;
if (request.mode === "navigate") {
if (url.pathname.startsWith("/app/") && !url.pathname.startsWith("/app/auth/")) {
event.respondWith(appShell(event)); // CSR section: one cached shell for every route
} else if (url.pathname.startsWith("/docs/")) {
event.respondWith(staleWhileRevalidate(event, DOCS)); // static, rarely changes
} else {
event.respondWith(networkFirstPage(event)); // SSR: fresh HTML, cached copy offline
}
return;
}
if (url.pathname.startsWith("/assets/")) {
event.respondWith(cacheFirst(request)); // content-hashed: safe to serve forever
}
// Everything else (API calls, uploads) goes to the network untouched.
});
async function appShell(event) {
// Navigation preload is on for the whole registration; settle the unused
// preload so the browser does not log a cancelled-preload warning.
if (event.preloadResponse) {
event.waitUntil(event.preloadResponse.catch(() => undefined));
}
const shell = await caches.match("/app/index.html", { cacheName: PRECACHE });
return shell || fetch(event.request);
}
Three details of this router are deliberate trade-offs worth knowing before you copy it:
- Navigation preload is registration-wide. It cannot be enabled for
/docs/and the server-rendered sections while staying off for/app/, so every app-shell navigation still triggers a preload request whose response is thrown away. Every preload request carries aService-Worker-Navigation-Preloadheader (valuetrueunless you change it withnavigationPreload.setHeaderValue()), so the server can recognize preload requests for/app/paths and answer them with an empty204instead of the full shell. SendVary: Service-Worker-Navigation-Preloadif an intermediate cache might store either variant. The Navigation Preload page covers the header in detail. - Decide what happens to a timed-out network response. A simple
networkFirstPage()that races the network against a 4-second timeout discards the late response: it is neither used nor cached, and a user with no cached copy sees the offline page even though the network might have answered a second later. The MPA worker on SPA vs MPA PWAs keeps the network promise alive withwaitUntil(), stores the late response, and keeps waiting when nothing is cached. /app/auth/is excluded from the shell and falls through tonetworkFirstPage(), which must cache only responses withoutno-storeorprivate. Authentication callbacks should sendCache-Control: no-store; if yours do not, add the path to a network-only branch.
Production versions of this router usually come from a build tool: Workbox expresses the same routes with NavigationRoute, NetworkFirst, StaleWhileRevalidate and CacheFirst, and the Vite PWA Plugin generates the precache manifest. Caching Strategies documents each strategy's edge cases, and Pitfalls & Anti-Patterns lists the failure modes to test before shipping.
Architecture and the navigations that skip your worker¶
Some of the fastest navigations never run your navigation strategy at all, and they are part of the architectural picture:
- Back/forward cache restores. When a user goes Back to a page that the browser kept frozen in memory, no navigation request is made, so neither the service worker nor the HTTP cache is involved. web.dev reports that, according to Chrome usage data, 1 in 10 navigations on desktop and 1 in 5 on mobile are back or forward navigations. Multi-page architectures benefit from this on every page; SPAs benefit only when the user leaves and returns to the app document. Worker operations such as
clients.claim(), apostMessage()to a cached page or a new version activating evict pages from the cache in Chrome. - Speculative loads. The Speculation Rules API (Chromium; Safari 26.2 behind a flag) lets a page prefetch or prerender likely next pages. From Chrome 138, a prefetch of a URL controlled by a service worker goes through the worker's fetch handler and is stored in the prefetch cache; earlier versions cancelled such prefetches.
Both are covered in detail in SPA vs MPA PWAs, together with cross-document View Transitions.
Pages in this section¶
-
SPA vs MPA PWAs
Routing, navigation fallback versus per-page caching, cross-document View Transitions, bfcache, Speculation Rules, memory, SEO, offline behavior and a decision guide with complete worker configurations.
-
Offline-First Data & Sync
Local-first data models in IndexedDB, outbox queues, conflict resolution, sync protocols and how they integrate with Background Sync.
-
Streaming Responses
Composing streamed HTML in the service worker from cached and network parts, transform streams, error handling mid-stream and server support.
-
App Shell Model
The client-side rendering architecture in depth: shell design, precaching, routing every navigation to the shell, and the LCP trade-off.
-
Caching Strategies
Cache-first, network-first, stale-while-revalidate, cache-only and network-only with timeouts, fallbacks and production code.
-
Static Routing API
Declarative routes that skip or race the service worker for navigations and subresources in Chromium and Safari.
Further reading¶
On this site
- SPA vs MPA PWAs
- Offline-First Data & Sync
- Streaming Responses
- App Shell Model
- Navigation Preload
- Precaching & Runtime Caching
- HTTP Caching & Service Workers
- Framework Integrations
External references
- Rendering on the Web (web.dev)
- The App Shell Model (developer.chrome.com)
- Incremental Static Regeneration (Next.js documentation)
- cacheComponents (Next.js documentation)
- Islands architecture (Astro documentation)
- Server islands (Astro documentation)
- renderToReadableStream (React documentation)
- Back/forward cache (web.dev)
- Service Workers specification (W3C)