next.js
next.js copied to clipboard
Any <Script /> with strategy="beforeInteractive" in RootLayout causes hydration errors
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
-
git clone https://github.com/ethanfann/beforeInteractive-script-rootLayout-hydration-errors
-
cd beforeInteractive-script-rootLayout-hydration-errors
-
npm install
-
npm run dev
- Navigate to
localhost:3000
- 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
I am having this issue as well - any updates? @ethanfann Did you find a solution?
I had the same problem. Have you solved it?
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.
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
'sbeforeInteractive
strategy should not be used outside ofpages/_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 */}
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 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.
@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>
)
}
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?
Still here...
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
-
First of all, make your Script component empty closing
<Script src="https://yourURL.to/somewhere/script.is.js" strategy="beforeInteractive"></Script>
-
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:
- Error - server-client mismatch
- 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:
-
<link rel="preload" as="script" href="https://yourURL.to/somewhere/script.is.js">
- comes higher in the<head>
-
<script src="https://yourURL.to/somewhere/script.is.js"></script>
- comes after the<link>
above
Could someone please confirm?
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.
Adding the
<Script>
withstrategy="beforeInteractive"
insidebody
orhead
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 :)
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?
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!).
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.
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.
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.
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 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.
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!
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.
Also encountering this issue, and disabling edge runtime isn't an option I have. Has anyone discovered any workarounds for this?
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
any luck solving this problem? I'm trying to add OneTrust through beforeinteractive and i'm heaving the same error.
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>
</>
);
}
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?
Yes @salikn, you can add more native <script>
s with dangerouslySetInnerHTML
:
<script
dangerouslySetInnerHTML={{
__html: `
...
`,
}}
></script>
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
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.