Skip to main content
doc0blade47/next.jsdocumentation home
Sign in

Third Party Libraries

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

The @next/third-parties package provides optimized React components and utility functions for popular third-party libraries and services in Next.js applications. By integrating directly with Next.js script loading lifecycles and performance measurement APIs, it improves loading performance and simplifies the setup of third-party tools such as Google Analytics, Google Tag Manager, YouTube embeds, and Google Maps.

Sources: packages/third-parties/package.json:1-37, packages/third-parties/src/google/ga.tsx:1-57, packages/third-parties/src/google/gtm.tsx:1-75, packages/third-parties/src/google/youtube-embed.tsx:1-35, packages/third-parties/src/google/google-maps-embed.tsx:1-18

Package architecture and export surface

Package exports and module definitions

The @next/third-parties package (packages/third-parties) defines its public API via package manifest exports targeting ./google. The distribution files are generated using TypeScript (tsc) and placed in the dist directory, with published distribution contents including both compiled outputs and the google.d.ts type definitions.

Sources: packages/third-parties/package.json:1-37

The package exposes Google-related integrations through a single entry point mapped in package.json:

  "exports": {
    "./google": {
      "types": "./dist/google/index.d.ts",
      "default": "./dist/google/index.js"
    }
  },

Sources: packages/third-parties/package.json:8-13

The entry point file packages/third-parties/src/google/index.tsx re-exports four core components and utility functions:

export { default as GoogleMapsEmbed } from './google-maps-embed'
export { default as YouTubeEmbed } from './youtube-embed'
export { GoogleTagManager, sendGTMEvent } from './gtm'
export { GoogleAnalytics, sendGAEvent } from './ga'

Sources: packages/third-parties/src/google/index.tsx:1-4

Dependencies and peer constraints

The package relies on third-party-capital for third-party script rendering logic and enforces peer dependencies across Next.js and React versions.

Dependency TypePackage NameVersion Specifier / Range
Dependencythird-party-capital1.0.20
Dev Dependencynextworkspace:*
Peer Dependencynext^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
Peer Dependencyreact^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0

Sources: packages/third-parties/package.json:25-37

Note

Global window extensions in packages/third-parties/src/types/google.ts augment the global Window interface with an optional dataLayer array of objects and dynamic index properties to support analytics event dispatching. Sources: packages/third-parties/src/types/google.ts:1-6

Script loader integration and strategy

Execution Strategies and Component Lifecycle

The core script component defined in packages/next/src/client/script.tsx supports multiple execution strategies for third-party scripts. The entry point packages/next/script.js proxies directly to this client module export.

