Svelte / Intermediate: Kit routes and server data policy / Lesson 11
Context, stores and request-state isolation
Choose state sharing by owner and lifetime rather than exporting mutable private state from a server module.
On this page
Lesson goals
- Distinguish component context, stores and request-owned server data.
- Preserve current-value access and nested ownership across component reuse.
A shared API does not imply global shared private state
A store or reactive module can expose a useful state interface, but its lifetime depends on where the instance is created. A module-level mutable user store on the server can be shared by unrelated requests. Component context provides a scoped way for descendants to obtain a value created by an ancestor, while event.locals belongs to a server request. These scopes solve different problems. Runes reduce some historical reasons to reach for stores, but stores remain useful for supported subscription-oriented interfaces. Do not replace every store mechanically or assume a rune variable is automatically request-isolated when exported from a singleton server module.
Provide current data through a scoped getter
The layout fragment sets context during component initialization and provides a getter for current catalog data. It assumes a shared stable token module and framework-supplied data/children props. Passing a getter avoids treating the initial data value as permanently current when Kit reuses the layout. A descendant must consume the same token and read the value in an appropriate reactive context. No component context was created here. The getter is not a credential verifier and must return only the permitted view model, not a server client or private configuration object.
<script>
import { setContext } from "svelte";
import { CATALOG_CONTEXT } from "#lib/context-key.js";
let { data, children } = $props();
setContext(CATALOG_CONTEXT, () => data.catalog);
</script>
{@render children()}
The shared token and public data contract are assumed. No context lookup or layout reuse was executed.
Keep subscription and mutable-reference ownership explicit
A store contract includes subscription behavior and cleanup, not just a value field. Automatic store syntax belongs to documented compiled contexts; a plain imported object with a similar name does not become a valid store. Context also does not deep-copy values: descendants can share the same mutable object deliberately or accidentally. Define which data is immutable and which state belongs to one component subtree or request. If an object is replaced, preserve the intended current-value relationship instead of leaving consumers with an old reference. Test lifetime and unsubscribe behavior, not merely that a first value appears.
Allocate independent nested state for each owner
The local factory creates a fresh principal wrapper and Map for each synthetic owner. Editing one draft set leaves the other untouched. This tests ordinary allocation and explicitly avoids shared nested mutable state. It is not a Svelte context, store or SSR test. A real application may safely share immutable service definitions, but not request-dependent credentials or drafts without an explicit protocol. Keep immutable values and one-operation state distinguished in the design; a fresh outer wrapper alone cannot isolate a shared inner object. Verify actual framework ownership and cross-request behavior separately.
function owner(id){return {principal:{id},drafts:new Map()};}
const a=owner("p1"),b=owner("p2");
a.drafts.set("c1","Draft A");
console.assert(a!==b && a.principal!==b.principal);
console.assert(a.drafts!==b.drafts && !b.drafts.has("c1"));
The two owners do not share principal wrappers or draft maps. No server requests or component contexts were created.
Align account changes, cleanup and server lifetimes
Retire old-account client state and reject incompatible pending results after login/logout changes. Keep server secrets in server-only modules and event-owned context, and review cache keys if any data is shared across callers. Test sequential and overlapping principals, component reuse, object replacement and unsubscribe cleanup in the actual authorized integration. This course executes only the allocation model; it does not run context, store subscriptions, SSR, logout or navigation. Record that limitation so a clean local Map test is not called proof that a deployed private store is isolated under concurrency.
Common errors and fixes
A server module store leaks another userβs state
- Why it happens
- Request-dependent mutable data lived in a shared module instance.
- How to fix it
- Create private state under its request or component owner.
- Prevent it next time
- Test overlapping and sequential principals.
A context consumer keeps an old catalog reference
- Why it happens
- Initial value capture was mistaken for current reactive access.
- How to fix it
- Provide a deliberate getter or stable reactive owner contract.
- Prevent it next time
- Test replacement and layout reuse without reload.
A fresh wrapper still shares editable inner state
- Why it happens
- Only outer identity changed while nested mutable references remained shared.
- How to fix it
- Define nested ownership and allocate or freeze values deliberately.
- Prevent it next time
- Assert nested mutation isolation, not only a!==b.
Check your understanding
1. Why is a new outer object not enough for request isolation?
Show worked solution
It may still point to the same nested principal, cache or draft collection. A shallow wrapper does not deep-copy dependencies. Define immutable shared values separately from mutable state owned by one operation, then test nested changes explicitly. Verify the real SSR/context allocation rather than inferring it from a local factory.
2. Are stores obsolete simply because Svelte 5 has runes?
Show worked solution
No. Runes cover many reactive-state use cases, while stores remain useful for their documented subscription interfaces and integrations. Choose by ownership, lifecycle and consumers rather than a blanket replacement rule. Both approaches can be unsafe if private server state is accidentally shared across requests.
Key takeaways
- State sharing needs a named owner and lifetime.
- Context and stores do not automatically isolate nested private state.