---
title: "Third‑Party Scripts"
description: "Use the Next.js third-party components and `next/script` loading strategies, then measure their effect on Total Blocking Time and field INP."
canonical_url: "https://vercel.com/academy/nextjs-foundations/third-party-scripts"
md_url: "https://vercel.com/academy/nextjs-foundations/third-party-scripts.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-08-21T03:37:31.944Z"
content_type: "lesson"
course: "nextjs-foundations"
course_title: "Next.js Foundations"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Third‑Party Scripts

Third-party code can delay rendering, occupy the main thread, and shift layout. Treat every analytics tag, widget, and embed as a performance cost that must earn its place.

\*\*Note: Working Directory\*\*

This lesson uses `apps/web`. All file paths are relative to that directory.

## Outcome

Google Analytics loaded with Next.js's maintained integration, optional scripts deferred with an appropriate strategy, and a before/after audit based on measured lab and field data.

## Fast Track

1. Use `@next/third-parties/google` for Google products.
2. Use `next/script` for other providers and choose the least eager strategy that still meets the feature requirement.
3. Compare Lighthouse **Total Blocking Time (TBT)** before and after the change; use real-user **Interaction to Next Paint (INP)** to judge production responsiveness.

## Prefer Maintained Integrations

Install Next.js's third-party component package:

```bash
pnpm add @next/third-parties@latest
```

Then add Google Analytics to the root layout. The component loads the Google script after hydration and supports client-side navigation tracking when Enhanced Measurement is configured in Google Analytics.

```tsx title="apps/web/src/app/layout.tsx"
import { GoogleAnalytics } from '@next/third-parties/google'

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en">
      <body>{children}</body>
      <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID!} />
    </html>
  )
}
```

Only expose the measurement ID through `NEXT_PUBLIC_GA_ID`; it is an identifier, not a secret. Keep API secrets and credentials in server-only environment variables.

To send an event from a Client Component:

```tsx title="apps/web/src/components/signup-button.tsx"
'use client'

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

export function SignupButton() {
  return (
    <button
      type="button"
      onClick={() => sendGAEvent('event', 'signup_started', { source: 'header' })}
    >
      Start free
    </button>
  )
}
```

## Loading Other Providers with `next/script`

Use `next/script` when Next.js does not provide a maintained component for the provider.

| Strategy            | When it loads            | Appropriate use                                                                  |
| ------------------- | ------------------------ | -------------------------------------------------------------------------------- |
| `beforeInteractive` | Before Next.js hydration | A rare, site-wide script whose documented requirement is to run before hydration |
| `afterInteractive`  | After some hydration     | Providers needed soon after the page becomes interactive                         |
| `lazyOnload`        | During browser idle time | Chat, social widgets, and other optional features                                |
| `worker`            | In a worker              | Experimental, Pages Router-only cases after compatibility testing                |

\*\*Warning: Avoid Remote Polyfill Services\*\*

Do not load Polyfill.io or another mutable remote polyfill bundle with `beforeInteractive`. The former Polyfill.io service was used in a supply-chain attack, and a script that runs before hydration has exceptional access and performance impact. Prefer the browsers supported by Next.js, a local audited dependency, or a narrowly scoped feature fallback that you ship with the application.

Most optional widgets should wait until idle:

```tsx title="apps/web/src/app/support/page.tsx"
import Script from 'next/script'

export default function SupportPage() {
  return (
    <>
      <main>
        <h1>Support</h1>
      </main>
      <Script
        src="https://widget.example.com/chat.js"
        strategy="lazyOnload"
      />
    </>
  )
}
```

Do not classify a script as critical from its product category alone. Confirm the provider's integration requirements, its need for DOM access, and whether the user needs it during the initial interaction.

## Measure Instead of Estimating

Lighthouse reports TBT as a lab proxy for main-thread responsiveness. INP is a field Core Web Vital based on real user interactions. They answer related but different questions:

- Use **TBT** for repeatable local comparisons while changing script loading.
- Use **INP** from production field data to determine whether real interactions are responsive.
- Also inspect the Network and Performance panels for transfer size, long tasks, request chains, and layout shifts.

Keep the test conditions identical. Run multiple samples because a single Lighthouse result is noisy.

| Run       | Analytics           | Optional widget     |           TBT | Notes                            |
| --------- | ------------------- | ------------------- | ------------: | -------------------------------- |
| Baseline  | Current integration | Current integration | Record result | Same device and throttling       |
| Isolation | Blocked in DevTools | Blocked in DevTools | Record result | Estimates total third-party cost |
| Candidate | Next.js integration | `lazyOnload`        | Record result | Compare with the baseline        |

Do not write a fixed savings claim before collecting these results. A provider's cost varies with its configuration, network, device, consent state, and the other work on the page.

## Hands-On Exercise 4.3

Audit the scripts in `apps/web/src/app/layout.tsx` and replace the hand-rolled Google Analytics tags.

**Requirements:**

1. Install `@next/third-parties`.
2. Remove the raw Google Analytics `<script>` tags and inline `gtag` bootstrap.
3. Add `<GoogleAnalytics gaId={...} />` to the root layout.
4. Classify every remaining third-party script by user need and loading requirement.
5. Capture at least three Lighthouse runs before and after the change and compare the median TBT.
6. Record where production INP will be monitored after deployment.

## Try It

1. Open Chrome DevTools and record a Performance trace during page load.
2. Identify long tasks attributed to third-party origins.
3. Run Lighthouse at least three times under the same throttling profile and record the median TBT.
4. Block one third-party origin in DevTools, repeat the test, and use the difference as evidence for keeping, removing, or deferring that provider.
5. Confirm analytics events arrive without double-counting page views.

## Done-When

- [ ] The layout uses `GoogleAnalytics` from `@next/third-parties/google`; no hand-rolled GA bootstrap remains.
- [ ] No page loads a mutable remote polyfill service.
- [ ] Each remaining external script has a documented owner, purpose, and loading strategy.
- [ ] The before/after table contains measured median TBT from at least three comparable runs.
- [ ] The deployment plan names the field-data source used to monitor INP.
- [ ] Analytics events and client-side page views are verified without duplicates.

## Solution

Google Analytics integration

```tsx title="apps/web/src/app/layout.tsx"
import type { Metadata } from 'next'
import { GoogleAnalytics } from '@next/third-parties/google'

import './globals.css'

export const metadata: Metadata = {
  title: process.env.NEXT_PUBLIC_APP_NAME ?? 'Vercel Academy Foundation - Web',
  description: 'VAF Web',
}

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en">
      <body className="container mx-auto px-4 py-8">{children}</body>
      {process.env.NEXT_PUBLIC_GA_ID ? (
        <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID} />
      ) : null}
    </html>
  )
}
```

This solution delegates Google Analytics loading to the maintained Next.js component. It does not need a Client Component solely for an `onLoad` callback; validate the integration in the browser's Network panel and in Google Analytics DebugView instead.

## References

- [Next.js third-party libraries guide](https://nextjs.org/docs/app/guides/third-party-libraries)
- [Next.js Script component](https://nextjs.org/docs/app/api-reference/components/script)
- [Next.js analytics guide](https://nextjs.org/docs/app/guides/analytics)
- [web.dev: Total Blocking Time](https://web.dev/articles/tbt)
- [web.dev: Interaction to Next Paint](https://web.dev/articles/inp)


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
