Skip to content

fix: prevent crash when page is translated by Google Translate - #5573

Open
turin-dev wants to merge 1 commit into
Dokploy:canaryfrom
turin-dev:fix/google-translate-crash
Open

turin-dev wants to merge 1 commit into
Dokploy:canaryfrom
turin-dev:fix/google-translate-crash

Conversation

@turin-dev

@turin-dev turin-dev commented Oct 4, 2026 •

Copy link
Copy Markdown

What is this PR about?

When the dashboard is translated with Google Translate (or Edge's translator / translation extensions), interacting with the UI crashes the whole app and shows the 500 error page.

Root cause: translators replace React-managed text nodes with their own <font> elements. React still holds references to the original (now detached) text nodes, so the next insertBefore / removeChild against them throws NotFoundError. The error is uncaught, React unmounts the tree, and Next.js renders _error (no statusCode on the client → "500").

The most common trigger is our Button component: {isLoading && <Loader2 />} is inserted before the (translated) label text, so clicking almost any button that shows a loading state crashes the page:

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

Fix: apply the well-known workaround from react/react#11538. Node.prototype.removeChild / insertBefore are patched on the client (imported from _app.tsx) so that only the calls that would otherwise throw are handled gracefully:

  • removeChild(child) where child is no longer a child → no-op
  • insertBefore(node, ref) where ref is no longer a child → append to the parent instead, so the new node is still shown and later removals still work

All other calls go through the native implementation unchanged, so behavior without a translator is identical. Translation keeps working instead of being disabled with translate="no", which matters since the UI is English-only.

Known limitation (inherent to translators mutating the DOM): text that React updates after translation may keep showing the stale translated text until the next full re-render — but the app no longer crashes.

How it was tested

Local Dokploy instance (dev mode, canary + this PR) — real Google Translate (Google Website Translator switched to Korean) driven by Playwright/Chromium. Each run started from a fresh database. "Before" is the same build with only the _app.tsx import removed.

Scenario Before After
Translate /register → click Register NotFoundError: Failed to execute 'insertBefore' on 'Node' in <LoaderCircle>, app tree unmounted (blank page) No errors, lands on the translated dashboard
Translate login page → click Login Same insertBefore error, blank page No errors, logged in
Translate /dashboard/projects → Create Project → Create insertBefore + removeChild (<Text>) errors, blank page No errors, project created and its page renders translated

In dev mode the crash shows up as a blank page; in a production build the same uncaught error is expected to render _error, which displays 500 (no statusCode on the client) — the symptom users report.

Minimal reproduction — React/React DOM 19.2.7 in headless Chromium with the same pattern as Button ({isLoading && <svg />}{children}), simulating the translator's <font> replacement: crashes without the patch, works with it, and produces identical DOM output to the unpatched build when the page is not translated.

tsc --noEmit and biome check pass.

Checklist

Before submitting this PR, please make sure that:

  • You created a dedicated branch based on the canary branch.
  • You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
  • You have tested this PR in your local instance. If you have not tested it yet, please do so before submitting. This helps avoid wasting maintainers' time reviewing code that has not been verified by you.

Issues related (if applicable)

N/A — refs react/react#11538

RetriggerConfidence Score: 4/5

The PR appears safe to merge, though translated pages can show an old status or a misplaced loading icon.

Summary

This PR keeps the dashboard updating when a browser translator replaces text nodes that React later tries to change. It applies the fix app-wide while leaving ordinary DOM calls unchanged.

  • Skips stale removals and appends new nodes when their reference is gone.
  • Loads the patch from the app entry point.

Reviews (1) · Last reviewed commit: 514c225

@turin-dev
turin-dev requested a review from Siumauricio as a code owner October 4, 2026 04:47
Browser translators replace React-managed text nodes with <font>
elements. React then calls removeChild/insertBefore with nodes that are
no longer children of the parent, which throws NotFoundError, unmounts
the app and shows the 500 error page (e.g. when clicking any Button
that toggles isLoading).

Patch Node.prototype.removeChild/insertBefore so only those
would-be-throwing calls are handled gracefully, keeping translation
usable. See react/react#11538.
@turin-dev
turin-dev force-pushed the fix/google-translate-crash branch from 514c225 to d2fd25b Compare October 4, 2026 04:48
Comment on lines +14 to +16
if (child.parentNode !== this) {
return child;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Old status stays visible

When a translator replaces a conditional text node, removeChild returns without removing the visible <font> replacement. If a swarm node loses its manager status, the old status can remain on screen. The fallback needs to account for the translator’s visible node, not just the detached text node.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional trade-off. Translators detach the original text node and insert their own <font> without keeping any link back to it, so from removeChild there's no reliable way to tell which <font> replaced the node React is removing. Guessing could remove unrelated content.

The goal of this patch is to stop the app from crashing (currently the whole tree unmounts and users get the 500 page). Text that changes after translation can look stale until the next re-render, which is noted in the PR description as a known limitation. Fixing it properly means wrapping the affected conditional text in elements (e.g. <span>) component by component. That's out of scope here and can be done case by case if needed.

Comment on lines +26 to +29
if (child && child.parentNode !== this) {
// Appending keeps the new node visible and attached where React
// expects its parent to be, so later removals still work.
return originalInsertBefore.call(this, node, null) as T;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Loading icon moves right

When a translator detaches a button label, insertBefore appends the loading icon instead of placing it before the label. Button uses a flex row, so the icon appears on the wrong side. Preserve the intended position when the reference node has been replaced.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional. Once the translator has replaced the reference node, its original position is unknown, so inserting before it isn't possible. The two options are dropping the insertion (the spinner, or any new content, never shows up, and later inserts that use it as a reference fail the same way) or appending to the correct parent. Appending keeps the node visible and keeps React's view of the DOM consistent, so later removals work natively.

In practice, the visible effect is the spinner sitting on the other side of a translated button label while it's loading, which is cosmetic compared to the current crash. Untranslated pages are unaffected: the fallback only runs when the native call would have thrown.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s fair. Once the translator has detached the reference node, its original position cannot be recovered reliably. Appending is the safer fallback because it keeps the inserted node visible and attached to the parent React expects, allowing subsequent removals to proceed; dropping the insertion could leave React’s DOM state inconsistent and reproduce the same failure on later updates. The reversed spinner position is therefore an accepted cosmetic limitation, while untranslated pages continue through the native implementation unchanged.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant