withastro/astro · error · Error

The passed value can't be serialized.

Error message

The passed value can't be serialized.

What it means

trySerializeLocals is the official helper adapters (Netlify, Vercel) use to pack locals into the x-astro-locals header when forwarding a request from edge middleware to the serverless function. It JSON.stringify's the value only if every part passes isLocalsSerializable; functions, Dates, Maps, Sets, class instances and other non-JSON values fail, and it throws a plain Error ('The passed value can't be serialized.').

Solutions

  1. Store only plain JSON values in edge-middleware locals (strings, numbers, booleans, arrays, plain objects)
  2. Convert before storing: dates to ISO strings, class instances to plain data (user.toJSON() or manual pick)
  3. Keep just an identifier in locals (session id) and load the rich object server-side in the function
  4. Strip unserializable keys before the handoff instead of letting the adapter serialize everything

Example fix

// before (edge middleware)
context.locals.session = session; // class instance with methods

// after
context.locals.sessionId = session.id;
Defensive patterns

Strategy: type-guard

Validate before calling

function assertLocalsSerializable(locals: App.Locals): void {
  for (const [key, value] of Object.entries(locals)) {
    if (!isSerializableValue(value)) {
      throw new TypeError(`locals.${key} is not JSON-serializable; store an identifier instead`);
    }
  }
}
// call before storing rich objects in edge middleware locals

Type guard

function isSerializableValue(value: unknown): boolean {
  if (value === null) return true;
  const type = typeof value;
  if (type === 'string' || type === 'number' || type === 'boolean') return true;
  if (type !== 'object') return false; // functions, symbols, undefined, bigint
  if (Array.isArray(value)) return value.every(isSerializableValue);
  if (value instanceof Date || value instanceof Map || value instanceof Set) return false;
  return Object.values(value).every(isSerializableValue);
}

Prevention

When it happens

Trigger: Edge middleware stores non-JSON values in context.locals right before the platform bridge serializes them: locals.visit = new Date(), locals.user = new User(...) (class instance), a function reference, a Map/Set cache, or undefined-bearing structures.

Common situations: Geolocation or auth SDK objects placed in locals at the edge; copying function-oriented session objects into locals; a codebase that worked on adapters without edge middleware failing once edge middleware is enabled.

Related errors


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

Appendix: source

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

	return proto === baseProto;
}

/**
 * It attempts to serialize `value` and return it as a string.
 *
 * ## Errors
 *  If the `value` is not serializable if the function will throw a runtime error.
 *
 * Something is **not serializable** when it contains properties/values like functions, `Map`, `Set`, `Date`,
 * and other types that can't be made a string.
 *
 * @param value
 */
function trySerializeLocals(value: unknown) {
	if (isLocalsSerializable(value)) {
		return JSON.stringify(value);
	} else {
		throw new Error("The passed value can't be serialized.");
	}
}

// NOTE: this export must export only the functions that will be exposed to user-land as officials APIs
export { createContext, sequence, trySerializeLocals };
export { defineMiddleware } from './defineMiddleware.js';

View on GitHub (pinned to e294953aa8)