---
title: "Third-party scripts"
description: "Analytics, tag managers and widgets — and why there is no Script component."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.rsc-kit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Third-party scripts

Write the tag. React 19 does the rest, and it does the parts Next's `<Script>`
component existed for.

## An external script

```tsx title="src/app/layout.tsx"
export default function RootLayout({ children }) {
  return (
<html>
  <body>
    {children}
    <script async src="https://www.clarity.ms/tag/abc123" />
  </body>
</html>
  )
}
```

Rendered from a server component, React **hoists** it into `<head>` and
**deduplicates** it — render the same `src` in three components and one tag
goes out. `async` means it never blocks the page. That is `afterInteractive`,
without an import.

## An inline snippet

Most analytics ship one — a few lines that stub a global and load the real
thing:

```tsx title="src/app/layout.tsx"
<script
  id="ms-clarity"
  dangerouslySetInnerHTML={{
__html: `(function(c,l,a,r,i,t,y){c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
  t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
  y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "abc123");`,
  }}
/>
```

It renders where you wrote it and runs during parse, **before hydration**. For
a snippet whose job is to start recording as early as possible, that is the
earlier of the two moments and the one its authors intended. Next's
`Script` with `strategy="afterInteractive"` would have made it later.

:::note[Put it in the root layout]
The root layout renders once and is kept across navigations, so the snippet
runs once. In a *page*, a client-side navigation to it re-renders the tag, and
whether the browser runs a script inserted that way is not something to rely on
either way. Site-wide scripts go in the layout; that is where they belong
anyway.
:::

## Porting from Next

| Next's `Script strategy` | here |
| --- | --- |
| `beforeInteractive` | `<script src>` without `async` — the browser blocks on it, which is what that strategy meant |
| `afterInteractive` | `<script async src>`, or the inline snippet above |
| `lazyOnload` | the effect below, inside `requestIdleCallback` |
| `worker` | not supported — that was Partytown, a separate project |

The `id` prop carries across unchanged. `onLoad` and `onReady` are the one
thing that needs a component, because they need to run in the browser:

## When a script has to run after hydration

Rare, and specific: a script that touches DOM React rendered, and would find it
missing if it ran during parse. A ten-line client component covers it, and it is
yours rather than ours because there is nothing to abstract:

```tsx title="src/components/AfterHydration.tsx"
'use client'

import { useEffect } from 'react'

export function AfterHydration({ src, onLoad }: { src: string; onLoad?: () => void }) {
  useEffect(() => {
const tag = document.createElement('script')

tag.src = src
tag.async = true
if (onLoad) tag.onload = onLoad

document.head.append(tag)

return () => tag.remove()
  }, [src, onLoad])

  return null
}
```

Wrap the body in `requestIdleCallback` and it is `lazyOnload`.

## Why nothing ships for this

A `Script` component would be a wrapper around a `script` tag whose two
useful behaviours — hoisting and deduplication — React already provides. The
package's own tests pin that React does, so the day it stops, this page is
what changes rather than your app.

Source: https://docs.rsc-kit.dev/guides/third-party-scripts/index.mdx
