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.
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>
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>
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);
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)