{"record":{"id":"5847eadb40289381","repo":"withastro/astro","slug":"localsreassigned","errorCode":"LocalsReassigned","errorMessage":"`locals` cannot be assigned directly.","messagePattern":"`locals` cannot be assigned directly\\.","errorType":"exception","errorClass":"AstroError","httpStatus":null,"severity":"error","filePath":"packages/astro/src/core/fetch/fetch-state.ts","lineNumber":1127,"sourceCode":"\n\t\tconst ctx = {\n\t\t\tget cookies() {\n\t\t\t\treturn state.cookies;\n\t\t\t},\n\t\t\troutePattern: this.routeData!.route,\n\t\t\tisPrerendered: this.routeData!.prerender,\n\t\t\tget clientAddress() {\n\t\t\t\treturn state.getClientAddress();\n\t\t\t},\n\t\t\tget currentLocale() {\n\t\t\t\treturn state.computeCurrentLocale();\n\t\t\t},\n\t\t\tgenerator: ASTRO_GENERATOR,\n\t\t\tget locals() {\n\t\t\t\treturn state.locals;\n\t\t\t},\n\t\t\tset locals(_) {\n\t\t\t\tthrow new AstroError(AstroErrorData.LocalsReassigned);\n\t\t\t},\n\t\t\t// SAFETY: getActionAPIContext is only called after route resolution,\n\t\t\t// so routeData is always set and the params getter always returns a value.\n\t\t\tparams: this.params!,\n\t\t\tget preferredLocale() {\n\t\t\t\treturn state.computePreferredLocale();\n\t\t\t},\n\t\t\tget preferredLocaleList() {\n\t\t\t\treturn state.computePreferredLocaleList();\n\t\t\t},\n\t\t\trequest: this.request,\n\t\t\tsite: this.pipeline.site,\n\t\t\turl: this.url,\n\t\t\tget originPathname() {\n\t\t\t\treturn getOriginPathname(state.request);\n\t\t\t},\n\t\t\tget csp() {\n\t\t\t\treturn state.getCsp();","sourceCodeStart":1109,"sourceCodeEnd":1145,"githubUrl":"https://github.com/withastro/astro/blob/d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca/packages/astro/src/core/fetch/fetch-state.ts#L1109-L1145","documentation":"Thrown by the setter on the Astro global's `locals` property. Astro.locals is meant to be mutated in place (Astro.locals.user = x), not reassigned wholesale, because the context holds a stable reference shared across middleware and the page. Reassigning would break that shared reference.","triggerScenarios":"Writing `Astro.locals = { ... }` (assignment to the property itself) inside a page, layout, endpoint, or action. The setter defined in createAstroPagePartial throws AstroErrorData.LocalsReassigned unconditionally.","commonSituations":"Developers used to initializing locals fresh per request who write `Astro.locals = {}`; refactoring middleware that does `context.locals = something` (the same guard exists on the middleware context); copying example code that reassigns locals.","solutions":["Mutate instead of reassign: set individual properties, e.g. `Astro.locals.user = user`.","If you need to reset, delete keys individually or assign to existing properties rather than replacing the object.","Initialize locals shape in middleware via context.locals.x = value, never context.locals = {...}."],"exampleFix":"// before\nAstro.locals = { user };\n\n// after\nAstro.locals.user = user;","handlingStrategy":"validation","validationCode":"// Never reassign; always mutate properties.\nAstro.locals.user = user;            // ok\nAstro.locals = { user };             // forbidden","typeGuard":"// locals must be an object you mutate, not replace.\nfunction assertLocalsMutable(locals: unknown): asserts locals is Record<string, unknown> {\n  if (typeof locals !== 'object' || locals === null) {\n    throw new Error('locals must be an object');\n  }\n}","tryCatchPattern":"try {\n  Astro.locals.x = 1;\n} catch (e) {\n  if (e instanceof Error && /cannot be assigned directly/i.test(e.message)) {\n    // fix the reassignment; this catch indicates the setter was hit\n  }\n  throw e;\n}","preventionTips":["Always use property assignment on locals, never whole-object assignment.","Configure a linter/ESLint rule to flag `locals =`.","Document the mutation-only contract in your project's conventions."],"tags":["locals","astro-global","middleware","state"],"backgroundTag":null,"analyzedSha":"d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca","analyzedAt":"2026-08-12T13:37:29.035Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}