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 AsyncLocalStorage
packages/one/src/router/useInitializeOneRouter.ts - Update initialization
packages/one/src/router/linkingConfig.ts - Move linkingConfig to context
packages/one/src/server/oneServe.ts - Wrap rendering in context
packages/one/src/cli/buildPage.ts - Wrap SSG rendering in context
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
AsyncLocalStoragefor proper per-request state isolation.Current Implementation
Problem: 14+ module-level variables in
router.tspersist across requests:initialized,routeNode,rootComponentinitialState,rootState,nextState,routeInfonavigationRef,navigationRefSubscriptionlinkingConfigCurrent workaround in
oneServe.ts:184:Proposed Solution
Use
AsyncLocalStorage(already used successfully inone-server-only.tsxfor per-request headers).1. Create
routerAsyncLocalStore.ts2. Update
router.tsReplace module-level variable access with context getters.
3. Wrap SSR rendering in
oneServe.tsBenefits
one-server-only.tsx)Considerations
preloadingLoader,cssInjectFunctions) at module level for performancegetStore()- but existing code has fallback patterns)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