Skip to content

Fix React #418 hydration errors and keep the copy-page button after navigation - #1261

Open
willeastcott wants to merge 2 commits into
mainfrom
claude/youthful-bhabha-caae55
Open

willeastcott wants to merge 2 commits into
mainfrom
claude/youthful-bhabha-caae55

Conversation

@willeastcott

Copy link
Copy Markdown
Contributor

Summary

Docs pages log a recoverable React #418 hydration error, and the Copy page button disappears after client-side navigation. On production the button is often missing on first load too, because React client-renders the mismatched tree and drops it. This PR fixes both problems.

#418: third-party scripts changing the SSR HTML before hydration

  • Prism. redocusaurus provides prismjs for every free Prism reference in the bundle (webpack ProvidePlugin). As a result, the Prism language components loaded for prism.additionalLanguages pull Prism's core into the main bundle, and that core highlights every code block on DOMContentLoaded. A new client module, src/client-modules/prism-manual.js, sets Prism.manual = true. Docusaurus and Redoc both highlight explicitly, so nothing depended on the automatic pass. prismjs becomes a direct dependency; it was already installed transitively at 1.30.0.
  • Copy-page button. The plugin inserts its button into the <article> or ToC 100 ms after DOMContentLoaded, often mid-hydration. It now runs with injectButton: false, and src/client-modules/copy-page-button.js loads the plugin's own client module from the first onRouteDidUpdate, after hydration.

The button vanishing after navigation

The plugin detects navigation by patching history.pushState and polling the URL. When the URL changes, Docusaurus is still showing the old page while it preloads the next route. The plugin finds its button still attached and does nothing, so the button goes with the old article when React mounts the new one. The plugin also listens for a docusaurus-route-update event that Docusaurus never dispatches. The client module now dispatches that event from later onRouteDidUpdate calls, which run in a layout effect after the new route is committed. Hash-only changes keep the same page and are skipped.

The commits are separate: the first is the #418 fix and the second is the navigation fix.

Test plan

  • npm run build (en + ja) passes with no warnings
  • Ran npm run serve and drove headless Chrome over CDP with real clicks on sidebar links (through the mobile drawer below 996 px). After every navigation there is exactly one button, and a MutationObserver confirms two never exist at once.
    • 1400 px, ToC pages (/user-manual/scripting/ and siblings): button at the top of the ToC rail
    • 1400 px, no-ToC pages (/user-manual/react/examples/*) and switching between no-ToC and ToC pages: after the breadcrumbs or on the rail, as appropriate
    • 800 px, same flows: button after the breadcrumbs
    • Hash links in the desktop and mobile ToC keep the same button element
    • Back and forward
    • 6× CPU throttling, en and ja locales
    • Clicking Copy page after a navigation copies the new page's markdown
  • No React Add page on shader chunk migrations #418 or other console errors in any of these runs
  • Baseline: the same script against production shows the button inserted before hydration, Add page on shader chunk migrations #418, and no button after navigating

Not fixed here: leaving /user-manual/react/examples/physics/ through the sidebar shows "This page crashed" (Cannot read properties of null (reading 'deleteBuffer')). It also happens on production and is being investigated separately.

🤖 Generated with Claude Code

willeastcott and others added 2 commits October 2, 2026 14:35
Two third-party scripts changed the server-rendered HTML before React
hydrated it:

- redocusaurus provides prismjs for every free `Prism` reference, which
  puts Prism's core in the main bundle, and that core highlights every
  code block on DOMContentLoaded. Set `Prism.manual` from a client module.
- docusaurus-plugin-copy-page-button inserts its button into the article
  or ToC 100 ms after DOMContentLoaded, often mid-hydration. Run it with
  `injectButton: false` and load its client module from the first
  `onRouteDidUpdate` instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plugin re-checks its button when the URL changes, but Docusaurus is
still showing the old page then, so the button is left in place and
goes with the old article when the new page replaces it. The plugin also
re-checks on a `docusaurus-route-update` event that Docusaurus never
sends, so send it from `onRouteDidUpdate`, which runs once the new page
is in the DOM. Hash-only changes keep the same page and are skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — fd2d2658 Deployed Oct 2, 2026 by vercel[bot]
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