withastro/astro · error · AstroError
LocalsReassigned
LocalsReassigned
Error message
`locals` cannot be assigned directly.
What it means
Thrown by the locals setter on the APIContext from createContext. As with the Astro global setter, reassigning context.locals wholesale is forbidden; the shared reference must be preserved so middleware and pages see the same object.
Source
Thrown at packages/astro/src/core/middleware/index.ts:119
},
url,
get originPathname() {
return getOriginPathname(request);
},
get clientAddress() {
if (clientAddress) {
return clientAddress;
}
throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable);
},
get locals() {
if (typeof locals !== 'object') {
throw new AstroError(AstroErrorData.LocalsNotAnObject);
}
return locals;
},
set locals(_) {
throw new AstroError(AstroErrorData.LocalsReassigned);
},
session: undefined,
cache: new DisabledAstroCache(),
csp: undefined,
logger: {
info() {},
warn() {},
error() {},
},
};
return Object.assign(context, {
getActionResult: createGetActionResult(context.locals),
callAction: createCallAction(context),
});
}
/**
* Checks whether the passed `value` is serializable.View on GitHub (pinned to d081033d5f)
Solutions
- Mutate in place: `context.locals.user = user`.
- If you need a clean shape, delete keys individually instead of replacing the object.
- Initialize locals fields in the first middleware via property assignment.
Example fix
// before
export const onRequest = (ctx, next) => {
ctx.locals = { startTime: Date.now() };
return next();
};
// after
export const onRequest = (ctx, next) => {
ctx.locals.startTime = Date.now();
return next();
}; Defensive patterns
Strategy: validation
Validate before calling
// Mutate locals; never reassign in middleware.
export const onRequest = (ctx, next) => {
ctx.locals.startTime = Date.now();
return next();
}; Type guard
function assertLocalsMutable(locals: unknown): asserts locals is Record<string, unknown> {
if (typeof locals !== 'object' || locals === null) throw new Error('locals must be object');
} Try / catch
try {
ctx.locals.x = 1;
} catch (e) {
if (e instanceof Error && /cannot be assigned directly/i.test(e.message)) {
// reassignment detected; switch to property mutation
}
throw e;
} Prevention
- Use `context.locals.key = value`, never `context.locals = {...}`.
- Lint against `locals =` in middleware files.
- Reset locals by deleting keys, not replacing the object.
When it happens
Trigger: Writing `context.locals = { ... }` inside middleware. The setter `set locals(_)` in createContext throws LocalsReassigned unconditionally.
Common situations: Middleware that tries to reset locals per request by reassigning; refactored code that replaces the locals object; copy-pasted examples that assign locals.
Related errors
- LocalsNotAnObject
- LocalsReassigned
- StaticClientAddressNotAvailable
- The passed value can't be serialized.
- MiddlewareNotAResponse
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/0b37ba74e79d13c2.
Report an issue: GitHub.