That 'Failed to execute removeChild on Node' crash in production? It's Chrome Translate rewriting the DOM under React's feet. Here's why it happens, how to stop the crash, and why browser translation was never localization in the first place.
You ship a React app. Weeks later, an error starts showing up in Sentry from a handful of users you can't reproduce:
NotFoundError: Failed to execute 'removeChild' on 'Node':
The node to be removed is not a child of this node.
Sometimes it's insertBefore instead of removeChild. The stack trace points
deep into React's reconciler, not your code. It only happens for some users.
You can't trigger it locally. And it tends to cluster around users in countries
where English isn't the first language.
That last clue is the tell. This is Chrome's built-in page translation fighting with React's virtual DOM — a crash that's widely reported and easy to misdiagnose.
When Chrome decides a page is in a language different from the user's
preference, it translates the page in place, in the browser, after your app
has rendered. To do that, it walks the DOM and rewrites your text nodes —
typically wrapping translated runs of text in <font> elements and swapping the
original text node out.
React doesn't know any of this happened. React keeps its own virtual model of
the DOM and assumes the real DOM still matches it. So the next time React tries
to update or remove a node that Chrome quietly replaced, it reaches for a child
that is no longer where React thinks it is — and the browser throws
removeChild/insertBefore errors. In the worst cases, the component tree
unmounts and the user sees a blank screen.
The long-running bug report is React issue #11538, filed in 2017, with more than 100 comments from engineers debugging exactly this in production. It is not a bug in your code, and it's not really a bug in React — it's two systems mutating the same DOM without coordinating.
The specific pattern that triggers it most often is a bare text node sibling to an element, where the text is dynamic:
// Fragile: "Hello, " and "!" are bare text nodes next to {name}.
// Chrome merges and rewraps them; React later can't find them.
<div>
Hello, {name}!
</div>
When Chrome translates "Hello," it restructures those sibling nodes. When
name changes and React re-renders, the reconciler trips over the rearranged
children.
There are two levels of fix. The first stops the bleeding.
1. Wrap dynamic text so there are no bare text-node siblings. Give React stable element boundaries Chrome won't merge across:
<div>
<span>Hello, </span>
<span>{name}</span>
<span>!</span>
</div>
2. Mark subtrees as not-translatable. The HTML5 translate="no" attribute
(and the notranslate class Google honors) tells the browser to leave a
subtree alone. Apply it to the dynamic, React-managed parts:
<div translate="no">
{/* React fully owns this subtree; the browser won't rewrite it */}
<LiveDashboard />
</div>
You can also opt the whole document out with <html translate="no"> or
<meta name="google" content="notranslate" /> — but that's the nuclear option,
and it points at the deeper problem.
Look at what those fixes actually do: they turn translation off. You've stopped the crash by ensuring your users don't get a translated page. Which means the original problem — your German, Japanese, or Brazilian users can't read your app — is still completely unsolved. You've just made it fail quietly instead of loudly.
This is the moment to step back and notice that Chrome Translate was never localization in the first place. Even when it doesn't crash your app, it's a fallback that breaks down in several ways that matter:
placeholder, aria-label,
alt, title, toast notifications rendered after the first pass — browser
translation routinely misses them. Your user gets a translated page with an
English "Enter your email" placeholder and an English "Invalid input" error.Real internationalization means your app renders correctly in each user's language regardless of their browser, device, or whether the browser offers to translate. Mechanically, that's three steps:
next-intl, react-i18next,
etc.) and extract them into message files.Now every user gets consistent, indexable copy in their language, and browser
translation has no reason to touch your DOM. The translate="no" band-aid becomes
unnecessary because you're no longer relying on the browser to paper over a gap.
The tedious part has always been steps 1 and 2 — finding the strings and wrapping
them by hand across a real codebase. That's the part we built
Polyglot to help with: polyglot scan walks your source
with tree-sitter and reports hardcoded-string candidates (placeholders and
aria-labels included), polyglot wrap applies a plan you've reviewed that
rewrites them with t() calls and extracts the message files, and
polyglot translate generates translations with context and your glossary,
which you review in the dashboard before they're approved. Our
June 2026 benchmark (AI-judged) documents
how that pipeline scored, with the dataset published.
But the tool is secondary to the point: if you're reaching for translate="no"
to stop a production crash, that's your signal to do i18n properly. Browser
translation isn't a strategy — it's a thing that happens to your app when you
haven't localized it.
Start in your terminal
Install the CLI, run a scan, and see exactly what you're missing. Free, no account required.