withastro/astro · error · Error

Cyclic reference detected while serializing props for <${met

Error message

Cyclic reference detected while serializing props for <${metadata.displayName} client:${metadata.hydrate}>!

Cyclic references cannot be safely serialized for client-side usage. Please remove the cyclic reference.

What it means

While serializing component props for an island (`serializeArray`), the array was already present in the `parents` WeakSet — i.e. the array is reachable from itself, forming a cycle. JSON serialization cannot represent cycles, and the client hydration payload would be infinite, so Astro throws. The message names the component (`displayName`) and its hydration directive.

Source

Thrown at packages/astro/src/runtime/server/serialize.ts:25

	RegExp: 2,
	Date: 3,
	Map: 4,
	Set: 5,
	BigInt: 6,
	URL: 7,
	Uint8Array: 8,
	Uint16Array: 9,
	Uint32Array: 10,
	Infinity: 11,
};

function serializeArray(
	value: any[],
	metadata: AstroComponentMetadata | Record<string, any> = {},
	parents = new WeakSet<any>(),
): any[] {
	if (parents.has(value)) {
		throw new Error(`Cyclic reference detected while serializing props for <${metadata.displayName} client:${metadata.hydrate}>!

Cyclic references cannot be safely serialized for client-side usage. Please remove the cyclic reference.`);
	}
	parents.add(value);
	const serialized = value.map((v) => {
		return convertToSerializedForm(v, metadata, parents);
	});
	parents.delete(value);
	return serialized;
}

function serializeObject(
	value: Record<any, any>,
	metadata: AstroComponentMetadata | Record<string, any> = {},
	parents = new WeakSet<any>(),
): Record<any, any> {
	if (parents.has(value)) {
		throw new Error(`Cyclic reference detected while serializing props for <${metadata.displayName} client:${metadata.hydrate}>!

View on GitHub (pinned to d081033d5f)

Solutions

  1. Break the cycle before passing the value: map the data to a flat, acyclic structure (e.g. strip parent pointers).
  2. Use `structuredClone` won't help — clone the data into plain JSON-safe form instead.
  3. Pass only the serializable subset of data the client actually needs.

Example fix

// before — node has a back-reference to its parent array
const items = [{ id: 1 }];
items[0].parent = items;
<Tree client:load nodes={items} /> // cyclic -> throws

// after — flatten to acyclic data
const items = [{ id: 1, parentId: null }];
<Tree client:load nodes={items} />
Defensive patterns

Strategy: validation

Validate before calling

// Reject cyclic arrays before passing them as island props
function assertAcyclic(value: unknown, seen = new WeakSet()): void {
  if (value && typeof value === 'object') {
    if (seen.has(value as object)) throw new Error('Cyclic array reference in props');
    seen.add(value as object);
    if (Array.isArray(value)) value.forEach((v) => assertAcyclic(v, seen));
  }
}

Type guard

function isAcyclic(value: unknown, seen = new WeakSet()): boolean {
  if (value && typeof value === 'object') {
    if (seen.has(value as object)) return false;
    seen.add(value as object);
    if (Array.isArray(value)) return value.every((v) => isAcyclic(v, seen));
  }
  return true;
}

Prevention

When it happens

Trigger: An array passed as a prop to a hydrated component contains a reference back to itself (directly or transitively); a tree/DOM-like structure with parent pointers passed to `client:*` components; a state object mutated to include a back-reference.

Common situations: Passing a rich data model (graph, tree with parent links, DOM nodes) as a prop; mutable shared state objects that gained circular references before render.

Related errors


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