Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cloudflare/vinext/llms.txt

Use this file to discover all available pages before exploring further.

The Script component provides optimized loading strategies for third-party scripts (analytics, ads, widgets) with control over when and how they execute.

Import

import Script from 'next/script'

Basic Usage

import Script from 'next/script'

export default function Page() {
  return (
    <>
      <Script src="https://example.com/script.js" />
      <div>Page content</div>
    </>
  )
}

API Reference

Props

src
string
Script source URL.
<Script src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID" />
strategy
'beforeInteractive' | 'afterInteractive' | 'lazyOnload' | 'worker'
default:"'afterInteractive'"
Loading strategy that controls when the script executes.
  • beforeInteractive — Load before page becomes interactive (SSR output)
  • afterInteractive — Load after page becomes interactive (default)
  • lazyOnload — Load during idle time (after window.load)
  • worker — Load in a web worker (requires Partytown)
<Script src="/critical.js" strategy="beforeInteractive" />
<Script src="/analytics.js" strategy="afterInteractive" />
<Script src="/ads.js" strategy="lazyOnload" />
<Script src="/heavy.js" strategy="worker" />
onLoad
(e: Event) => void
Callback invoked when the script has loaded.
<Script
  src="https://example.com/sdk.js"
  onLoad={() => {
    console.log('Script loaded')
    window.ExampleSDK.init()
  }}
/>
onReady
() => void
Callback invoked when the script is ready.Called after onLoad, and also on every re-render if the script is already loaded.
<Script
  src="https://maps.googleapis.com/maps/api/js"
  onReady={() => {
    new google.maps.Map(document.getElementById('map'))
  }}
/>
onError
(e: Event) => void
Callback invoked when the script fails to load.
<Script
  src="https://example.com/script.js"
  onError={(e) => {
    console.error('Script failed to load', e)
  }}
/>
id
string
Unique identifier for the script (prevents duplicate loading).
<Script id="google-analytics" src="https://www.googletagmanager.com/gtag/js" />
children
string
Inline script content.
<Script id="inline-script">
  {`console.log('Hello from inline script')`}
</Script>
dangerouslySetInnerHTML
{ __html: string }
Alternative to children for inline scripts.
<Script
  id="config"
  dangerouslySetInnerHTML={{
    __html: `window.CONFIG = ${JSON.stringify(config)}`
  }}
/>

Standard Script Attributes

All standard <script> attributes are supported:
type
string
Script MIME type (e.g., "module", "text/partytown").
async
boolean
Load script asynchronously.
defer
boolean
Defer script execution.
crossOrigin
string
CORS setting ("anonymous" or "use-credentials").
nonce
string
Nonce for Content Security Policy.
integrity
string
Subresource Integrity hash.

Loading Strategies

beforeInteractive

Load the script before the page becomes interactive. Rendered in SSR output. Use for:
  • Critical polyfills
  • Feature detection scripts
  • Scripts that must run before React hydration
import Script from 'next/script'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Script
          src="/polyfills.js"
          strategy="beforeInteractive"
        />
        {children}
      </body>
    </html>
  )
}
Only works in app/layout.tsx or pages/_document.tsx. Ignored elsewhere.

afterInteractive (Default)

Load the script after the page becomes interactive. Use for:
  • Analytics scripts
  • Tag managers
  • Chat widgets
import Script from 'next/script'

export default function Page() {
  return (
    <>
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
        strategy="afterInteractive"
      />
      <Script id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'GA_MEASUREMENT_ID');
        `}
      </Script>
      <div>Page content</div>
    </>
  )
}

lazyOnload

Load the script during idle time (after window.load + requestIdleCallback). Use for:
  • Non-critical scripts
  • Ads
  • Social media widgets
  • Anything that can wait
import Script from 'next/script'

export default function Page() {
  return (
    <>
      <Script
        src="https://connect.facebook.net/en_US/sdk.js"
        strategy="lazyOnload"
        onLoad={() => console.log('Facebook SDK loaded')}
      />
      <div>Page content</div>
    </>
  )
}

worker (Partytown)

Load the script in a web worker using Partytown. Use for:
  • Heavy analytics scripts
  • Anything that blocks the main thread
import Script from 'next/script'

export default function Page() {
  return (
    <>
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
        strategy="worker"
      />
      <div>Page content</div>
    </>
  )
}
Requires Partytown setup. Sets type="text/partytown" on the script tag.

Examples

Google Analytics

import Script from 'next/script'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Script
          src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_ID}`}
          strategy="afterInteractive"
        />
        <Script id="google-analytics" strategy="afterInteractive">
          {`
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '${process.env.NEXT_PUBLIC_GA_ID}');
          `}
        </Script>
        {children}
      </body>
    </html>
  )
}

