withastro/astro · error · AstroError

LocalsReassigned

LocalsReassigned

Error message

`locals` cannot be assigned directly.

What it means

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.

Source

Thrown at packages/astro/src/core/fetch/fetch-state.ts:1127

		const ctx = {
			get cookies() {
				return state.cookies;
			},
			routePattern: this.routeData!.route,
			isPrerendered: this.routeData!.prerender,
			get clientAddress() {
				return state.getClientAddress();
			},
			get currentLocale() {
				return state.computeCurrentLocale();
			},
			generator: ASTRO_GENERATOR,
			get locals() {
				return state.locals;
			},
			set locals(_) {
				throw new AstroError(AstroErrorData.LocalsReassigned);
			},
			// SAFETY: getActionAPIContext is only called after route resolution,
			// so routeData is always set and the params getter always returns a value.
			params: this.params!,
			get preferredLocale() {
				return state.computePreferredLocale();
			},
			get preferredLocaleList() {
				return state.computePreferredLocaleList();
			},
			request: this.request,
			site: this.pipeline.site,
			url: this.url,
			get originPathname() {
				return getOriginPathname(state.request);
			},
			get csp() {
				return state.getCsp();

View on GitHub (pinned to d081033d5f)

Solutions

  1. Mutate instead of reassign: set individual properties, e.g. `Astro.locals.user = user`.
  2. If you need to reset, delete keys individually or assign to existing properties rather than replacing the object.
  3. Initialize locals shape in middleware via context.locals.x = value, never context.locals = {...}.

Example fix

// before
Astro.locals = { user };

// after
Astro.locals.user = user;
Defensive patterns

Strategy: validation

Validate before calling

// Never reassign; always mutate properties.
Astro.locals.user = user;            // ok
Astro.locals = { user };             // forbidden

Type guard

// locals must be an object you mutate, not replace.
function assertLocalsMutable(locals: unknown): asserts locals is Record<string, unknown> {
  if (typeof locals !== 'object' || locals === null) {
    throw new Error('locals must be an object');
  }
}

Try / catch

try {
  Astro.locals.x = 1;
} catch (e) {
  if (e instanceof Error && /cannot be assigned directly/i.test(e.message)) {
    // fix the reassignment; this catch indicates the setter was hit
  }
  throw e;
}

Prevention

When it happens

Trigger: Writing `Astro.locals = { ... }` (assignment to the property itself) inside a page, layout, endpoint, or action. The setter defined in createAstroPagePartial throws AstroErrorData.LocalsReassigned unconditionally.

Common situations: 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.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/5847eadb40289381. Report an issue: GitHub.