DEV Community

Vladimir Elchinov for Session Replay

Posted on

Your Error Tracker Is Full of removeChild Errors From Users Who Translated the Page

There is a crash that shows up in every long-lived single-page app's error tracker, always with the same shape:

NotFoundError: Failed to execute 'removeChild' on 'Node':
The node to be removed is not a child of this node.
Enter fullscreen mode Exit fullscreen mode

The stack is entirely inside your framework. You have never seen it locally. It arrives in clusters, from users you cannot identify, and the one piece of information that would explain it is not in the report.

It is usually the browser's page translation, and the mechanism is written down.

What translation actually replaces

The HTML Standard defines the translate attribute as controlling whether "an element's attribute values and the values of its Text node children are to be translated when the page is localized."

Text node children. Not the element, not its innerHTML as a string. The browser walks your DOM and swaps out the text nodes themselves.

Your framework is holding references to those nodes. It built them, it remembers them, and it expects to find them in the same place when it next updates. After translation, the node it remembers has been replaced by a different one, so the parent it is about to be removed from is no longer its parent.

Which lands exactly on the DOM Standard's wording. removeChild is defined by one line:

The removeChild(child) method steps are to return the result of pre-removing child from this.

And pre-removing opens with the check that produces your crash:

To pre-remove a node child from a node parent: If child's parent is not parent, then throw a "NotFoundError" DOMException.

So the exception is not a framework bug and not a race in your code. It is the DOM refusing to remove a node from a parent that is no longer its parent, and something outside your application moved it.

Everything is translatable unless you say otherwise

The part worth knowing is the default, which the spec is explicit about:

The attribute's missing value default and invalid value default are both the Inherit state

and when there is no parent to inherit from, the element's translation mode is translate-enabled. Nothing opts in. Every text node on your page is a candidate, including the ones your framework is actively managing.

There is a trap in the same paragraph, and it is the kind that survives code review:

its empty value default is the Yes state

<span translate> and <span translate=""> mean translate this. If you have written either reaching for "do not translate", you have written the opposite of what you intended, and it parses cleanly.

The opt-out is the explicit keyword:

<span translate="no">{{ user.displayName }}</span>
Enter fullscreen mode Exit fullscreen mode

Put it on the parts a translation has no business touching: names, identifiers, code samples, and anything your framework re-renders from a text node reference.

Why it never happens to you

Browsers offer to translate when the page's language differs from the user's. You build in the language you ship in, so you are never offered it, and you will not reproduce this by clicking around.

That is the whole gap. The crash is conditional on a user-side setting that is invisible from inside the page, triggered by a prompt the user accepted once and does not remember, and the resulting exception names your framework rather than the cause.

What to collect

Your error tracker already has the stack, which is the part that does not help. What is missing is anything that distinguishes a translated page from an untranslated one.

Two cheap fields turn the cluster into a diagnosis:

navigator.language              // what the browser prefers
document.documentElement.lang   // what you declared
Enter fullscreen mode Exit fullscreen mode

When those two disagree across most of a removeChild cluster, you have your answer, and it took one line of report metadata rather than an afternoon of staring at framework internals.

The deeper version of the problem is that the page was edited by something that was never going to tell it. Your own code is the last place that would notice, because from inside, nothing is wrong until the moment something is very wrong.

Top comments (0)