next.js
next.js copied to clipboard
bug(next/script): `<Script />` does not execute resource in page context
Verify canary release
- [X] I verified that the issue exists in the latest Next.js canary release
Provide environment information
(N/A)
From Replit:
Operating System:
Platform: linux
Arch: x64
Version: #21~20.04.1-Ubuntu SMP Fri Aug 5 12:53:07 UTC 2022
Binaries:
Node: 12.22.10
npm: 6.14.16
Yarn: 1.22.17
pnpm: N/A
Relevant packages:
next: 12.2.5-canary.7
eslint-config-next: N/A
react: 17.0.2
react-dom: 17.0.2
What browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
Describe the Bug
A StackOverflow user reported an issue loading a jQuery library with the <Script />
component. Even when the script is correctly placed in public/
and loaded with <Script src="..." />
, globalThis.$
is undefined
at runtime.
See this repro.
Expected Behavior
<Script src="..." />
should add the referenced code to the runtime bundle.
To test: <Head><Script src="/jquery-3.6.0.min.js" /></Head>
should result in useEffect(() => console.log(globalThis.$), [])
logging a non-nullish value. See repro.
Link to reproduction
https://replit.com/@ctjlewis/Next-Script-tags
To Reproduce
- Attempt to load a minified jQuery bundle in
public/
with<Script />
- Verify that
globalThis.$
is undefined in the page context at runtime
For the Replit, just hit "Run".
The jQuery loaded by the <Script />
component will certainly not be available when React hydrates your page by default (If it is available during useEffect
, then it is a bug and you should report it to Next.js).
<Script />
component is designed to handle non-essential scripts for you (E.g., Google Analytics is essential for website owners, but not essential for rendering the page). Thus Next.js will never load the script before React hydrates your page, resulting in a better performance and user experience.
In this case, jQuery should be considered an essential script for rendering the page, thus it shouldn't be loaded through <Script />
.
I would recommend the following approach:
- Includes jQuery and bootstrap in your bundle
import $ from 'assets/js/jquery-3.5.1.min.js';
import 'assets/js/bootstrap.bundle.min.js';
- Use
beforeInteractive
// _document.jsx
import { Html, Head, Main, NextScript } from 'next/document'
import Script from 'next/script'
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
{/** You can place it everywhere as long as it is inside a custom _document */}
{/** And Next.js will always inject the script to the <head /> */}
<Script
src="assets/js/jquery-3.5.1.min.js"
strategy="beforeInteractive"
></Script>
</body>
</Html>
)
}
I'm having this issue with "Google reCAPTCHA". It loads 9 times out of 10 (on a SSG page).
I correctly render the next/Script
tag inside a page within a FormComponent
<Script src="https://www.google.com/recaptcha/api.js" async defer />
I'm having similar issue.
The following code doesn't work at all.
<Head>
<Script
async
data-blockingmode="auto"
data-cbid={process.env.NEXT_PUBLIC_COOKIE_BOT_TOKEN}
id="Cookiebot"
src="https://consent.cookiebot.com/uc.js"
type="text/javascript"
/>
</Head>
The following variants works. But the first one throws warning messages in the console, and the second loads a bit later.
<Head>
<script
async
data-blockingmode="auto"
data-cbid={process.env.NEXT_PUBLIC_COOKIE_BOT_TOKEN}
id="Cookiebot"
src="https://consent.cookiebot.com/uc.js"
type="text/javascript"
></script>
</Head>
<Head></Head>
<Script
async
data-blockingmode="auto"
data-cbid={process.env.NEXT_PUBLIC_COOKIE_BOT_TOKEN}
id="Cookiebot"
src="https://consent.cookiebot.com/uc.js"
type="text/javascript"
/>