Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any <Script /> with strategy="beforeInteractive" in RootLayout causes hydration errors #51242

Closed
1 task done
ethanfann opened this issue Jun 13, 2023 · 30 comments
Closed
1 task done
Assignees
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. linear: next Confirmed issue that is tracked by the Next.js team. locked

Comments

@ethanfann
Copy link

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000
    Binaries:
      Node: 18.16.0
      npm: 9.5.1
      Yarn: N/A
      pnpm: N/A
    Relevant packages:
      next: 13.4.6-canary.0
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0
      typescript: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true), Script optimization (next/script)

Link to the code that reproduces this issue or a replay of the bug

https://github.com/ethanfann/beforeInteractive-script-rootLayout-hydration-errors

To Reproduce

  1. git clone https://github.com/ethanfann/beforeInteractive-script-rootLayout-hydration-errors
  2. cd beforeInteractive-script-rootLayout-hydration-errors
  3. npm install
  4. npm run dev
  5. Navigate to localhost:3000
  6. View hydration errors

Describe the Bug

Following the docs for loading a script using the beforeInteractive strategy within a RootLayout results in hydration errors.

Ex:

import Script from 'next/script'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
      <Script
        src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"
        strategy="beforeInteractive"
      />
    </html>
  )
}

Expected Behavior

No hydration errors should occur with barebones example sites that follow official documentation.

Which browser are you using? (if relevant)

Chrome 114.0.5735.106 (Official Build) (arm64)

How are you deploying your application? (if relevant)

No response

@ethanfann ethanfann added the bug Issue was opened via the bug report template. label Jun 13, 2023
@github-actions github-actions bot added the area: app App directory (appDir: true) label Jun 13, 2023
@housseindjirdeh housseindjirdeh self-assigned this Jun 13, 2023
@tdsprogramming
Copy link

tdsprogramming commented Jun 23, 2023

I am having this issue as well - any updates? @ethanfann Did you find a solution?

@zhangxinyong12
Copy link

I had the same problem. Have you solved it?

@zhangxinyong12
Copy link

image

@leerob leerob added the linear: next Confirmed issue that is tracked by the Next.js team. label Jun 26, 2023
@donalffons
Copy link

donalffons commented Jul 8, 2023

Having the same issues. I was also expecting this to work as shown in the original issue, based on the documentation and the fact that you could insert beforeInteractive scripts anywhere into _document.tsx with the good old page router.

@zhangxinyong12's workaround (of putting the <Script> into the <head> element) seems to work, though.

@igordanchenko
Copy link

There is one more related issue - ESLint is complaining about beforeInteractive being used outside of pages/_document.js (doh)

./app/layout.tsx
31:17 Warning: next/script's beforeInteractive strategy should not be used outside of pages/_document.js. See: https://nextjs.org/docs/messages/no-before-interactive-script-outside-document @next/next/no-before-interactive-script-outside-document

Had to suppress this false positive.

{/* eslint-disable-next-line @next/next/no-before-interactive-script-outside-document */}

@JonatasCopernico
Copy link

Having the same issues. I was also expecting this to work as shown in the original issue, based on the documentation and the fact that you could insert beforeInteractive scripts anywhere into _document.tsx with the good old page router.

@zhangxinyong12's workaround (of putting the <Script> into the <head> element) seems to work, though.

Tried doing that and the script disappeared... If I don't use the the script goes to the body (and I need the script at the head). Did you solve it?

@patricktansg
Copy link

@JonatasCopernico Can update here too if you manage to get it work in head with the AppRouter because I'm also searching this for a while but didn't manage to get it works.

@desnor
Copy link

desnor commented Aug 24, 2023

@ethanfann Does it make any difference if you place the Script tag inside the body tag? In the first example at the top of this issue it is actually after the body's closing tag, which I don't really know, but might be a problem. I am using multiple Script tags with strategy="beforeInteractive" inside the body tag and they are being loaded and the client side rendering is happening without any hydration issues.

What I'm suggesting to is to change the top example to this:

import Script from 'next/script'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"
          strategy="beforeInteractive"
        />
      </body>
    </html>
  )
}

