Skip to content

Script Optimization

Third-party scripts — analytics, chat widgets, ad tags — are a common source of slow, janky pages if loaded carelessly. next/script gives you explicit control over when each script loads relative to the rest of the page.

Controlling Script Loading with a Strategy

A plain <script> tag in the <head> blocks the browser from rendering the rest of the page until it downloads and executes, which is rarely what you want for non-critical third-party code. next/script's strategy prop lets you explicitly choose when a script should load relative to the page's own content.

Choosing the right strategy per script — rather than loading everything eagerly — is one of the simpler, highest-leverage performance wins available in a Next.js app with several third-party integrations.

import Script from "next/script";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <Script
        src="https://analytics.example.com/script.js"
        strategy="afterInteractive"
      />
    </>
  );
}

afterInteractive loads this analytics script only after the page has become interactive, avoiding any render-blocking impact.

Available Loading Strategies

strategy="beforeInteractive"  // loads before any page content is interactive
strategy="afterInteractive"   // loads right after the page becomes interactive (default)
strategy="lazyOnload"         // loads during idle time, well after everything else
strategy="worker"             // (experimental) runs off the main thread via a web worker
  • beforeInteractive should be reserved for scripts truly required before anything else, like bot detection.
  • afterInteractive is the default and right choice for most analytics and tag manager scripts.
  • lazyOnload is ideal for low-priority scripts like chat widgets or non-critical ads.
  • Choosing the least aggressive strategy that still meets the script's actual requirement improves performance.

Script Strategy Cheat Sheet

Which strategy fits which type of script.

Script Type Recommended Strategy
Bot/fraud detection (rare) beforeInteractive
Analytics (Google Analytics, etc.) afterInteractive
Tag managers afterInteractive
Chat widgets lazyOnload
Non-critical ads lazyOnload

Inline Scripts and onLoad Handlers

next/script also supports inline script content and lifecycle event handlers like onLoad, useful for running code that depends on a third-party script finishing initialization.

<Script
  src="https://widget.example.com/embed.js"
  strategy="lazyOnload"
  onLoad={() => {
    console.log("Widget script has finished loading");
  }}
/>

Avoiding Duplicate Script Loads

Placing a <Script> in a layout used by many nested routes ensures the script loads once for that section, rather than being duplicated if it were placed in every individual page — Next.js also deduplicates identical script tags automatically across navigations.

Common Mistakes

  • Using a plain <script> tag instead of next/script, losing control over loading strategy.
  • Defaulting every third-party script to beforeInteractive, blocking meaningful content unnecessarily.
  • Placing the same script in multiple pages instead of a shared layout, risking duplicate loads.
  • Not using onLoad when code genuinely needs to run only after a script has finished initializing.

Key Takeaways

  • next/script gives explicit control over third-party script loading via the strategy prop.
  • afterInteractive is the default and fits most analytics and tag manager use cases.
  • lazyOnload is best for low-priority widgets that shouldn't compete with page content for resources.
  • beforeInteractive should be reserved for scripts genuinely required before interactivity.
  • Placing shared scripts in a layout avoids duplication across nested routes.

Pro Tip

Audit every third-party script currently loaded eagerly in your app and ask, honestly, whether it needs to block anything — most analytics and marketing scripts have no real dependency on loading before the page is interactive, and moving them to afterInteractive or lazyOnload is often a five-minute change with a real, measurable performance benefit.