Type something to search...
Next.js Optimizing third-party libraries

Next.js Optimizing third-party libraries

This article covers a narrower, more specific tool than it might sound like at first glance. If you've read the "How to incorporate third-party libraries in Next.js" article elsewhere on this blog, that one is about the general problem — how to safely integrate any arbitrary third-party package into the App Router's server/client rendering model, using dynamic(), useEffect, and the rest. This article is about something narrower and more specific: @next/third-parties, an official package purpose-built for a handful of specific, extremely common third-party integrations — Google Tag Manager, Google Analytics, Google Maps, YouTube — pre-optimized so you don't have to hand-roll the performance work yourself.

Why these specific integrations get their own package

Google Tag Manager, Analytics, Maps, and YouTube embeds are collectively responsible for a genuinely large share of real-world third-party script bloat across the web — they're extremely common, and naively embedded (a raw <script> tag, or an unoptimized <iframe>), each one can meaningfully drag down a page's loading performance and Core Web Vitals. @next/third-parties exists because these specific integrations are common enough, and their performance pitfalls well-understood enough, that it made sense for the Next.js team to solve them once, centrally, rather than leaving every team to rediscover the same optimizations independently.

Installing it

npm install @next/third-parties@latest next@latest

Worth flagging directly: this is explicitly labeled an experimental package, still under active development as more third-party integrations get added over time. The recommendation is to install with the latest or canary tag specifically while that expansion is ongoing, rather than pinning to an older stable-looking version that might be missing integrations added since.

Google Tag Manager

GoogleTagManager instantiates a GTM container on your page, fetching the underlying script after hydration by default rather than blocking on it upfront:

// app/layout.tsx — loads for every route
import { GoogleTagManager } from "@next/third-parties/google";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <GoogleTagManager gtmId="GTM-XYZ" />
      <body>{children}</body>
    </html>
  );
}

Placed in the root layout, it loads for every route; placed instead in a single page file, it loads only there. This mirrors the layout-vs-root-layout scoping decision covered in this series' Script-loading article — the same "scope to what actually needs it" principle applies here too, not just to raw next/script usage.

Once the component is present anywhere in the tree (a parent layout, a page, or the same file), sendGTMEvent lets you push custom events into the dataLayer directly from your own code:

"use client";

import { sendGTMEvent } from "@next/third-parties/google";

export function EventButton() {
  return (
    <button
      onClick={() => sendGTMEvent({ event: "buttonClicked", value: "xyz" })}
    >
      Send Event
    </button>
  );
}

If you're running a server-side tag manager, serving your own gtm.js from your own tagging server rather than Google's default endpoint, gtmScriptUrl points the component at that custom URL instead of the default. Worth knowing as a genuinely easy-to-miss detail: gtmId becomes optional specifically when gtmScriptUrl is provided, to support the Google tag gateway for advertisers setup pattern — if you're wondering why a working GTM setup in your codebase doesn't seem to specify a gtmId anywhere, this is very likely why.

Google Analytics

GoogleAnalytics wires up GA4 via gtag.js, following the identical loading pattern — deferred until after hydration by default:

// app/layout.tsx
import { GoogleAnalytics } from "@next/third-parties/google";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
      <GoogleAnalytics gaId="G-XYZ" />
    </html>
  );
}

One genuinely important piece of guidance worth taking seriously rather than skimming past: if Google Tag Manager is already present in your app, configure Analytics through GTM directly rather than adding this separate GoogleAnalytics component alongside it. Running both independently is a common, easy-to-make mistake, and it has a real, specific consequence — duplicate pageview and event data in your Analytics reports, silently inflating every number you look at until someone notices the discrepancy and traces it back to two separate tracking mechanisms firing for the same events.

sendGAEvent, once the component is present somewhere in the tree, works the same way as its GTM counterpart:

"use client";

import { sendGAEvent } from "@next/third-parties/google";

export function EventButton() {
  return (
    <button
      onClick={() => sendGAEvent("event", "buttonClicked", { value: "xyz" })}
    >
      Send Event
    </button>
  );
}