@buesing
Copy link

buesing commented Aug 30, 2023

I have the same problem. I'm doing it exactly as described in the documentation here and getting hydration errors (and a linter warning). I feel like the docs should be updated to reflect the way it's supposed to be done?

nweldev added a commit to nweldev/eurocraties that referenced this issue Sep 4, 2023
add actual preferred color theme switch support via cookies

⚠️ This commit introduce several issues directly linked to the App
Router's current limitations :

1. preferred-color-scheme isn't supported anymore
2. every route is now dynamically rendered by default as we use cookies
in the root layout

A better approach would be to load a client script as
"beforeInteractive" to (1) check if a "color-theme" preferrence has been
set in the local storage (2) if not, set it based on
preferred-color-scheme and (3) add or remove the 'dark' class on
document.documentElement accordingly. Then, the DarkModToggle element
could work exactly as it is, but using the local storage instead
of a cookie.

Unfortunately, this wouldn't be a better approach at the moment as the
Next.js Script component isn't supported by the App Router.

see vercel/next.js#51242
and https://nextjs.org/docs/app/api-reference/functions/cookies
and https://nextjs.org/docs/pages/api-reference/components/script#beforeinteractive

inspired by https://michaelangelo.io/blog/darkmode-rsc
nweldev added a commit to nweldev/eurocraties that referenced this issue Sep 4, 2023
add actual preferred color theme switch support via cookies

⚠️ This commit introduce several issues directly linked to the App
Router's current limitations :

1. preferred-color-scheme isn't supported anymore
2. every route is now dynamically rendered by default as we use cookies
in the root layout

A better approach would be to load a client script as
"beforeInteractive" to (1) check if a "color-theme" preferrence has been
set in the local storage (2) if not, set it based on
preferred-color-scheme and (3) add or remove the 'dark' class on
document.documentElement accordingly. Then, the DarkModToggle element
could work exactly as it is, but using the local storage instead
of a cookie.

Unfortunately, this wouldn't be a better approach at the moment as the
Next.js Script component isn't supported by the App Router.

see vercel/next.js#51242
and https://nextjs.org/docs/app/api-reference/functions/cookies
and https://nextjs.org/docs/pages/api-reference/components/script#beforeinteractive

inspired by https://michaelangelo.io/blog/darkmode-rsc
@fuccsoc
Copy link

fuccsoc commented Sep 16, 2023

Still here...

@deverin
Copy link

deverin commented Sep 20, 2023

UPDATE 23/09: Don't use edge runtime if you want this to work

Hello Everyone

After fiddling around with this next/script component after reading your inputs, I manged to find a working solution. I need someone to confirm it.

I am on nextjs 13.4.19

Try this

  1. First of all, make your Script component empty closing
    <Script src="https://yourURL.to/somewhere/script.is.js" strategy="beforeInteractive"></Script>

  2. Place it in root layout
    note: async and usual functional component worked
    - right after <body> or
    - right before </body>
    Both of them worked for me

This also eliminated two issues for me:

  1. Error - server-client mismatch
  2. Warning - the resource was preloaded using link preload but not used within a few seconds

Result
Bunch of stuff gets pushed into the <head> but with this way around your <head> you should end up having two more tags:

  1. <link rel="preload" as="script" href="https://yourURL.to/somewhere/script.is.js"> - comes higher in the <head>
  2. <script src="https://yourURL.to/somewhere/script.is.js"></script> - comes after the <link> above

Could someone please confirm?

@jaapaurelio
Copy link

jaapaurelio commented Oct 3, 2023

Adding the <Script> with strategy="beforeInteractive" inside body or head it's creating hydrations errors, even following @deverin guidelines.

Warning: Expected server HTML to contain a matching <div> in <div>.
Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.

Next 13.5.4

-- Edit --
Adding right after <body> tag worked for me. Not woking any other place.

@deverin
Copy link

deverin commented Oct 3, 2023

Adding the <Script> with strategy="beforeInteractive" inside body or head it's creating hydrations errors, even following @deverin guidelines.


