ianstormtaylor/slate · error · Error

Cannot merge the node at path [${path}] with the previous si

Error message

Cannot merge the node at path [${path}] with the previous sibling because it is not the same kind: ${Scrubber.stringify(
          node
        )} ${Scrubber.stringify(prevNode)}

What it means

mergeNodes throws when the node being merged and its previous sibling are of different kinds — specifically when one is a Text node and the other is an Element, or their structures make merging invalid (e.g. merging an element into a text node). Merging requires both nodes to be the same type so their contents can combine.

Source

Thrown at packages/slate/src/transforms-node/merge-nodes.ts:116

      match: n => levels.includes(n) && hasSingleChildNest(editor, n),
    })

    const emptyRef = emptyAncestor && Editor.pathRef(editor, emptyAncestor[1])
    let properties
    let position

    // Ensure that the nodes are equivalent, and figure out what the position
    // and extra properties of the merge will be.
    if (Node.isText(node) && Node.isText(prevNode)) {
      const { text, ...rest } = node
      position = prevNode.text.length
      properties = rest as Partial<Text>
    } else if (Node.isElement(node) && Node.isElement(prevNode)) {
      const { children, ...rest } = node
      position = prevNode.children.length
      properties = rest as Partial<Element>
    } else {
      throw new Error(
        `Cannot merge the node at path [${path}] with the previous sibling because it is not the same kind: ${Scrubber.stringify(
          node
        )} ${Scrubber.stringify(prevNode)}`
      )
    }

    // If the node isn't already the next sibling of the previous node, move
    // it so that it is before merging.
    if (!isPreviousSibling) {
      Transforms.moveNodes(editor, { at: path, to: newPath, voids })
    }

    // If there was going to be an empty ancestor of the node that was merged,
    // we remove it from the tree.
    if (emptyRef) {
      Transforms.removeNodes(editor, { at: emptyRef.current!, voids })
    }

View on GitHub (pinned to 72a37c701e)

Solutions

  1. Check the target and its previous sibling (or parent for first children) are the same kind (Node.isText on both, or both Elements) before merging
  2. Use Editor.node(editor, Path.previous(path)) to inspect the prev sibling and skip/unwrap instead
  3. Prefer built-in Editor.deleteBackward or Transforms.removeNodes for mixed-kind structures instead of manual mergeNodes

Example fix

// before
Transforms.mergeNodes(editor, { at })

// after
const [prev] = Editor.node(editor, Path.previous(at))
const [cur] = Editor.node(editor, at)
if (Node.isText(prev) === Node.isText(cur)) {
  Transforms.mergeNodes(editor, { at })
} else {
  Transforms.removeNodes(editor, { at })
}
Defensive patterns

Strategy: type-guard

Validate before calling

const [cur] = Editor.node(editor, at)
let prevEntry
try { prevEntry = Editor.node(editor, Path.previous(at)) } catch { prevEntry = null }
if (prevEntry) {
  const [prev] = prevEntry
  if (Node.isText(prev) === Node.isText(cur)) {
    Transforms.mergeNodes(editor, { at })
    return
  }
}
Transforms.removeNodes(editor, { at })

Type guard

const isSameKind = (a: Node, b: Node): boolean =>
  Node.isText(a) === Node.isText(b)

Prevention

When it happens

Trigger: Transforms.mergeNodes where target is a Text but prev sibling is an Element, or vice versa; merging the first child of an element into the element itself when kinds mismatch; merging nodes at depth where prev sibling is a text while target is an inline element.

Common situations: Custom delete/Backspace handlers calling mergeNodes on arbitrary paths; normalizing structure where text and inline-element siblings are interleaved; calling mergeNodes with at pointing at the first text of an element whose parent merge then mismatches.

Related errors


AI-assisted analysis of ianstormtaylor/slate@72a37c701e (2026-08-27). Data as JSON: /api/errors/3a2e513b8f95ce17. Report an issue: GitHub.