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

  1. Mutate in place: `context.locals.user = user`.
  2. If you need a clean shape, delete keys individually instead of replacing the object.
  3. 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

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


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