vercel/next.js · error · SerializableError
Error serializing `${path}` returned from `${method}` in "${
Error message
Error serializing `${path}` returned from `${method}` in "${page}".
Reason: Circular references cannot be expressed in JSON (references: `${visited.get(value) || '(self)'}`). What it means
Thrown by isSerializableProps() when, during deep traversal of the props object, a value is encountered that already exists in the visited Map — i.e. a circular reference. JSON cannot express cycles, so Next.js reports the first path that closed the loop (or '(self)' if it points to its own path).
Source
Thrown at packages/next/src/lib/is-serializable-props.ts:36
export function isSerializableProps(
page: string,
method: string,
input: any
): true {
if (!isPlainObject(input)) {
throw new SerializableError(
page,
method,
'',
`Props must be returned as a plain object from ${method}: \`{ props: { ... } }\` (received: \`${getObjectClassLabel(
input
)}\`).`
)
}
function visit(visited: Map<any, string>, value: any, path: string) {
if (visited.has(value)) {
throw new SerializableError(
page,
method,
path,
`Circular references cannot be expressed in JSON (references: \`${
visited.get(value) || '(self)'
}\`).`
)
}
visited.set(value, path)
}
function isSerializable(
refs: Map<any, string>,
value: any,
path: string
): true {
const type = typeof valueView on GitHub (pinned to 0ae8c72462)
Solutions
- Break the cycle before returning: map bidirectional relations to one direction only, or replace back-references with IDs.
- Use a safe stringify or selective picker (e.g. only expose whitelisted fields) to flatten the graph into a tree.
- Sanitize with JSON.parse(JSON.stringify(props)) to drop cyclic references before returning.
- Unit test data-fetching functions against a serialize check.
Example fix
// before
export async function getStaticProps() {
const post = await getPost(id)
post.author.posts = [post] // circular: post -> author -> post
return { props: { post } }
}
// after
export async function getStaticProps() {
const post = await getPost(id)
return {
props: {
post: { ...post, author: { name: post.author.name } } // no back-ref
},
}
} Defensive patterns
Strategy: validation
Validate before calling
function hasCycle(obj: unknown): boolean {
const seen = new WeakSet()
function visit(v: unknown): boolean {
if (v && typeof v === 'object') {
if (seen.has(v)) return true
seen.add(v)
return Object.values(v).some(visit)
}
return false
}
return visit(obj)
}
// if (hasCycle(props)) throw new Error('circular props') Type guard
function isAcyclic(v: unknown, seen = new WeakSet()): boolean {
if (v && typeof v === 'object') {
if (seen.has(v)) return false
seen.add(v)
return Object.values(v).every((c) => isAcyclic(c, seen))
}
return true
} Prevention
- Avoid bidirectional ORM relations in props; reference by ID.
- Build props as a fresh plain object, selecting fields explicitly.
- Sanitize with JSON.parse(JSON.stringify(props)) to drop unserializable/cyclic refs before returning.
- Unit test data-fetching functions against a serialize check.
When it happens
Trigger: A getStaticProps/getServerSideProps return value contains an object that references itself (directly or transitively). The visit() function tracks each object/array by path; re-encountering one throws with the original path that referenced it.
Common situations: Bidirectional ORM relations (user.posts[].user); manually linking nodes in a tree; caching an object and reusing the same reference in multiple places that form a cycle; or building a graph data structure in props.
Related errors
- Error serializing props returned from `${method}` in "${page
- Error serializing `${path}` returned from `${method}` in "${
- Error serializing `${path}` returned from `${method}` in "${
- Error serializing `${path}` returned from `${method}` in "${
- Error serializing `${path}` returned from `${method}` in "${
AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06).
Data as JSON: /api/errors/0bf9380288b40eb7.
Report an issue: GitHub.