-
-
Notifications
You must be signed in to change notification settings - Fork 116
Open
Description
Summary
Currently, the router uses module-level state that persists across SSR requests. We work around this by calling globalThis['__vxrnresetState']?.() before each render, but this doesn't handle concurrent requests safely.
This issue tracks refactoring to use AsyncLocalStorage for proper per-request state isolation.
Current Implementation
Problem: 14+ module-level variables in router.ts persist across requests:
initialized,routeNode,rootComponent- Navigation state:
initialState,rootState,nextState,routeInfo navigationRef,navigationRefSubscription- 3 subscriber Set collections
linkingConfig
Current workaround in oneServe.ts:184:
// Reset router state for each SSR request to ensure correct routing
// TODO: Consider using AsyncLocalStorage to isolate router state per request
// instead of using global reset, for better concurrency handling
globalThis['__vxrnresetState']?.()Proposed Solution
Use AsyncLocalStorage (already used successfully in one-server-only.tsx for per-request headers).
1. Create routerAsyncLocalStore.ts
import { AsyncLocalStorage } from 'node:async_hooks'
interface RouterContextState {
initialized: boolean
routeNode: RouteNode | null
rootState: NavigationState | undefined
nextState: ResultState | undefined
routeInfo: UrlObject | undefined
navigationRef: NavigationContainerRef<any> | null
// ... other state
}
export const ROUTER_CONTEXT_STORE = new AsyncLocalStorage<RouterContextState>()
export function getRouterContext() {
return ROUTER_CONTEXT_STORE.getStore()
}
export function runWithRouterContext<T>(fn: () => T): T {
return ROUTER_CONTEXT_STORE.run(createInitialContext(), fn)
}2. Update router.ts
Replace module-level variable access with context getters.
3. Wrap SSR rendering in oneServe.ts
const response = await runWithRouterContext(async () => {
const rendered = await (await getRender())({ ... })
return new Response(rendered, { headers, status })
})Benefits
- True request isolation (no state leakage between concurrent requests)
- Concurrency safe for parallel SSR requests
- Automatic cleanup per request
- Removes manual reset workaround
- Follows existing pattern in codebase (
one-server-only.tsx)
Considerations
- Keep cacheable data (
preloadingLoader,cssInjectFunctions) at module level for performance - Need fallback for Vercel Lambda (doesn't support
getStore()- but existing code has fallback patterns) - Only applies to SSR path, not client-side
Files to Modify
packages/one/src/router/router.ts- Move state to AsyncLocalStoragepackages/one/src/router/useInitializeOneRouter.ts- Update initializationpackages/one/src/router/linkingConfig.ts- Move linkingConfig to contextpackages/one/src/server/oneServe.ts- Wrap rendering in contextpackages/one/src/cli/buildPage.ts- Wrap SSG rendering in context
Metadata
Metadata
Assignees
Labels
No labels