withastro/astro · error · AstroError

LocalsReassigned

LocalsReassigned

Error message

`locals` cannot be assigned directly.

What it means

The setter for locals on createContext()-built contexts always throws AstroErrorData.LocalsReassigned. As with Astro.locals, the object identity is shared with everything downstream of the context, so it can only be decorated property-by-property, never replaced. This is the same contract enforced on every Astro context variant.

Solutions

  1. Mutate instead: context.locals.user = user
  2. To restore many fields: Object.assign(context.locals, parsedLocals)
  3. Initialize individual fields early in middleware rather than replacing the container

Example fix

// before
const parsed = JSON.parse(request.headers.get('x-astro-locals') ?? '{}');
context.locals = parsed;

// after
const parsed = JSON.parse(request.headers.get('x-astro-locals') ?? '{}');
Object.assign(context.locals, parsed);
Defensive patterns

Strategy: validation

Validate before calling

function restoreLocals(context: APIContext, parsed: Record<string, unknown>): void {
  Object.assign(context.locals, parsed);
}

Prevention

When it happens

Trigger: `context.locals = {...}` inside middleware running against an adapter-created (e.g. edge-bridged) context; attempting to reassign locals to a fresh object after parsing them off a header.

Common situations: Edge middleware that receives locals from a header and 'restores' them by assignment instead of merging; developers porting code where ctx.state is assignable; initializing locals at the top of middleware with an assignment.

Related errors


AI-assisted analysis of withastro/astro@e294953aa8 (2026-08-18). Data as JSON: /api/errors/0b37ba74e79d13c2. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/core/middleware/index.ts:140

		},
		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 e294953aa8)