Script Optimization (next/script)
<script> tag dropped into the page can block HTML parsing while it downloads and executes, delaying everything else from rendering. The <Script> component from next/script gives you explicit control over when a script loads relative to the rest of the page.<script src="..."> tag placed in the <head> or body without any async/defer attribute is render-blocking by default — the browser pauses parsing the rest of the HTML until that script finishes downloading and running. On a slow third-party server, this alone can add seconds to the time before anything appears on screen.The strategy prop
<Script> accepts a strategy prop that tells Next.js exactly when to load and execute the script.Strategy | Loads | Typical use case |
|---|---|---|
| Before any page JavaScript, injected into the initial HTML | Bot detection, consent management — scripts that must run before the page is interactive |
| Early on, right after the page becomes interactive | Most analytics tools, tag managers |
| During idle time, after everything else has loaded | Chat widgets, social share buttons — low-priority scripts |
A worked analytics example
app/layout.tsx
import Script from 'next/script'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="afterInteractive"
/>
<Script id="ga-init" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
`}
</Script>
</body>
</html>
)
}<Script> has no src — it wraps an inline script instead, which is exactly how you initialize a library after its main bundle has loaded. Giving inline scripts a unique id is required so Next.js can track and dedupe them correctly.strategy="beforeInteractive" should be placed in the root layout, and Next.js automatically injects them into the document's <head> regardless of where in the component tree they're written — this strategy is reserved for scripts your page truly cannot function without.onLoad and onError
'use client'
import Script from 'next/script'
export default function ChatWidget() {
return (
<Script
src="https://widget.example.com/chat.js"
strategy="lazyOnload"
onLoad={() => console.log('Chat widget loaded')}
onError={(e) => console.error('Failed to load chat widget', e)}
/>
)
}<Script>replaces plain<script>tags to control exactly when a third-party script loads relative to page rendering.strategy="beforeInteractive"loads before any page JS — reserve it for scripts the page cannot function without.strategy="afterInteractive"(the default) suits most analytics and tag manager scripts.strategy="lazyOnload"defers low-priority scripts like chat widgets until the browser is idle.