Lesson 23 of 81
A raw <script> tag gives you exactly one loading behavior. Third-party scripts don't all deserve the same one — a cookie consent manager and a chat widget have opposite urgency, and next/script's strategy prop is the dial that says so.
9 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
A `<Script src="..." />` is added to a page with no `strategy` prop set. When does it actually load?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Every third-party script you add — analytics, a chat widget, a bot detector — competes with your own page's JavaScript for the same main thread. next/script's strategy prop is how you tell Next.js when a given script actually needs to run, so low-priority scripts don't get to slow down high-priority ones.
There are four strategies, ordered from "load earliest" to "load whenever":
beforeInteractive — injected into the server-rendered HTML, downloaded before any Next.js code, executed before the page hydrates.afterInteractive (the default) — injected client-side, loads early but after some hydration has already happened.lazyOnload — injected client-side during browser idle time, after everything else on the page has already loaded.worker (experimental) — offloaded to a web worker via Partytown, so it never touches the main thread at all.// 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://cdn.cookie-consent.example/init.js" strategy="beforeInteractive" />
</body>
</html>
);
}
// app/page.tsx
import Script from "next/script";
export default function Page() {
return (
<>
<Script src="https://cdn.analytics.example/tag.js" strategy="afterInteractive" />
<Script src="https://cdn.chat-widget.example/embed.js" strategy="lazyOnload" />
</>
);
}
The cookie consent manager has to be evaluated before anything else runs, since it can gate whether other scripts and cookies are even allowed to fire — that's a genuine "must be ready in time" case, so it earns beforeInteractive, and it must live in the root layout since that's the only place this strategy is supported. The analytics tag manager wants to start recording pageviews soon, but a moment's delay after hydration doesn't lose real data — afterInteractive, the default, is the right level of urgency, not more. The chat widget is the lowest priority of the three: nobody is angry that the chat bubble took an extra second to appear, so lazyOnload gives the main thread the least competition from it.
afterInteractive is the default, not beforeInteractive. Adding a <Script> with no strategy prop already defers it past initial hydration — you don't need to set anything to get the "safe" default behavior.beforeInteractive scripts must live in the root layout, because that's the only file guaranteed to render for every page — placing one anywhere else doesn't give it that "loads for the whole site" guarantee the strategy implies.worker doesn't work in the App Router yet. It's explicitly experimental and, as of this doc, only usable in the pages/ directory — reaching for it in an App Router project won't do what the name suggests, however tempting "off the main thread entirely" sounds.onLoad and onError require a Client Component and can't be combined with beforeInteractive. They're for reacting to afterInteractive/lazyOnload scripts finishing (or failing) — not a general-purpose script lifecycle hook usable everywhere.beforeInteractive for something that could tolerate lazyOnload isn't "extra safe," it's actively worse for the metric it's supposed to protect."You have a cookie consent script, an analytics tag, and a chat widget to add — how do you load each one?" tests whether a candidate treats third-party scripts as one undifferentiated blob (load them all the same way) or actually reasons about each script's real urgency against the cost it imposes on the main thread.