Pageview tracking is automatic — GA4 tracks pageviews whenever the browser's history state changes, which covers ordinary Next.js client-side navigations with zero extra configuration on your part. The one thing worth actually verifying rather than assuming: confirm "Enhanced Measurement" is enabled in your GA4 Admin panel, with "Page changes based on browser history events" specifically checked — without that setting, client-side route changes in your App Router app may simply not register as pageviews at all, silently undercounting your traffic in a way that's easy to miss until you notice your numbers look implausibly low.

If you do decide to send pageviews manually instead of relying on the automatic behavior, disable the default automatic measurement first — running both simultaneously produces the identical duplicate-data problem described above for the GTM/Analytics overlap, just triggered by a different cause.

Google Maps Embed

GoogleMapsEmbed wraps Google's Maps Embed API, lazy-loading by default so an embed further down the page doesn't cost anything until it's actually near the viewport:

import { GoogleMapsEmbed } from "@next/third-parties/google";

export default function Page() {
  return (
    <GoogleMapsEmbed
      apiKey="XYZ"
      height={200}
      width="100%"
      mode="place"
      q="Brooklyn+Bridge,New+York,NY"
    />
  );
}

The loading prop's default of lazy is exactly the right choice for the overwhelmingly common case — a map embedded somewhere in page content, below the immediately visible fold. Explicitly override it only when you know, specifically, that this particular embed sits above the fold and genuinely needs to be visible immediately on load; leaving the default in that specific case would delay a map your users see the instant the page renders.

YouTube Embed

YouTubeEmbed is a thin, optimized wrapper around lite-youtube-embed under the hood — a library purpose-built to avoid the substantial loading cost of YouTube's actual, full embed iframe until the moment a user actually clicks play:

import { YouTubeEmbed } from "@next/third-parties/google";

export default function Page() {
  return (
    <YouTubeEmbed videoid="ogfYd705cRs" height={400} params="controls=0" />
  );
}

The performance difference this makes is genuinely worth understanding, not just accepting as marketing copy: a naive, direct YouTube iframe embed loads a meaningful chunk of YouTube's own player JavaScript immediately, on page load, regardless of whether the visitor ever actually plays the video at all. lite-youtube-embed's whole premise is deferring that real cost until an actual click — before that, it's essentially just a lightweight thumbnail image with a play button overlaid on top, at a small fraction of the loading cost of the genuine embed.

The params prop passes through YouTube's own documented player parameters as a query string — controls=0, start=10&end=30, and the rest of the standard YouTube player parameter set all work exactly as YouTube's own API documentation describes them, since they're passed through verbatim rather than reimplemented by this wrapper.

When to reach for this package versus the general integration patterns

A simple, genuinely useful rule: if the specific library you need is Google Tag Manager, Google Analytics, Google Maps, or YouTube — check @next/third-parties first, since someone has already done the optimization work for exactly this integration and you'd otherwise be re-deriving it yourself. For literally anything else — a charting library, a payment SDK, a chat widget, an arbitrary npm package with no dedicated Next.js integration — that's precisely the territory the general "incorporating third-party libraries" article covers: dynamic() for client-only libraries, next/script with a deliberately chosen strategy for external script tags, server-only for anything that must never reach the client bundle.

Given this package is explicitly experimental and still actively growing its integration list, it's worth periodically checking whether a library you're currently hand-optimizing yourself has since gained official support here — the roster of covered integrations is a moving target, not a fixed, final list.

Key Takeaways

IntegrationComponentDefault loading behavior
Google Tag ManagerGoogleTagManagerDeferred until after hydration
Google AnalyticsGoogleAnalyticsDeferred until after hydration; pageviews tracked automatically
Google MapsGoogleMapsEmbedLazy-loaded (override only if genuinely above the fold)
YouTubeYouTubeEmbedLightweight thumbnail until clicked, via lite-youtube-embed
SituationReach for
GTM, GA, Maps, or YouTube specifically@next/third-parties
Any other third-party libraryThe general integration patterns (dynamic(), next/script, server-only)
Both GTM and GA installed independentlyStop — configure Analytics through GTM instead, to avoid duplicate data

@next/third-parties is a narrow tool solving a specific, high-frequency problem — it's not a general-purpose answer to "how do I add any third-party library to Next.js," and it isn't trying to be. Used for exactly the four integrations it covers, it saves you from re-deriving loading-strategy decisions the Next.js team has already made carefully on your behalf; reached for outside that scope, it simply doesn't apply, and the general integration patterns are still where that broader work happens.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper