emberjs/ember.js · error · Error

Rehydration with nextSibling not supported

Error message

Rehydration with nextSibling not supported

What it means

The rehydration builder can only replay server-rendered HTML by walking the existing children of the parent element; it has no support for starting mid-stream at a specific sibling. The RehydrateBuilder constructor throws immediately if a non-null nextSibling cursor is supplied.

Source

Thrown at packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts:51

  constructor(
    element: SimpleElement,
    nextSibling: Nullable<SimpleNode>,
    public readonly startingBlockDepth: number
  ) {
    super(element, nextSibling);
    this.openBlockDepth = startingBlockDepth - 1;
  }
}

export class RehydrateTree extends NewTreeBuilder implements TreeBuilder {
  private unmatchedAttributes: Nullable<SimpleAttr[]> = null;
  declare cursors: Stack<RehydratingCursor>; // Hides property on base class
  blockDepth = 0;
  startingBlockOffset: number;

  constructor(env: Environment, parentNode: SimpleElement, nextSibling: Nullable<SimpleNode>) {
    super(env, parentNode, nextSibling);
    if (nextSibling) throw new Error('Rehydration with nextSibling not supported');

    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
    let node = this.currentCursor!.element.firstChild;

    while (node !== null) {
      if (isOpenBlock(node)) {
        break;
      }
      node = node.nextSibling;
    }

    assert(node, 'Must have opening comment for rehydration.');
    this.candidate = node;
    const startingBlockOffset = getBlockDepth(node);
    if (startingBlockOffset !== 0) {
      // We are rehydrating from a partial tree and not the root component
      // We need to add an extra block before the first block to rehydrate correctly
      // The extra block is needed since the renderComponent API creates a synthetic component invocation which generates the extra block

View on GitHub (pinned to 26f97246a8)

Solutions

  1. Pass `null` as nextSibling when creating the rehydrating builder.
  2. If you must append relative to a sibling, clear or restructure the parent element so rehydration starts from the first child.
  3. Use the non-rehydrating element builder for insertion at a specific sibling position instead of RehydrateBuilder.

Example fix

// before
let builder = new RehydrateBuilder(env, rootElement, anchorNode);

// after
let builder = new RehydrateBuilder(env, rootElement, null);
Defensive patterns

Strategy: validation

Validate before calling

if (nextSibling != null) {
  throw new Error('RehydrateBuilder requires nextSibling === null');
}
const builder = new RehydrateBuilder(env, parentNode, null);

Type guard

function canRehydrate(cursor) {
  return cursor.nextSibling == null;
}

Prevention

When it happens

Trigger: Constructing `new RehydrateBuilder(env, parentNode, nextSibling)` with a non-null nextSibling — i.e. attempting to serialize/rehydrate into a cursor that points at an insertion point between existing nodes.

Common situations: Embedding Glimmer SSR/rehydration into a custom host framework that uses element cursors with siblings; using Glimmer's client-side builder APIs against a rehydrating element with an anchor node.

Related errors


AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01). Data as JSON: /api/errors/44d22c51eacc6a12. Report an issue: GitHub.