How to Choose next/script Loading Strategies
This guide is part of Next.js Performance, within Framework Performance. Third-party scripts — analytics, tag managers, consent platforms, chat widgets, A/B testing, ads, social embeds — often cost more main-thread time than the application itself. In a Next.js app, they compete with hydration: if a tag manager executes 200ms of JavaScript while React hydrates, both take longer, and interactions during that window are slow.
next/script controls when scripts load. beforeInteractive injects scripts into the initial HTML so they load before hydration. afterInteractive (the default) loads scripts after hydration begins on the client. lazyOnload waits until the browser is idle after load. worker (experimental) runs scripts in a web worker via Partytown. Choosing well — and loading some scripts only on interaction — protects LCP and INP without losing the scripts' function.
Rapid Diagnosis
- List third-party scripts and how they are loaded (
next/scriptstrategy, raw<script>tags, injected by a tag manager). - Record a load trace with CPU throttling; attribute long tasks to script URLs.
- Use Long Animation Frames in RUM to see which scripts run during slow interactions.
- Check whether each script is needed early (consent banners may be; chat is not).
Root Cause Analysis
1. Everything loads early. Scripts default to early loading or are placed in the document head.
2. Tag managers inject more scripts. One container loads many tags at once.
3. Heavy widgets on every page. Chat and social embeds load for all users.
4. beforeInteractive overuse. Scripts delay hydration and compete with the LCP.
Step-by-Step Resolution
1. Assign a strategy per script
import Script from 'next/script';
export default function RootLayout({ children }) {
return (<html lang="en"><body>
{children}
<Script src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXX" strategy="afterInteractive" />
<Script src="https://widget.example-chat.com/loader.js" strategy="lazyOnload" />
</body></html>);
}
// trade-off: lazyOnload delays the chat widget until idle, so users who want to
// chat immediately wait a little; a facade button removes even that cost.
2. Load heavy widgets on interaction
Replace chat widgets and embeds with a lightweight facade (a button or image) that loads the real script when clicked.
3. Audit the tag manager
Remove unused tags, set triggers to fire after load or on interaction, and avoid tags that inject synchronous scripts.
4. Reserve beforeInteractive for genuine needs
Use it only for scripts that must run before the page is interactive (some consent or bot-detection scripts). Everything else belongs later.
Verification
Record traces before and after: long tasks during hydration should shrink, and third-party script execution should move later. In the field, compare INP for early interactions and TBT-related metrics. Confirm scripts still work (analytics hits arrive, chat opens).
Worked Example: A SaaS Marketing Site
A SaaS marketing site loaded a tag manager (with 14 tags), a chat widget, a session replay tool and an A/B testing script, all via raw script tags in the document head. On mobile, TBT was 1.1 seconds and INP p75 310ms. The team moved the tag manager to afterInteractive and removed six unused tags, switched the chat widget to a facade that loads on click, moved session replay to lazyOnload with 10% sampling, and moved A/B testing to edge middleware so variants were rendered server-side. TBT fell to 290ms, INP p75 to 160ms, and LCP improved by 300ms because the A/B script no longer hid the page while waiting.
The worker Strategy
The experimental worker strategy uses Partytown to run third-party scripts in a web worker, moving their execution off the main thread. It works well for scripts that mostly send data (analytics) and poorly for scripts that need synchronous DOM access (widgets that render UI). Because DOM access from the worker is proxied, scripts that touch the DOM frequently can become slower. Test each script carefully and monitor data accuracy.
Measuring Third-Party Cost
Long Animation Frames attribution reports, per long frame, which script URLs ran and for how long. Collecting this in RUM shows which third-party scripts contribute most to slow interactions in the field. Combine it with a lab trace on a throttled device to see main-thread time per script during load. A per-script cost table — main-thread time, bytes, and business owner — makes it easier to decide which scripts to defer, replace or remove.
Governance for Third-Party Scripts
Third-party scripts are usually added by marketing, analytics or support teams, often through a tag manager without a code review. Agree on simple rules: every script has an owner and a purpose, a loading strategy chosen with engineering, and a periodic review to remove scripts that are no longer used. Keep a register of scripts with their measured main-thread cost. When a new script is requested, test it on a staging build with a throttled profile and add it with the latest strategy that still meets its purpose. This turns third-party performance from a recurring firefight into a routine decision.
Common Mistakes
- Raw script tags in the head. Bypass
next/scriptand block parsing. - beforeInteractive for analytics. Delays hydration with no user benefit.
- Loading chat on every page. Most visitors never open it.
- Ignoring tags inside the tag manager. The container is light; its tags are not.
Edge Cases
Consent requirements. Some scripts must not load before consent; load them in response to the consent event.
Inline scripts. next/script supports inline scripts with an id; the same strategies apply.
Pages Router. beforeInteractive scripts belong in _document; other strategies work in pages and _app.
onLoad callbacks. Use onLoad or onReady to initialise scripts after they load rather than polling.
FAQ
What is the default next/script strategy?
afterInteractive, which loads the script after hydration begins on the client.
When should I use beforeInteractive?
Only for scripts that must run before the page is interactive, such as some consent or bot-protection scripts. Keep them small.
Is lazyOnload safe for analytics?
It delays analytics until idle, which may miss very short visits. afterInteractive is the usual choice for analytics.
Does the worker strategy work with all scripts?
No. Scripts that need synchronous DOM access may break or slow down. Test each one.
How do I load a script only on some pages?
Include the Script component in those pages or layouts instead of the root layout.
Can next/script load scripts on interaction?
Not directly; render the Script component conditionally after the user interacts (for example, after clicking a chat facade).
How do I know which third-party script hurts INP?
Use Long Animation Frames attribution in RUM, or record interactions in the Performance panel and check script URLs in long tasks.
Should A/B tests run client-side?
Server-side or edge experiments avoid blocking rendering. Client-side experiments that hide the page while loading hurt LCP.
Do scripts in the root layout load on every route?
Yes. Scripts rendered in the root layout load on every page; place scripts in specific layouts or pages if only some routes need them.
Can I measure each script's cost before adding it?
Yes. Add it on a preview deployment and record a throttled trace with and without it, comparing main-thread time and long tasks during load.
Related
- Reducing Next.js first load JavaScript — your own JavaScript.
- Replacing YouTube embeds with a facade — the facade pattern.
- Lazy-loading iframes — embeds without the cost.