Blazity
Performance Optimization
Discover how Next.js's `next/script
` component simplifies and optimizes script loading, addressing performance, user experience, and SEO through intelligent loading strategies and callback functions.
This article is part of The Expert Guide to Next.js Performance Optimization, a comprehensive Ebook you can download for free here.
The guide covers every aspect of building fast, scalable web apps with Next.js, including:
- Streaming, Suspense & Hydration
- Third-Party Scripts
- Infrastructure
- Real-life Examples
- How to Measure Web Performance
- Bundle Analysis.
This article delves into how Next.js's next/script
component revolutionizes script loading and optimization, tackling common issues such as performance bottlenecks caused by blocking behavior, main thread monopolization by heavy scripts, and the complexities of traditional script loading strategies.
It highlights next/script
's ability to streamline script management through intelligent loading strategies (beforeInteractive
, afterInteractive
, lazyOnload
) and enhance control with callback functions (onLoad
, onReady
, onError
), ultimately improving Core Web Vitals, boosting SEO rankings, and enhancing the overall user experience. The article also introduces `@next/third-parties` for simplified integration of popular third-party scripts.
Script loading is critical to web development because it directly affects website performance, influencing user experience and search engine optimization (SEO). Understanding how these scripts interact with a browser's main thread is essential to optimizing loading times and page responsiveness.
Impact on the Main Thread
The main thread parses HTML, executes JavaScript, and renders page content. Since JavaScript is inherently blocking, when the browser encounters a script, it must pause other tasks until the script is downloaded and executed. This blocking behavior can cause significant delays in page interactivity and visual stability, especially if the script is large or served from a slow server.
Script Loading Strategies to Mitigate Blocking:
Pretty complicated, isn't it?
Fortunately, Next.js introduces a more streamlined and efficient method.
Next.js provides developers with the powerful next/script component to simplify and optimize script handling in modern web applications. It manages scripts intelligently based on loading performance and priority, helping to overcome the limitations of traditional script tags.
The component also offers greater flexibility by supporting callback functions such as onLoad, onReady, and onError, allowing developers to control script behavior better and respond to loading events.
Let’s dive deeper and understand its full capabilities.
The beforeInteractive loading strategy is for scripts that must run before the page becomes interactive. These scripts are embedded directly into the server-rendered HTML and are prioritized for early download and execution. This loading strategy ensures critical scripts influence the page’s initial load and behavior without delay.
Code Placement:
Scripts tagged with beforeInteractive are automatically placed within the <head> tag of the HTML document, and they include the defer attribute. This setup allows the browser to parse the HTML without blocking, download the script early, and execute it before the page is hydrated.
Use Cases:
Code
1import { Html, Head, Main, NextScript } from 'next/document';
2import Script from 'next/script';
3
4const Document = () => {
5 return (
6 <Html>
7 <Head />
8 <body>
9 <Main />
10 <NextScript />
11 <Script
12 strategy="beforeInteractive"
13 src="/path/to/bot-detector.js"
14 />
15 </body>
16 </Html>
17 );
18};
19
20export default Document;
21
22
The afterInteractive strategy loads scripts right after the page becomes interactive. It is meant for scripts that are important but not as critical as those using beforeInteractive. These scripts are injected on the client side and load only after Next.js completes hydration.
Code Placement:
You can place afterInteractive scripts anywhere within your components. They will execute only when the page or component containing them is rendered in the browser.
Use Cases:
Code
1import Script from 'next/script';
2
3export const Analytics = () => {
4 return (
5 <Script
6 strategy="afterInteractive"
7 src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
8 />
9 );
10};
11
lazyOnload scripts are loaded during the browser's idle time using requestIdleCallback, ensuring they do not impact the page's critical load path. This solution is ideal for non-essential functionalities that can wait until all higher-priority tasks are complete.
Code Placement:
You can place afterInteractive scripts anywhere within your components; they will only execute when the browser renders the containing page or component.
Use Cases:
Code
1import Script from 'next/script';
2
3export const SocialMediaWidget = () => {
4 return (
5 <Script
6 strategy="lazyOnload"
7 src="/path/to/social-media-widget.js"
8 />
9 );
10};
11
Next.js improves script management by supporting callback functions like onLoad, onReady, and onError. These callbacks give developers more control over script behavior at different stages of the loading process:
Here’s how you might use these callbacks in a Next.js project:
Code
1import Script from 'next/script';
2
3export const CustomScript = () => {
4 return (
5 <Script
6 src="https://example.com/important-script.js"
7 strategy="beforeInteractive"
8 onLoad={() => console.log("Script loaded successfully!")}
9 onReady={() => console.log("Script is ready to execute!")}
10 onError={(e) => console.error("Script failed to load!", e)}
11 />
12 );
13};
14
The @next/third-parties
library simplifies integrating and managing popular third-party scripts in Next.js apps by providing pre-built components tailored for common use cases. By building on the <Script>
component in Next.js, you can optimize loading strategies and improve the developer experience when handling external scripts.
<Script>
component for each tag.lazyOnload
and can be configured to use web workers, helping reduce main-thread usage and improving performance.Code
1import { GoogleTagManager, GoogleAnalytics } from "@next/third-parties/google";
2import { YouTubeEmbed } from "@next/third-parties/youtube";
3
4const Layout = ({ children }) => {
5 return (
6 <html lang="en">
7 <body>
8 {children}
9 <GoogleTagManager gtmId="GTM-XYZ" />
10 <GoogleAnalytics gaId="G-XYZ" />
11 <YouTubeEmbed videoid="dQw4w9WgXcQ" height={400} />
12 </body>
13 </html>
14 );
15}
16
17export default Layout
18
It also provides useful utility functions like sendGTMEvent for interacting with the GTM data layer. These functions simplify development and make code easier to understand.
Get a quote
“We are very happy with the outcomes and look forward to continuing to work with them on larger initiatives.”
Brian Grafola
CTO at Vibes