DEV Community

TuTuTu
TuTuTu

Posted on

Chrome Translate Broke My React Button: Fixing insertBefore

A React button worked normally during development but crashed after a user translated the page in Chrome:

NotFoundError: Failed to execute 'insertBefore' on 'Node':
The node before which the new node is to be inserted is not a child of this node.
Enter fullscreen mode Exit fullscreen mode

I encountered it in the image-conversion workflow on TattooIdeas.app. Clicking the generation button replaces its icon with a spinner and changes its label.

What caused it?

The original JSX looked harmless:

<Button>
  {isGenerating ? <Spinner /> : <ToolIcon />}
  {isGenerating ? 'Generating…' : 'Generate · 4 credits'}
</Button>
Enter fullscreen mode Exit fullscreen mode

Chrome Translate may replace a text node managed by React with nested <font> elements. React still holds a reference to the original node.

When the state changes, React can try to insert the spinner relative to a node that is no longer a direct child of the button. The browser then throws insertBefore.

This type of DOM-mutation problem has a long history in React.

The fix

I gave the icon and label stable wrapper elements:

<Button>
  <span aria-hidden="true" className="button-icon">
    {isGenerating ? <Spinner /> : <ToolIcon />}
  </span>

  <span translate="no">
    {isGenerating ? 'Generating…' : 'Generate · 4 credits'}
  </span>
</Button>
Enter fullscreen mode Exit fullscreen mode

The wrappers keep the button's direct-child structure stable. translate="no" also asks browser translators not to rewrite this small, stateful label.

I would not apply translate="no" to the entire application. It is better to use it only for frequently changing UI and provide normal localized text through the application's own i18n system.

Testing the actual failure mode

I also made Playwright imitate a browser translator:

await label.evaluate((element) => {
  const text = [...element.childNodes].find(
    (node) => node.nodeType === Node.TEXT_NODE
  );

  const outer = document.createElement('font');
  const inner = document.createElement('font');

  inner.textContent = text?.textContent ?? '';
  outer.append(inner);
  text?.replaceWith(outer);
});

await button.click();

expect(
  pageErrors.filter((error) => error.message.includes('insertBefore'))
).toHaveLength(0);
Enter fullscreen mode Exit fullscreen mode

The broader lesson is that browser extensions, translators, and third-party scripts can modify DOM that React assumes it owns.

Stable component boundaries—and a test that recreates the external mutation—are much more useful than treating the error as a random production crash.

Top comments (0)