next.js icon indicating copy to clipboard operation
next.js copied to clipboard

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

Open ethanfann opened this issue 1 year ago • 19 comments

Verify canary release

  • [X] 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 avatar Jun 13 '23 15:06 ethanfann

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

tdsprogramming avatar Jun 23 '23 22:06 tdsprogramming

I had the same problem. Have you solved it?

zhangxinyong12 avatar Jun 26 '23 06:06 zhangxinyong12

image

zhangxinyong12 avatar Jun 26 '23 07:06 zhangxinyong12

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.

donalffons avatar Jul 08 '23 12:07 donalffons

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 */}

igordanchenko avatar Jul 10 '23 20:07 igordanchenko

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?

JonatasCopernico avatar Jul 19 '23 20:07 JonatasCopernico

@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.

patricktansg avatar Jul 20 '23 02:07 patricktansg

@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>
  )
}

desnor avatar Aug 24 '23 23:08 desnor

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?

buesing avatar Aug 30 '23 11:08 buesing

Still here...

fuccsoc avatar Sep 16 '23 22:09 fuccsoc

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?

deverin avatar Sep 20 '23 22:09 deverin

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.

jaapaurelio avatar Oct 03 '23 14:10 jaapaurelio

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 :)

deverin avatar Oct 03 '23 15:10 deverin

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?

NielsVdA avatar Nov 09 '23 12:11 NielsVdA

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!).

Dynalon avatar Nov 23 '23 16:11 Dynalon

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.

joshunger avatar Dec 29 '23 18:12 joshunger

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.

plrthink avatar Jan 19 '24 07:01 plrthink

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.

nvsd avatar Feb 01 '24 17:02 nvsd

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.

altano avatar Feb 02 '24 00:02 altano

@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.

nvsd avatar Feb 02 '24 19:02 nvsd

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!

altano avatar Feb 03 '24 00:02 altano

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.

jSadoski avatar Feb 06 '24 17:02 jSadoski

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

willstaff avatar Feb 19 '24 17:02 willstaff

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

borzaka avatar Feb 22 '24 13:02 borzaka

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

salikn avatar Mar 17 '24 02:03 salikn

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>
    </>
  );
}

borzaka avatar Mar 17 '24 21:03 borzaka

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?

salikn avatar Mar 17 '24 23:03 salikn

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

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

borzaka avatar Mar 18 '24 08:03 borzaka

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

  • #63403 will help error the React hydration error message with the incorrect behavior in the error overlay
  • #63401 update the docs to correct the bad use case in nextjs documentation

huozhi avatar Mar 18 '24 10:03 huozhi

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[bot] avatar Apr 02 '24 00:04 github-actions[bot]