Tous les guides de plateforme

Add the selector to a Next.js site

FrameworkRoot layout or custom document

Ce guide est écrit à partir de la documentation propre de Next.js. Nous ne l'avons pas encore exécuté sur un vrai site, donc si une étape ne correspond pas à ce que vous voyez, dites-le nous et nous corrigerons le guide.

Written from how Next.js works rather than from an install we did ourselves. What we have confirmed on a real Next.js site is the part that matters: a server rendered tag boots correctly and the selector survives hydration.

Copy the snippet from Settings, then Selector, in your project. It carries your project id in data-ut-tenant, so paste it rather than retyping it.

Where the snippet goes

It belongs in the shared layout, never in an individual route. In a single route the selector appears on that one page and nowhere else.

App Router. File: app/layout.tsx

Render it as an ordinary element inside <body>:

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <script
          src="https://translate.ultimsuite.com/ut/v1.js"
          defer
          data-ut-tenant="YOUR-PROJECT"
        />
      </body>
    </html>
  );
}

Pages Router. File: pages/_document.tsx

next/document's <Head> is safe here: that document renders on the server and is never hydrated.

import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <script
          src="https://translate.ultimsuite.com/ut/v1.js"
          defer
          data-ut-tenant="YOUR-PROJECT"
        />
      </Head>
      <Main />
      <NextScript />
    </Html>
  );
}

Prefer a plain tag over next/script. Its default afterInteractive strategy injects the tag from the browser after hydration, so the selector appears later than it needs to and may never appear at all if hydration fails. If you do use next/script, keep data-ut-tenant on it: the script reads its own tag to know which project it belongs to.

The trap on Next.js

A menu that only exists once it is open. The common shape:

{open && <MobileNav />}

The whole menu, and anything you place inside it, is absent from the served HTML and enters the DOM only when a visitor opens it. A selector placed in there ships on zero pages. It looks correct when you inspect the DOM with the menu open, which is why it is easy to miss. We hit this on our own site.

Render the container always and hide it while closed:

<MobileNav open={open} />

display: none is fine, the selector still renders inside and appears when the menu opens. If you hide it with max height or opacity so it can animate, add inert and aria-hidden while closed, or every link inside stays in the tab order and in screen reader output while the menu looks shut.

To place the selector inside a menu, try the picker first (Settings, then Selector, and open your menu inside the picker before clicking the spot). If the position does not hold, use the placement element from the "Paste an element" method and put it at the exact spot in your nav component:

<div data-ut-selector="your-selector-id" />

A second, milder trap: do not wrap the tag in an explicit <head> element you write yourself. React can render it into the server HTML and then insert it again while hydrating, leaving two tags on the live page. Nothing breaks, the script starts once however many tags load it and mounts one selector per configured position, so it is untidy rather than harmful. The examples above avoid it.

How to know it worked

Open the deployed site and view the page source, then search for ut/v1.js. One match means it is live. Check the deployed site rather than your local one, since a missing selector is usually a tag that never made it into the deployed build.

The check on the Selector settings screen asks your site directly and answers plainly.

Because a hydration duplicate does not appear in view source, check the running page too. In the browser console:

document.querySelectorAll("script[src*='/ut/v1.js']").length

Expect 1. If you get 2, remove the explicit head wrapper and check again.