Designing for a runtime that can die at any moment
Manifest V3 forces a rethink of how you keep state alive that used to be a safe assumption. What it teaches about designing systems for ephemeral environments.
For years, writing a browser extension meant assuming your background script lived as long as the browser was open. With Manifest V3, that assumption disappeared: the service worker that replaces the background script can be terminated at any time, typically within seconds of inactivity, and the browser doesn't warn you before doing it.
This isn't an arbitrary limitation. It's the same philosophy behind serverless workers or containers managed by an orchestrator: the runtime exists to execute code, not to store state, and any design that conflates the two eventually fails unpredictably.
The concrete problem
An ephemeral service worker breaks a very common pattern: keeping a persistent connection (a WebSocket, say) to receive real-time events. If the process holding that connection can die at any moment, the connection dies with it, and reconnecting has a cost, in time, and sometimes in events lost in the window between death and reconnection.
The naive solution is "just reconnect fast." The real solution requires two distinct questions:
- Who actually needs to stay alive, and why? If the answer is "I need to receive real-time events from a source I don't control," the service worker alone can't guarantee that. It needs help from another piece of the system with longer-lived guarantees.
- What happens to events that arrive while the process is dead? If the answer is "they're lost," you need to explicitly decide whether that's acceptable. If it isn't, some point in the system needs a buffer that survives the process's death.
The fix isn't fighting the runtime
The strategy that works isn't trying to forcibly keep the service worker alive (there are techniques to delay its death, but they're fragile and depend on non-guaranteed behavior). The strategy that works is accepting ephemerality as part of the design:
- Use a longer-lived auxiliary document (an offscreen document, in the case of extensions) to hold the connection that actually needs to persist, leaving the service worker as a simple event dispatcher.
- Introduce a buffer with expiration (TTL) for events that arrive while the real destination, a tab, a DOM field, doesn't exist yet or isn't ready to receive them.
- Design every message to be safe to process more than once, because in a system with frequent restarts, "exactly once" is a guarantee almost never worth chasing.
The generalization
Any system running today on third-party-managed infrastructure, serverless functions, edge workers, pods an orchestrator restarts, faces this same question, even if the specific mechanism changes. The engineering skill isn't learning the specific API of one ephemeral runtime. It's internalizing that the process is disposable, and the state that matters can't live solely inside it. Designing with that premise from the start is much cheaper than discovering it after something critical gets lost in production.