{"record":{"id":"9891a41dde6ab0b1","repo":"withastro/astro","slug":"localsnotanobject","errorCode":"LocalsNotAnObject","errorMessage":"`locals` can only be assigned to an object. Other values like numbers, strings, etc. are not accepted.","messagePattern":"`locals` can only be assigned to an object\\. Other values like numbers, strings, etc\\. are not accepted\\.","errorType":"exception","errorClass":"AstroError","httpStatus":null,"severity":"error","filePath":"packages/astro/src/core/middleware/index.ts","lineNumber":135,"sourceCode":"\t\tget preferredLocaleList(): string[] | undefined {\n\t\t\treturn (preferredLocaleList ??= computePreferredLocaleList(request, userDefinedLocales));\n\t\t},\n\t\tget currentLocale(): string | undefined {\n\t\t\treturn (currentLocale ??= computeCurrentLocale(route, userDefinedLocales, defaultLocale));\n\t\t},\n\t\turl,\n\t\tget originPathname() {\n\t\t\treturn getOriginPathname(request);\n\t\t},\n\t\tget clientAddress() {\n\t\t\tif (clientAddress) {\n\t\t\t\treturn clientAddress;\n\t\t\t}\n\t\t\tthrow new AstroError(AstroErrorData.StaticClientAddressNotAvailable);\n\t\t},\n\t\tget locals() {\n\t\t\tif (typeof locals !== 'object') {\n\t\t\t\tthrow new AstroError(AstroErrorData.LocalsNotAnObject);\n\t\t\t}\n\t\t\treturn locals;\n\t\t},\n\t\tset locals(_) {\n\t\t\tthrow new AstroError(AstroErrorData.LocalsReassigned);\n\t\t},\n\t\tsession: undefined,\n\t\tcache: new DisabledAstroCache(),\n\t\tcsp: undefined,\n\t\tlogger: {\n\t\t\tinfo() {},\n\t\t\twarn() {},\n\t\t\terror() {},\n\t\t},\n\t};\n\treturn Object.assign(context, {\n\t\tgetActionResult: createGetActionResult(context.locals),\n\t\tcallAction: createCallAction(context),","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/withastro/astro/blob/e294953aa8aadd98d5be92e60a03037b05dbdfd4/packages/astro/src/core/middleware/index.ts#L117-L153","documentation":"On contexts built by createContext(), the locals getter validates that the underlying locals value is an object and throws AstroErrorData.LocalsNotAnObject otherwise. The locals value comes from whoever created the context — on Vercel/Netlify it is deserialized from the locals forwarding header (x-astro-locals) — so a scalar (string, number, boolean, null) in that channel makes the very first access to context.locals throw.","triggerScenarios":"An adapter or hand-written bridge passes a non-object into createContext({ locals }): a JSON string that was not parsed, a scalar value from the x-astro-locals header (e.g. overwritten by a proxy or test fixture to '42' or 'abc'), or locals arriving as an array.","commonSituations":"Edge middleware to function handoff where the locals header gets overwritten or double-encoded; custom integrations/tests calling createContext with a serialized locals string; middleware that stores a scalar into a header-based channel that later feeds locals.","solutions":["Pass a plain object as locals: parse first (const parsed = JSON.parse(raw)) and hand the object to createContext","Adapter/bridge authors: validate before creating the context — if the value is not a non-null object, substitute {} or fail loudly","Never overwrite the platform's locals forwarding header with non-object JSON"],"exampleFix":"// before (adapter bridge)\nconst ctx = createContext({ request, locals: request.headers.get('x-astro-locals') });\n\n// after\nconst raw = request.headers.get('x-astro-locals');\nconst parsed = raw ? JSON.parse(raw) : {};\nconst ctx = createContext({ request, locals: parsed && typeof parsed === 'object' ? parsed : {} });","handlingStrategy":"validation","validationCode":"const raw = request.headers.get('x-astro-locals');\nlet locals: App.Locals = {};\nif (raw) {\n  const parsed: unknown = JSON.parse(raw);\n  if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {\n    locals = parsed as App.Locals;\n  }\n}\nconst ctx = createContext({ request, locals });","typeGuard":"const isLocalsObject = (value: unknown): value is Record<string, unknown> =>\n  typeof value === 'object' && value !== null && !Array.isArray(value);","tryCatchPattern":null,"preventionTips":["Bridge authors: validate the parsed locals shape before creating a context","Keep everything stored in locals as object properties; never let a scalar ride the forwarding header","Do not overwrite or re-encode the platform's locals header in proxies or tests"],"tags":["locals","type-validation","edge-middleware","adapter"],"backgroundTag":"context-state-type-invalid","analyzedSha":"e294953aa8aadd98d5be92e60a03037b05dbdfd4","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}