function Script(props: ScriptProps): JSX.Element | null {
  const {
    id,
    src = '',
    onLoad = () => {},
    onReady = null,
    strategy = 'afterInteractive',
    onError,
    stylesheets,
    ...restProps
  } = props

Sources: packages/next/src/client/script.tsx:200-210

The component relies on two refs (hasOnReadyEffectCalled and hasLoadScriptEffectCalled) to ensure that onLoad, onReady, and script injection callbacks execute precisely once across initial mounts and strict mode re-runs or <OffScreen /> boundary toggles.

const hasOnReadyEffectCalled = useRef(false)
 
useEffect(() => {
  const cacheKey = id || src
  if (!hasOnReadyEffectCalled.current) {
    // Run onReady if script has loaded before but component is re-mounted
    if (onReady && cacheKey && LoadCache.has(cacheKey)) {
      onReady()
    }
 
    hasOnReadyEffectCalled.current = true
  }
}, [onReady, id, src])
 
const hasLoadScriptEffectCalled = useRef(false)
 
useEffect(() => {
  if (!hasLoadScriptEffectCalled.current) {
    if (strategy === 'afterInteractive') {
      loadScript(props)
    } else if (strategy === 'lazyOnload') {
      loadLazyScript(props)
    }
 
    hasLoadScriptEffectCalled.current = true
  }
}, [props, strategy])

Sources: packages/next/src/client/script.tsx:245-271

App Directory and Preloading Integration

When operating inside the App Directory (appDir), the component leverages ReactDOM.preinit for stylesheets and ReactDOM.preload for scripts. Before-interactive scripts push serialized payload configurations into a global queue self.__next_s instead of relying on native defer attributes.

// Before interactive scripts need to be loaded by Next.js' runtime instead
// of native <script> tags, because they no longer have `defer`.
if (strategy === 'beforeInteractive') {
  if (!src) {
    // For inlined scripts, we put the content in `children`.
    if (restProps.dangerouslySetInnerHTML) {
      // Casting since lib.dom.d.ts doesn't have TrustedHTML yet.
      restProps.children = restProps.dangerouslySetInnerHTML
        .__html as string
      delete restProps.dangerouslySetInnerHTML
    }
 
    return (
      <script
        nonce={nonce}
        dangerouslySetInnerHTML={{
          __html: `(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString(
            JSON.stringify([0, { ...restProps, id }])
          )})`,
        }}
      />
    )
  } else {
    // @ts-ignore
    ReactDOM.preload(
      src,
      restProps.integrity
        ? {
            as: 'script',
            integrity: restProps.integrity,
            nonce,
            crossOrigin: restProps.crossOrigin,
          }
        : { as: 'script', nonce, crossOrigin: restProps.crossOrigin }
    )
    return (
      <script
        nonce={nonce}
        dangerouslySetInnerHTML={{
          __html: `(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString(
            JSON.stringify([src, { ...restProps, id }])
          )})`,
        }}
      />
    )
  }

Sources: packages/next/src/client/script.tsx:315-360

Note

beforeInteractive scripts without a src attribute take their contents from dangerouslySetInnerHTML.__html, assigning it to restProps.children after sanitizing the markup structure. Sources: packages/next/src/client/script.tsx:318-325

Google Analytics and Tag Manager

Client Component Implementation and DataLayer Initialization

The GoogleAnalytics and GoogleTagManager components are implemented as Client Components, indicated by the 'use client' directive. Both components emit a feature usage telemetry signal via performance.mark('mark_feature_usage', { detail: { feature: 'next-third-parties-ga' } }) or next-third-parties-gtm inside an useEffect hook upon mounting.

Sources: packages/third-parties/src/google/ga.tsx:1-34, packages/third-parties/src/google/gtm.tsx:1-51

GoogleAnalytics accepts gaId, optional dataLayerName (defaulting to 'dataLayer'), a boolean debugMode, and a nonce. It renders two Script elements: an inline initialization script (_next-ga-init) that establishes the global array queue and defines the gtag() helper function, followed by the remote script (_next-ga) loading https://www.googletagmanager.com/gtag/js?id=${gaId}.

export function GoogleAnalytics(props: GAParams) {
  const { gaId, debugMode, dataLayerName = 'dataLayer', nonce } = props
 
  if (currDataLayerName === undefined) {
    currDataLayerName = dataLayerName
  }
 
  useEffect(() => {
    // performance.mark is being used as a feature use signal. While it is traditionally used for performance
    // benchmarking it is low overhead and thus considered safe to use in production and it is a widely available
    // existing API.
    // The performance measurement will be handled by Chrome Aurora
 
    performance.mark('mark_feature_usage', {
      detail: {
        feature: 'next-third-parties-ga',
      },
    })
  }, [])
 
  return (
    <>
      <Script
        id="_next-ga-init"
        dangerouslySetInnerHTML={{
          __html: `
          window['${dataLayerName}'] = window['${dataLayerName}'] || [];
          function gtag(){window['${dataLayerName}'].push(arguments);}
          gtag('js', new Date());
 
          gtag('config', '${gaId}' ${debugMode ? ",{ 'debug_mode': true }" : ''});`,
        }}
        nonce={nonce}
      />
      <Script
        id="_next-ga"
        src={`https://www.googletagmanager.com/gtag/js?id=${gaId}`}
        nonce={nonce}
      />
    </>
  )

Sources: packages/third-parties/src/google/ga.tsx:16-56

GoogleTagManager constructs a dynamic script URL defaulting to https://www.googletagmanager.com/gtm.js, appending URL query parameters based on its props.

Prop NameQuery ParameterCondition / Behavior
gtmIdidAppended when gtmId is provided
dataLayerNamelAppended when dataLayerName is not equal to 'dataLayer'
authgtm_authAppended when auth string is provided
previewgtm_preview, gtm_cookies_winAppended when preview string is provided (sets gtm_cookies_win to 'x')

Sources: packages/third-parties/src/google/gtm.tsx:10-39

Note

The GoogleTagManager initialization inline script creates an IIFE that safely instantiates the data layer array, pushes a gtm.start timestamp event, and supports serializing an optional initial dataLayer object payload if passed via props. Sources: packages/third-parties/src/google/gtm.tsx:55-66

Event Dispatching and Window DataLayer Management

Both integrations expose helper functions to dispatch tracking events into the global dataLayer queue: sendGAEvent and sendGTMEvent.

sendGAEvent verifies whether currDataLayerName has been initialized. If undefined, it emits a console warning. Otherwise, it pushes arguments onto window[currDataLayerName], warning if the data layer array does not exist on window.

export function sendGAEvent(..._args: Object[]) {
  if (currDataLayerName === undefined) {
    console.warn(`@next/third-parties: GA has not been initialized`)
    return
  }
 
  if (window[currDataLayerName]) {
    window[currDataLayerName].push(arguments)
  } else {
    console.warn(
      `@next/third-parties: GA dataLayer ${currDataLayerName} does not exist`
    )
  }
}

Sources: packages/third-parties/src/google/ga.tsx:59-72

sendGTMEvent accepts event data and an optional custom dataLayerName, falling back to currDataLayerName. It defensively initializes window[dataLayer] as an empty array if absent, allowing event queueing prior to full GTM script initialization.

export const sendGTMEvent = (data: Object, dataLayerName?: string) => {
  // special case if we are sending events before GTM init and we have custom dataLayerName
  const dataLayer = dataLayerName || currDataLayerName
  // define dataLayer so we can still queue up events before GTM init
  window[dataLayer] = window[dataLayer] || []
  window[dataLayer].push(data)
}

Sources: packages/third-parties/src/google/gtm.tsx:77-83

Warning

Using standard inline script tags (<script src="www.googletagmanager.com/gtag/js"> or www.google-analytics.com/analytics.js) directly in React code will trigger the ESLint rule next-script-for-ga, which requires replacing them with @next/third-parties/google components. Sources: packages/eslint-plugin-next/src/rules/next-script-for-ga.ts:10-54

Shared script embed wrapper

Core Component Structure and Type Definitions

ThirdPartyScriptEmbed is a client-side React component marked with the 'use client' directive, serving as the foundational container wrapper for embedded third-party widgets and scripts across @next/third-parties. The component accepts a strongly typed ScriptEmbed configuration object defining properties for raw HTML injection, sizing dimensions, element children, and telemetry feature tracking.

export type ScriptEmbed = {
  html?: string | null
  height?: string | number | null
  width?: string | number | null
  children?: React.ReactElement | React.ReactElement[]
  dataNtpc?: string
}

Sources: packages/third-parties/src/ThirdPartyScriptEmbed.tsx:1-11

The following table details the configuration parameters accepted by ThirdPartyScriptEmbed:

PropertyTypeDefault ValueDescription
htmlstring | nullundefinedRaw HTML string to be injected via dangerouslySetInnerHTML.
heightstring | number | nullnullContainer height; appends px if numeric, or evaluates to auto if null.
widthstring | number | nullnullContainer width; appends px if numeric, or evaluates to auto if null.
childrenReact.ReactElement | React.ReactElement[]undefinedReact child elements rendered alongside the container.
dataNtpcstring''Identifier string appended to feature telemetry and data attributes.

Sources: packages/third-parties/src/ThirdPartyScriptEmbed.tsx:5-19

Telemetry Signal Emission and DOM Rendering

Upon mounting, ThirdPartyScriptEmbed executes an useEffect hook that inspects the dataNtpc prop. If dataNtpc is truthy, the component invokes performance.mark() with the feature usage signal name formatted as next-third-parties-${dataNtpc}. This low-overhead browser performance API is leveraged as a safe production telemetry signal to track feature adoption.

useEffect(() => {
  if (dataNtpc) {
    // performance.mark is being used as a feature use signal. While it is traditionally used for performance
    // benchmarking it is low overhead and thus considered safe to use in production and it is a widely available
    // existing API.
    performance.mark('mark_feature_usage', {
      detail: {
        feature: `next-third-parties-${dataNtpc}`,
      },
    })
  }
}, [dataNtpc])

Sources: packages/third-parties/src/ThirdPartyScriptEmbed.tsx:20-31

Note

performance.mark is deliberately utilized for feature usage signaling due to its negligible execution overhead, making it safe for production monitoring without triggering custom analytics overhead. Sources: packages/third-parties/src/ThirdPartyScriptEmbed.tsx:21-24

The component returns a React fragment containing any provided children elements. If the html prop is present, it renders a wrapper div element configured with dynamic inline dimensions and a data-ntpc attribute matching the dataNtpc prop.

return (
  <>
    {/* insert script children */}
    {children}
    {/* insert html */}
    {html ? (
      <div
        style={{
          height: height != null ? `${height}px` : 'auto',
          width: width != null ? `${width}px` : 'auto',
        }}
        data-ntpc={dataNtpc}
        dangerouslySetInnerHTML={{ __html: html }}
      />
    ) : null}
  </>
)

Sources: packages/third-parties/src/ThirdPartyScriptEmbed.tsx:33-49

YouTube and Google Maps embeds

YouTube Embed Mechanics and Strategy

The YouTubeEmbed component relies on third-party-capital via TPCYouTubeEmbed to resolve raw markup, external stylesheets, and script configurations for video embeds based on standard parameters such as videoid, dimensions, and custom player options.

export type YouTubeEmbed = {
  height?: number
  width?: number
  videoid: string
  playlabel?: string
  params?: string
  style?: string
}

Sources: packages/third-parties/src/types/google.ts:59-65

YouTubeEmbed delegates its wrapper rendering to ThirdPartyScriptEmbed, stamping the container with a dataNtpc value of "YouTubeEmbed". The returned element maps over any required external scripts provided by third-party-capital, instantiating Next.js Script components with execution strategies mapped explicitly through scriptStrategy.

const scriptStrategy = {
  server: 'beforeInteractive',
  client: 'afterInteractive',
  idle: 'lazyOnload',
  worker: 'worker',
}

Sources: packages/third-parties/src/google/youtube-embed.tsx:9-14

export default function YouTubeEmbed(props: YouTubeEmbedTypes) {
  const { html, scripts, stylesheets } = TPCYouTubeEmbed(props)
 
  return (
    <ThirdPartyScriptEmbed
      height={props.height || null}
      width={props.width || null}
      html={html}
      dataNtpc="YouTubeEmbed"
    >
      {scripts?.map((script) => (
        <Script
          key={script.url}
          src={script.url}
          strategy={scriptStrategy[script.strategy] as ScriptProps['strategy']}
          stylesheets={stylesheets}
        />
      ))}
    </ThirdPartyScriptEmbed>
  )

Sources: packages/third-parties/src/google/youtube-embed.tsx:16-35

Note

The scriptStrategy lookup maps third-party-capital script descriptor strings directly to Next.js Script component loading strategies, routing idle descriptors to lazyOnload and server descriptors to beforeInteractive. Sources: packages/third-parties/src/google/youtube-embed.tsx:9-14

Google Maps Embed Integration

The GoogleMapsEmbed component accepts location and display configuration properties, extracting the apiKey property and formatting it as a standard key parameter before passing the object to TPCGoogleMapEmbed.

export type GoogleMapsEmbed = {
  height?: number | string
  width?: number | string
  mode: 'place' | 'view' | 'directions' | 'streetview' | 'search'
  apiKey: string
  style?: string
  allowfullscreen?: boolean
  loading?: 'eager' | 'lazy'
  q?: string
  id?: string
  center?: string
  zoom?: string
  maptype?: string
  language?: string
  region?: string
}

Sources: packages/third-parties/src/types/google.ts:42-57

export default function GoogleMapsEmbed(props: GoogleMapsEmbedTypes) {
  const { apiKey, ...restProps } = props
  const formattedProps = { ...restProps, key: apiKey }
  const { html } = TPCGoogleMapEmbed(formattedProps)
 
  return (
    <ThirdPartyScriptEmbed
      height={formattedProps.height || null}
      width={formattedProps.width || null}
      html={html}
      dataNtpc="GoogleMapsEmbed"
    ></ThirdPartyScriptEmbed>
  )

Sources: packages/third-parties/src/google/google-maps-embed.tsx:6-18

Warning

Omitting the apiKey property or supplying an invalid mode will result in malformed output generation from the underlying third-party-capital mapper function. Sources: packages/third-parties/src/google/google-maps-embed.tsx:7-9

Was this page helpful?