Facebook Pixel

import Script from 'next/script'

export default function Page() {
  return (
    <>
      <Script id="facebook-pixel" strategy="afterInteractive">
        {`
          !function(f,b,e,v,n,t,s)
          {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
          n.callMethod.apply(n,arguments):n.queue.push(arguments)};
          if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
          n.queue=[];t=b.createElement(e);t.async=!0;
          t.src=v;s=b.getElementsByTagName(e)[0];
          s.parentNode.insertBefore(t,s)}(window, document,'script',
          'https://connect.facebook.net/en_US/fbevents.js');
          fbq('init', '${process.env.NEXT_PUBLIC_FB_PIXEL_ID}');
          fbq('track', 'PageView');
        `}
      </Script>
      <noscript>
        <img
          height="1"
          width="1"
          style={{ display: 'none' }}
          src={`https://www.facebook.com/tr?id=${process.env.NEXT_PUBLIC_FB_PIXEL_ID}&ev=PageView&noscript=1`}
        />
      </noscript>
      <div>Page content</div>
    </>
  )
}

External SDK

import Script from 'next/script'
import { useState } from 'react'

export default function Page() {
  const [sdkReady, setSdkReady] = useState(false)
  
  return (
    <>
      <Script
        src="https://sdk.example.com/v1/sdk.js"
        strategy="afterInteractive"
        onLoad={() => {
          window.ExampleSDK.init({ apiKey: process.env.NEXT_PUBLIC_API_KEY })
          setSdkReady(true)
        }}
        onError={() => {
          console.error('Failed to load Example SDK')
        }}
      />
      
      {sdkReady ? (
        <button onClick={() => window.ExampleSDK.doSomething()}>
          Use SDK
        </button>
      ) : (
        <div>Loading SDK...</div>
      )}
    </>
  )
}

Inline Configuration

import Script from 'next/script'

export default function Page() {
  const config = {
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
    features: ['feature1', 'feature2'],
  }
  
  return (
    <>
      <Script
        id="app-config"
        strategy="beforeInteractive"
        dangerouslySetInnerHTML={{
          __html: `window.APP_CONFIG = ${JSON.stringify(config)}`
        }}
      />
      <div>Page content</div>
    </>
  )
}

Deduplication

Scripts with the same id or src are only loaded once:
// Both components render the same script — only loaded once
function ComponentA() {
  return <Script id="analytics" src="/analytics.js" />
}

function ComponentB() {
  return <Script id="analytics" src="/analytics.js" />
}

SSR Behavior

  • beforeInteractive: Rendered in SSR HTML output
  • afterInteractive: Not rendered (injected client-side)
  • lazyOnload: Not rendered (injected client-side)
  • worker: Not rendered (injected client-side)

Performance

Impact on Core Web Vitals

StrategyFCPLCPCLSTBT
beforeInteractive🟡🟡🟡
afterInteractive🟡
lazyOnload
worker

Best Practices

  1. Use lazyOnload for non-critical scripts
  2. Minimize beforeInteractive usage (blocks hydration)
  3. Use worker for heavy analytics (requires Partytown)
  4. Always provide id for inline scripts (enables deduplication)
  5. Avoid blocking the main thread

Limitations

beforeInteractive placement: Only works in app/layout.tsx or pages/_document.tsx. Silently ignored in page components.
No SSR execution: Inline scripts do not execute during SSR. Use them for browser-only code.
CSP nonces: If using CSP, pass nonce to all inline scripts.

Migration from HTML script tag

HTMLNext.js Script
<script src="..."><Script src="..." />
<script async src="..."><Script src="..." strategy="afterInteractive" />
<script defer src="..."><Script src="..." strategy="afterInteractive" />
<script>...</script> (in head)<Script id="..." strategy="beforeInteractive">...</Script>
<script>...</script> (in body)<Script id="...">...</Script>

Source

View source code → Implementation: /home/daytona/workspace/source/packages/vinext/src/shims/script.tsx

Build docs developers (and LLMs) love