Skip to content
17 changes: 17 additions & 0 deletions client/src/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";

import { fixExternalDOMMutationsCrashes } from "./utils/helpers/domMutation.utils";

// Prevent crashes from browser extensions like Google Translate
fixExternalDOMMutationsCrashes();

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter />
</StrictMode>,
);
});
51 changes: 51 additions & 0 deletions client/src/utils/helpers/domMutation.utils.ts
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should keep the original implementation from React.

  1. Add a comment like this:
    // Reference: https://github.com/facebook/react/issues/11538
    // This fixes React crashing when the DOM is manipulated by extensions, e.g. Google Translate
  2. Name the method something like fixExternalDOMMutationsCrashes()
  3. Use the implementation from Make React resilient to DOM mutations from Google Translate facebook/react#11538 (comment). As far as we know, it's the best implementation of the fix (from the React team).
  4. Instead of logging to console we can also consider sending warning or info to Sentry.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Guards against React crashes when the DOM is manipulated by extensions like Google Translate
// ? Reference: https://github.com/facebook/react/issues/11538#issuecomment-417504600
declare global {
interface Window {
__renku_appliedDOMFix?: boolean;
}
}

export function fixExternalDOMMutationsCrashes() {
Copy link
Copy Markdown
Member

@leafty leafty Jun 4, 2026

Choose a reason for hiding this comment

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

Note: I have seen implementations use a variable on window to ensure this is executed only once, e.g.

if (typeof window === 'object' && window.__renku_appliedDOMFix == null) {
  // ...body...
  window.__renku_appliedDOMFix = true;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That's a good suggestion to prevent accidental double run. Worst case, it's an unnecessary safeguard
ffa7631

// Prevent invoking the code twice
if (typeof window !== "object" || window.__renku_appliedDOMFix) return;

if (typeof Node === "function" && Node.prototype) {
const originalRemoveChild = Node.prototype.removeChild;
Node.prototype.removeChild = function <T extends Node>(child: T): T {
if (child.parentNode !== this) {
if (console) {
// eslint-disable-next-line no-console
console.warn(
"Cannot remove a child from a different parent",
child,
this,
);
}
return child;
}
return originalRemoveChild.call(this, child) as T;
};
Comment thread
leafty marked this conversation as resolved.

const originalInsertBefore = Node.prototype.insertBefore;
Node.prototype.insertBefore = function <T extends Node>(
newNode: T,
referenceNode: Node | null,
): T {
if (referenceNode && referenceNode.parentNode !== this) {
if (console) {
// eslint-disable-next-line no-console
console.warn(
"Cannot insert before a reference node from a different parent",
referenceNode,
this,
);
}
return newNode;
}
return originalInsertBefore.call(this, newNode, referenceNode) as T;
};
}
Comment thread
leafty marked this conversation as resolved.

window.__renku_appliedDOMFix = true;
}
Loading