Skip to content

Conversation

@Kyujenius
Copy link

@Kyujenius Kyujenius commented Jan 24, 2026

Description

Fixes #6438

When using async or defer attributes on script tags via useHead or Scripts, React throws a hydration warning even if suppressHydrationWarning is used. This is because of how React handles hydration mismatches for specific attributes on script tags during streaming/suspense boundaries.

This PR solves the root cause by stripping async, defer, and src attributes from the script tag on the client-side only (during hydration).

  • Server: Renders full <script src="..." async /> to ensure the browser loads it.
  • Client: Renders a placeholder <script /> (via Asset.tsx) that matches the server's hydration expectation for a controlled script tag, effectively preventing the mismatch warning.

Solution

The Asset component now explicitly destructures and removes these attributes when !router.isServer:

if (!router.isServer) {
  const { src: _src, async: _async, defer: _defer, ...rest } = attrs || {}
  return (
    <script
      suppressHydrationWarning
      dangerouslySetInnerHTML={{ __html: '' }} // Explicitly empty
      {...rest}
    ></script>
  )
}
image

Verification

New tests added to cover these scenarios:

  1. E2E (script-duplication.spec.ts): Verifies that no hydration warnings are logged to the console when loading a page with async scripts.
  2. Unit (Scripts.test.tsx): Verifies that the server output correctly includes async/defer attributes (using regex to be resilient to attribute ordering).

Summary by CodeRabbit

  • New Features

    • Added an /async-scripts route demonstrating asynchronous and deferred script loading.
    • Included navigation link to the new async-scripts page.
  • Bug Fixes

    • Fixed script attribute handling to properly support async and defer attributes without hydration warnings.
  • Tests

    • Added test suite for async/defer script loading and hydration validation.

✏️ Tip: You can customize this high-level summary in your review settings.

Strip async, defer, and src attributes from script tags during client-side hydration to match the server-rendered output and prevent React hydration mismatch warnings.
Adds unit tests to verify server output includes async/defer attributes, and e2e tests to verify no hydration warnings occur on the client.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 24, 2026

📝 Walkthrough

Walkthrough

This PR fixes a hydration mismatch issue triggered by async scripts. It modifies the client-side script placeholder to exclude src, async, and defer attributes while preserving them server-side, adds e2e tests to verify correct behavior, and includes a new example route demonstrating async and deferred script loading.

Changes

Cohort / File(s) Summary
Core library fix
packages/react-router/src/Asset.tsx
Modified client-side script placeholder to exclude src, async, and defer attributes from destructuring, preventing hydration mismatches while maintaining server-side attribute rendering.
Unit tests
packages/react-router/tests/Scripts.test.tsx
Added tests verifying server-side rendered scripts include async/defer attributes and that client placeholders exclude src/async/defer attributes while preserving crossOrigin.
New example route
e2e/react-start/basic/src/routes/async-scripts.tsx, e2e/react-start/basic/src/routes/__root.tsx
Created new async-scripts route loading two scripts (one async, one deferred) with component rendering; added navigation link in root layout.
Route tree generation
e2e/react-start/basic/src/routeTree.gen.ts
Auto-generated route type mappings and module augmentations for the new AsyncScriptsRoute.
E2E tests
e2e/react-start/basic/tests/script-duplication.spec.ts
Added test suite validating async script hydration warning absence and correct loading of async/deferred scripts.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

🐰 A hop, a skip, and scripts align,
No hydration warnings—they're now benign!
Async attrs dance on server's side,
While client stays calm, with nothing to hide.
The rabbit rejoices! Scripts flow free,
From head to heart, in harmony! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: removing async/defer attributes during hydration to prevent React warnings, which is the core fix in the PR.
Linked Issues check ✅ Passed The PR addresses issue #6438 by stripping async/defer/src from script tags during client-side hydration while preserving them server-side, matching the expected behavior to eliminate hydration warnings.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the fix: modifying Asset.tsx for hydration handling, adding comprehensive tests (E2E and unit), and creating a demo route for testing purposes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Assets: async script triggers hydration mismatch

1 participant