This proposal is an early design sketch by Google Chrome Loading team to describe the problem below and solicit feedback on the proposed solution. It has not been approved to ship in Chrome.
- Google Chrome Loading team
- Introduction
- Goals
- Non-goals
- User research
- Use cases
- Synthetic Response for navigations via the Static Routing API
- Detailed design discussion
- Security and Privacy Considerations
- Stakeholder Feedback / Opposition
- References & acknowledgements
The Service Worker Static Routing API (https://github.com/WICG/service-worker-static-routing-api) provides a mechanism to bypass Service Worker bootstrap by declaratively routing requests. While this solves the "Service Worker Startup" tax, navigation performance is still limited by network latency.
Currently, even with a static route, the browser process must wait for the first bytes of a network response before it can create/find a renderer process and commit the navigation. This "Network-to-Commit" gap can often take hundreds of milliseconds.
Service Worker Synthetic Response extends the Static Routing API to eliminate this gap. It allows the browser to immediately serve a cached "synthetic" response (like an App Shell) to trigger the renderer commit phase instantly, while the dynamic content is fetched from the network in the background.
- Early Navigation Commit: Trigger the renderer's commit phase immediately using cached data, without waiting for a network round-trip.
- Parallelize Renderer Initialization and Network Fetch: Overlap the time spent setting up the renderer process with the time spent waiting for the server.
- Improved Loading Metrics: By committing the static part of the response immediately, the browser can start initializing the renderer process, which includes the document creation or JavaScript engine startup. This results in parsing resources (CSS/JS) and painting the UI sooner.
- Handling Non-GET Requests: The synthetic response mechanism is intended for navigation requests (requestMode: "navigate"), which are primarily GET requests. Handling POST or other non-idempotent methods is not a goal.
- Full Body Merging Logic: The synthetic response proposes a solution to merge responses. But particularly how to merge two response bodies might be handled by other proposals e.g. Declarative partial updates.
- Offline Solution: While it utilizes the cache, it is primarily a performance optimization for connected navigations. It is not intended to replace full offline-first PWA architectures.
There are many websites using Service Worker App Shell architecture. We can frame the synthetic response as a native support of App Shell.
The Problem: The Network-to-Commit Gap Even with Static Routing API, standard navigation follows these steps.
- Browser initiates a network request.
- Idle Time: Browser waits for the server to respond (TTFB).
- Browser receives the response.
- Renderer Setup: Browser finds/creates a renderer process and starts the Commit phase.
- Renderer begins loading and parsing.
The renderer is idle during step 2. For users on slow or high-latency networks, this gap is the primary source of perceived slowness.
self.addEventListener("install", e => {
e.addRoutes({
condition: { requestMode: "navigate" },
source: {
syntheticResponse: {
cacheName: "static-resources",
contentPath: "/app-shell.html",
fallbackPath: “/error-page.html”,
timeout: 3000
}
}
});
});Key Performance Benefits:
- Unblocking the Renderer: The renderer's life cycle (Find -> Commit -> Load) is moved much earlier in the timeline, often occurring before the server has even finished processing the request.
- Decoupling from TTFB: Navigation commitment becomes a local operation (cache hit) rather than a network-bound operation.
- Optimized Resource Discovery: By committing the shell early, the renderer can discover and start fetching static sub-resources (like site-wide CSS or fonts) defined in the of the shell while waiting for the primary body content.
This allows the browser to commit a full static UI shell while waiting for the body content.
self.addEventListener("install", e => {
e.addRoutes({
condition: { requestMode: "navigate" },
source: {
syntheticResponse: {
cacheName: "static-resources",
contentPath: "/app-shell.html", // Served immediately to trigger commit
fallbackPath: “/error-page.html”,
timeout: 3000
}
}
});
});The simplest optimization: commit the renderer with just the cached headers. This allows the renderer to begin pre-initializing (e.g., preparing for the specific Content-Type) while the entire body comes from the network.
self.addEventListener("install", e => {
e.addRoutes({
condition: { requestMode: "navigate" },
source: {
syntheticResponse: {
cacheName: "v1",
httpHeaderPath: "/_header" // Minimal headers to unblock commit
}
}
});
});- Body Merging Strategy: Defining exactly where the network body starts and the cached shell ends. Declarative partial updates proposal might be a solution.
- CSP Nonce Consistency: Handling Content Security Policy nonces that are typically unique per-request but are now partially served from a static cache. Using tag is one workaround, but it may be better to provide a way to inform browser generated nonce strings for CSP to servers, and encourage servers to use them.
- Server Signaling: Providing a way for the server to know it only needs to send the "body" part (e.g., via a specialized request header).
The synthetic response has the same level of capability that the current Service Worker and Service Worker Static Routing API can do. Please refer the Security and Privacy section of the Service Worker Static Routing API proposal.
This proposal was presented at Web Applications WG TPAC 2025. There were no clear objections. More detailed design and proposal were requested to evaluate that this is really the case that existing Service Worker APIs can't achieve.