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
- Store only plain JSON values in edge-middleware locals (strings, numbers, booleans, arrays, plain objects)
- Convert before storing: dates to ISO strings, class instances to plain data (user.toJSON() or manual pick)
- Keep just an identifier in locals (session id) and load the rich object server-side in the function
- 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
- Keep edge-middleware locals to plain JSON: primitives, arrays, plain objects
- Store IDs, not instances (sessionId instead of a session class); hydrate server-side
- Convert Dates to ISO strings before storing
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
- LocalsNotAnObject
- BAD_REQUEST
- StaticClientAddressNotAvailable
- ActionsReturnedInvalidDataError
- AdapterSupportOutputMismatch
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)