Warning: Expected server HTML to contain a matching <div> in <div>.


Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.

Next 13.5.4

-- Edit --

Adding right after <body> tag worked for me. Not woking any other place.

Did you use open and closing component tags? Like this:

<Script></Script>

not self closing

<Script/>

I have hydration issue when I use self-closing tag

I am on Next.js 13.4.9

Could you confirm please :)

@NielsVdA
Copy link

NielsVdA commented Nov 9, 2023

Even with deverin's setup (including <Script></Script>) Im still getting the hydration errors.
Can you maybe post your (root) layout file in total to make sure we're doing the same thing?

@Dynalon
Copy link

Dynalon commented Nov 23, 2023

This bug was giving me a real headache (I had an external script which is a wasm polyfill that was indeterministically present/not present depending on load speeds) until @deverin workaround. It works for me now (using all his mentioned steps!).

@joshunger
Copy link

joshunger commented Dec 29, 2023

If placed in <head> you also receive this warning (adding so others can find this issue):

Warning: Prop `dangerouslySetInnerHTML` did not match. Server: "" Client: "(self.__next_s=self.__next_s||[]).push([0,{\"children\":\"\\n          window.dataLayer = window.dataLayer || [];\\n          function gtag(){dataLayer.push(arguments);}\\n          gtag('consent','default',{\\n            'ad_storage':'denied',\\n            'analytics_storage':'denied',\\n            'wait_for_update': 500\\n          });\\n          gtag('set', 'ads_data_redaction', true);\\n          \"}])"
    at script
    at Script (webpack-internal:///(app-pages-browser)/../../node_modules/next/dist/client/script.js:175:13)
    at head
    at html

And the other common warning with this issue -

The resource ... was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate as value and it is preloaded intentionally.

@plrthink
Copy link

I found even using next/script with strategy="beforeInteractive" right inside the body tag, the scripts are getting executed before next and application code, actually they are injected after the initial assets. This makes polyfills not working.

@nvsd
Copy link

nvsd commented Feb 1, 2024

I'm getting this as well. Even doing the above workaround still gives me several errors.

@leerob any chance you could let us know what's happening with this? It's hard to convince my team that transitioning to next is a good idea when on render in dev you get several unavoidable errors right away.

@altano
Copy link

altano commented Feb 2, 2024

Thank you for editing your message.

Let’s all remember that this is a free, open source framework and we’re all on the line for fixing things. We are not entitled to a fix from Vercel.

@nvsd
Copy link

nvsd commented Feb 2, 2024

@altano Stop.
To be clear I only edited my message to remove mildly frustrated language. Let's not turn this into something it's not.

We are not entitled to a fix from Vercel.

Cmon man, are you serious? I'm not saying we're entitled to a fix. Vercel is a company that makes profit (~60 million?) by getting people to use their framework and deploy that on their infrastructure. They've provided no update on this issue. Of course I'm frustrated when I'm seeing an issue like this that hasn't gotten any response in 6 months.

If my statement is unhelpful then you coming in here and adding a nothing burger comment like that is even more so.

@altano
Copy link

altano commented Feb 3, 2024

I only edited my message to remove mildly frustrated language.
...
I'm not saying we're entitled to a fix.

Perfect, sounds like we're in total agreement!

@jSadoski
Copy link

jSadoski commented Feb 6, 2024

Just wanted to pop in here with my experience.

I needed to add a cookie consent manager as a beforeInteractive script. I followed all steps outlined by @deverin, with the caveat that my script was an inline script. I put my <Script></Script> tag immediately below and before .

The script tag is in the head, but hydration still fails and Next.js switches to client-side rendering.

Hopefully they're able to provide better support for beforeInteractive scripts in App Router soon.

Quick note about @jaapaurelio's approach: This populated the script in the body for me, which would not fit the behavior I needed from beforeInteractive.

EDIT: I previously said this worked without issues, but I was mistaken. My console errors were being minified, which I didn't realize was the hydration error in production.

@willstaff
Copy link

Also encountering this issue, and disabling edge runtime isn't an option I have. Has anyone discovered any workarounds for this?

@borzaka
Copy link

borzaka commented Feb 22, 2024

I'am trying to implement the CookiePro by OneTrust script. The script needs to be inside the <head>.

I have followed the official docs: https://nextjs.org/docs/app/api-reference/components/script#beforeinteractive

Inside my src/app/layout.tsx:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="hu">
      <body>{children}</body>

      {/* eslint-disable-next-line @next/next/no-before-interactive-script-outside-document,react/self-closing-comp */}
      <Script
        src="https://cookie-cdn.cookiepro.com/scripttemplates/otSDKStub.js"
        charSet="UTF-8"
        data-document-language="true"
        data-domain-script="XXXX"
        async
        strategy="beforeInteractive"
      ></Script>
    </html>
  );
}

