How to Embed a Minyut Chatbot in Next.js (The Right Way)
How to embed an AI chatbot in Next.js App Router using the Script component — lazyOnload strategy, programmatic API, and why Shadow DOM keeps your styles safe.
Next.js handles a lot of third-party integrations cleanly — until you try to add a script that needs to load after the page and attach itself to the DOM. That's exactly the situation with an embeddable chatbot widget, and doing it with a raw <script> tag in your layout will either block page load, throw hydration errors, or simply not work.
This guide covers the right way to embed a Minyut chatbot in a Next.js App Router project: using the built-in Script component, understanding what happens at the server/client boundary, and optionally controlling the widget programmatically from your React components.
Why a raw script tag does not work in App Router
In Next.js App Router, your layout.tsx is a Server Component by default. Server Components render on the server and stream HTML to the browser — they do not execute JavaScript on the client. Placing a raw <script src=“...”> tag inside a Server Component works for simple static scripts, but gives you no control over when or how the script loads relative to the rest of the page.
The other common approach — injecting the script via useEffect after marking a component 'use client' — adds an extra render cycle, causes the widget to flash in after hydration, and bypasses Next.js's built-in script loading optimisations entirely.
The correct tool is next/script. The Script component works inside Server Components, deduplicates the script so it only loads once even across route transitions, and gives you a strategy prop that controls loading behaviour without any custom code.
Adding the widget to your layout
Open your root app/layout.tsx. Import Script from next/script and add it before the closing </body> tag:
// app/layout.tsx
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://cdn.minyut.com/widget.js"
data-chatbot-id="YOUR_CHATBOT_ID"
data-embed-token="YOUR_EMBED_TOKEN"
strategy="lazyOnload"
/>
</body>
</html>
);
}Replace YOUR_CHATBOT_ID and YOUR_EMBED_TOKEN with the values from your Minyut dashboard. Both are on the chatbot's Embed tab. They are safe to expose in client-side code — they grant read-only access to your chatbot config, nothing more.
“beforeInteractive” for scripts that must run before any JavaScript executes, like consent managers. The widget does not need that.Site-wide vs. single-page embedding
Placing the Script tag in app/layout.tsx adds the chatbot to every page in your app. That is usually what you want — visitors can ask questions from your pricing page, documentation, or homepage without any per-route configuration.
If you only want the chatbot on specific pages — say, your support or documentation section — add the Script tag in those pages directly instead of the layout:
// app/docs/page.tsx
import Script from "next/script";
export default function DocsPage() {
return (
<>
<main>{/* your content */}</main>
<Script
src="https://cdn.minyut.com/widget.js"
data-chatbot-id="YOUR_CHATBOT_ID"
data-embed-token="YOUR_EMBED_TOKEN"
strategy="lazyOnload"
/>
</>
);
}Next.js deduplicates Script tags with the same src, so if both a layout and a page include it, the script still loads only once.
Controlling the widget from React components
The Minyut widget exposes a window.__nestled__ object with three methods: open(), close(), and send(message). You can use these to trigger the chat panel from anywhere in your app — a “Get help” button in your nav, a “Questions?” link at the bottom of a pricing table, or a step in an onboarding flow.
Because window does not exist during server-side rendering, any call to the programmatic API must happen in a client component, inside an event handler:
"use client";
export function HelpButton() {
function handleClick() {
window.__nestled__?.open();
}
return (
<button onClick={handleClick} type="button">
Ask a question
</button>
);
}The optional chaining (?.) handles the case where the script has not loaded yet — the button does nothing rather than throwing. In practice, by the time a visitor clicks anything on the page, lazyOnload will have fired and __nestled__ will be available.
You can also pre-populate the chat with a message — useful when you know what the visitor is looking at:
window.__nestled__?.send("What's included in the Pro plan?");This opens the chat panel and sends the message as if the visitor typed it, triggering a real answer from your documents. It is useful for contextual help triggers — a “How does pricing work?” link next to a confusing line item, for example.
Why the widget will not break your styles
Developers sometimes worry about style bleed — a third-party widget overriding Tailwind classes, or site-level CSS resetting the widget's appearance. The Minyut widget runs inside a Shadow DOM, which creates a fully isolated CSS scope. Your styles cannot affect it; its styles cannot affect your layout.
This means no z-index juggling, no CSS resets, no wrapper <div>. The widget manages its own stacking context, appears at the bottom-right of the viewport by default, and goes full-screen on mobile. It does not interact with your layout's flex or grid context at all.
To change the button colour, label text, welcome message, or chat header — those settings live in your Minyut dashboard, not in CSS. The configuration reference lists every appearance option. If you want to override settings per-page using data-* attributes on the script tag, the widget docs cover the full attribute list and the order in which config sources are resolved.
If the widget does not appear
Run npm run dev and look for the chat button in the bottom-right corner. If it does not show up:
- Open DevTools → Network and filter for
widget.js. A 404 means the script URL is wrong. - Check the Console for errors mentioning
chatbot-idorembed-token. An invalid token produces a visible error inside the chat panel, not a silent failure. - Check that the chatbot is set to active in your Minyut dashboard — the widget renders nothing if the chatbot is paused.
- If you are running
next buildlocally and testing the production output, make sure you are not blocking external scripts in your Content Security Policy. The widget needs to load fromcdn.minyut.com.
This guide covers Next.js specifically. If you need the same walkthrough for plain HTML, WordPress, Webflow, or Shopify, the full installation guide covers all of them.
If you do not have a Minyut account yet, start free — one chatbot, three documents, 100 queries per month, no card needed. You can have a working chatbot embedded in your Next.js project in under 10 minutes.
Ready to build your own? Upload a doc, get a chatbot, paste one script tag.
Start free — no card needed