And I'am getting this error:

Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.

Warning: Expected server HTML to contain a matching <script> in <html>.

See more info here: https://nextjs.org/docs/messages/react-hydration-error

I have tried to follow deverin, but with no luck.

Next.js version 13.5.6

@salikn
Copy link

salikn commented Mar 17, 2024

any luck solving this problem? I'm trying to add OneTrust through beforeinteractive and i'm heaving the same error.

@borzaka
Copy link

borzaka commented Mar 17, 2024

My solution: put it directly inside the native <head> in the root layout, and use the native <script> tag.

I know it's the opposite as the docs says/recommends, but it was the only solution that worked for me.
I understand that with this solution I'am not benefiting from any Next.js optimizations.

layout.tsx

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="hu">
      <head>
        <CookiePro />
      </head>
      <body>{children}</body>
    </html>
  );
}

cookiepro.tsx

export function CookiePro() {
  return (
    <>
      <script
        src="https://cookie-cdn.cookiepro.com/scripttemplates/otSDKStub.js"
        charSet="UTF-8"
        data-document-language="true"
        data-domain-script="XXXX"
        async
      ></script>

      <script
        dangerouslySetInnerHTML={{
          __html: `
            ...
          `,
        }}
      ></script>
    </>
  );
}

@salikn
Copy link

salikn commented Mar 17, 2024

Thanks @borzaka Yes this one actually work,
What about cookie table list etc..? you had to use dangerouslySetInnerHTML to set the html fragment that comes from OT right?

@borzaka
Copy link

borzaka commented Mar 18, 2024

Yes @salikn, you can add more native <script>s with dangerouslySetInnerHTML:

<script
  dangerouslySetInnerHTML={{
    __html: `
      ...
    `,
  }}
></script>

@huozhi
Copy link
Member

huozhi commented Mar 18, 2024

Putting Script in the head or body are the way to go, I filed two PRs to improve this:

huozhi added a commit that referenced this issue Mar 18, 2024
script tag cannot be placed under html directly, users reported a case
in #51242 that having `<Script>` under html will cause hydration error,
this will display the React hydration error related warning of bad usage
for it.

You will see this warning in dev overlay instead of displaying nothing
```
In HTML, <script> cannot be a child of <html>.
This will cause a hydration error.
```

Added two other react warnings detection patterns  as well
* `Warning: In HTML, text nodes cannot be a child of <%s>.\nThis will
cause a hydration error.',`
* `Warning: In HTML, whitespace text nodes cann...`

But tested they're not generating hydration errors, only warnings in
console, so we don't need to have tests for them.

Closes NEXT-2835
delbaoliveira added a commit that referenced this issue Mar 18, 2024
Related #51242 

React will error `In HTML, <script> cannot be a child of <html>. This
will cause a hydration error.` when `script` is rendered under `html`
directly. The examples in our docs are causing this hydration error.
Moving it under `body` tag will fix the hydration error

Closes NEXT-2834

Co-authored-by: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com>
@huozhi huozhi closed this as completed Mar 18, 2024
Copy link
Contributor

github-actions bot commented Apr 2, 2024

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot added the locked label Apr 2, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 2, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. linear: next Confirmed issue that is tracked by the Next.js team. locked
Projects
None yet
Development

No branches or pull requests