To add live chat to a Next.js or React site, render one chat component once, high in the component tree, so it loads in the browser after the page and stays mounted while visitors move between routes. Done that way, the widget never breaks server-side rendering, never loads twice and does not reset on client-side navigation. This guide shows how to do it with Talkmio’s React component for the Next.js App Router, the Pages Router, Vite, Remix and Gatsby, how to pass the logged-in user, and how to avoid the usual single-page-app pitfalls.
Why Single-Page Apps Need a Different Approach
On a classic website you paste a chat snippet before </body> and every page load runs it. React apps behave differently:
- Server-side rendering. In Next.js, components can render on the server, where there is no
windowordocument. A widget script that touches the browser during server rendering throws errors or causes hydration warnings. - Client-side navigation. Moving between routes does not reload the page. A script injected inside one page component can be removed, re-added or duplicated as the visitor navigates.
- Strict Mode in development. React deliberately mounts components twice in development to expose side-effect bugs, which can create two widgets if loading is not guarded.
The fix is a component that loads the widget once, only in the browser, and lives in the root layout. That is what Talkmio’s component does: it is SSR-safe, adds the widget script asynchronously only after the component mounts, skips loading if the script is already on the page, and identifies the current user.
Before You Start
- Create a Talkmio account at app.talkmio.com. The free plan needs no card.
- Add your website under Websites and copy the 12-character site key.
- Download the React package from the React / Next.js integration page:
talkmio-react-1.0.0.zip.
You can either copy src/index.js from the zip into your project, or unpack it and install it as a local package with npm install ./talkmio-react; the package is named @talkmio/react. The component is <TalkmioChat siteKey="…" />.
How to Add Live Chat to a Next.js App Router Project
The App Router renders components on the server by default. Browser-only code belongs in a client component, marked with the 'use client' directive. Create a small wrapper and include it from the root layout.
Step 1: Create a client wrapper
Create app/chat.js (or components/Chat.js):
'use client';
import TalkmioChat from '@talkmio/react';
export default function Chat() {
return <TalkmioChat siteKey="YOUR_SITE_KEY" />;
}
If you copied src/index.js into your project instead of installing the package, import it from that path.
Step 2: Render it once in the root layout
In app/layout.js, add the wrapper inside <body>:
import Chat from './chat';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Chat />
</body>
</html>
);
}
The root layout stays mounted across route changes, so the widget loads once and keeps its state while visitors navigate. Do not put the component inside individual pages.
Step 3: Keep the site key in configuration
The site key is public by design, since it appears in the page, but keeping it in an environment variable makes it easy to use different websites for staging and production. In Next.js, browser-visible variables need the NEXT_PUBLIC_ prefix, for example NEXT_PUBLIC_TALKMIO_SITE_KEY, read as process.env.NEXT_PUBLIC_TALKMIO_SITE_KEY.
Pages Router, Vite, Remix and Gatsby
Next.js Pages Router
In projects that still use the pages/ directory, the equivalent of the root layout is the custom App component. Render the chat in pages/_app.js, next to your page component:
import TalkmioChat from '@talkmio/react';
export default function App({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
<TalkmioChat siteKey="YOUR_SITE_KEY" />
</>
);
}
Because _app.js wraps every page and persists across navigation, the widget loads once.
Vite, Remix and Gatsby
The same principle applies elsewhere: render the component once at the top of the tree.
- Vite with React. Render
<TalkmioChat />in your rootAppcomponent, outside the router’s route outlet, so it is not unmounted on route changes. - Remix. Add it to the root route (
app/root.jsx), inside the body, next to the outlet. - Gatsby. Put it in the layout component that wraps every page, or wrap the root element so it persists between pages.
If you use another React framework, look for the component that wraps every route and stays mounted. That is where the chat belongs.
Passing the Logged-In User
If your app has accounts, pass the current user so your team sees who is writing instead of an anonymous visitor:
<TalkmioChat siteKey="YOUR_SITE_KEY" user={{ name, email }} />
Take name and email from your auth state, for example a session hook. The component records the user before it loads the widget, so render it with the user details available. If visitors log in after the widget has already loaded, test that the new details appear in the Inbox; a page reload after login is the simplest guarantee. For anonymous visitors, simply leave user out.
Identifying users saves time in every conversation: no “what is your account e-mail?” before help starts, and Contacts keeps each person’s conversation history. Pass only what the chat needs. Our guide to live chat identity verification explains why a name in the chat is not proof of identity for account changes.
Component vs Plain Script: Which to Use
| Question | React component | Plain snippet or tag manager |
|---|---|---|
| Safe with server-side rendering | Yes, loads only in the browser | Depends on where you add it |
| Loads once across route changes | Yes, when placed in the root layout | Yes, if added to the document, not a page component |
| Passes the logged-in user | Yes, via the user prop |
No |
| Needs a code deploy to change | Yes | Not if managed in a tag manager |
| Works without a build step | No | Yes |
If your marketing team manages scripts in a tag manager, Talkmio also offers a Google Tag Manager container import, and our guide to adding live chat without a developer covers snippet installs. For apps with logged-in users, the component is usually the better choice.
TypeScript and Widget Settings
TypeScript projects
The package includes type definitions (src/index.d.ts), so in a TypeScript project the siteKey and user props are typed and your editor flags mistakes. Rename your wrapper to chat.tsx and import the component the same way.
Change the look without a deploy
Colour, position, assistant name, welcome and offline texts, pre-chat form, language and business hours are set in Talkmio under Websites, not in your code. Changes apply within five minutes, so designers and support managers can adjust the widget without asking developers for a release. Keep the React side minimal: site key and user, nothing else.
Staging and preview deployments
Preview deployments on platforms such as Vercel get their own URLs. Add a separate website in Talkmio for staging and use its key in preview environments, so test chats do not land in the production Inbox.
Performance and Core Web Vitals
A chat widget should never delay your first render. Loading it in the browser after the app has mounted keeps it off the critical path. After installing, compare your page with and without the component using Lighthouse or your real-user monitoring, and check Largest Contentful Paint and Interaction to Next Paint. If you build your own loader with plain script tags in Next.js, the Next.js Script component with a non-blocking strategy is the right tool. Our article on chat widget page speed explains how to measure the impact properly.
Testing Your Installation
- Run a production build (
next buildandnext start) as well as the dev server, because Strict Mode double-mounting only happens in development. - Open the site in a private window and check the bubble appears.
- Navigate between several routes. The widget should stay put, with an open conversation intact.
- Check the console for hydration warnings or blocked scripts. A Content Security Policy may need the chat script’s domain allowed.
- Log in as a test user and start a chat; the Talkmio Inbox should show the name and e-mail.
- Ask real questions. Ask two or three questions customers often ask and check that answers come from your own pages. Use Try Mio in the app to see which knowledge pieces were used.
Troubleshooting
Two chat bubbles appear
The component is rendered in more than one place, or a snippet is also loaded elsewhere, such as in a tag manager. Keep one. In development, also confirm it only happens with Strict Mode; the production build is what visitors see.
“window is not defined” during build
Your own wrapper code touches browser APIs at render time on the server. Keep the 'use client' directive in the wrapper and avoid reading window at module level.
The widget disappears after navigation
It was placed inside a page or route component that unmounts. Move it to the root layout, _app.js or the root route.
Nothing appears in production
Check the environment variable is set in your hosting platform with the NEXT_PUBLIC_ prefix, and that the website you added in Talkmio matches the production domain.
Teaching the Assistant About Your Product
Once the widget runs, the answers matter more than the code. Talkmio’s assistant, Mio, answers only from your content: add your marketing site and docs under Mio AI → Knowledge base, upload files such as PDFs or Markdown, add Q&A pairs, and write business instructions about tone and limits. Mio reads public pages on the same domain, respecting robots.txt, so content that only appears inside your logged-in app is not visible to it; add that information as files or Q&A pairs if customers ask about it. Mio replies in the visitor’s language and hands the chat to your team when it cannot answer from your content, when the visitor asks for a person, complains or asks about an order. See our guide for live chat in SaaS companies for in-app support ideas.
Frequently Asked Questions
How do I add live chat to a Next.js site?
Create a client component marked with ‘use client’ that renders the chat component with your site key, then include it once in app/layout.js inside the body. With the Pages Router, render it in pages/_app.js instead. The widget then loads once in the browser and stays mounted while visitors move between routes.
Will a chat widget break server-side rendering in Next.js?
Not if it loads only in the browser. Talkmio’s React component is SSR-safe: it loads the widget once on the client and does not touch browser APIs during server rendering. Keep your own wrapper marked as a client component and avoid reading window at module level, which is the usual cause of build errors.
Why does the chat widget appear twice in development?
React Strict Mode mounts components twice in development to expose side effects, and some loaders then create two widgets. A guarded component loads only once. If you still see two, check that the component is not rendered in several places and that no snippet is also loaded through a tag manager. Always test a production build.
How can I show the logged-in user in chat?
Pass the user to the component, for example user with name and email taken from your authentication state. Your team then sees who is writing instead of an anonymous visitor, and Contacts keeps their conversation history. Leave the prop out for anonymous visitors, and never treat the name as proof of identity for account changes.
Does the React chat component work with Vite, Remix and Gatsby?
Yes. Talkmio’s component supports React with Vite, Next.js App and Pages Router, Remix and Gatsby. In each case, render it once in the component that wraps every route and stays mounted: the root App component, the root route or the shared layout. That keeps the widget loaded across client-side navigation.
Should I use the React component or a plain script snippet?
Use the component for React apps, especially those with logged-in users, because it is SSR-safe and can pass the current user. A plain snippet or tag manager suits marketing sites where non-developers manage scripts. Use only one method on the same site, or visitors may see two chat bubbles.
The Bottom Line
To add live chat to a Next.js or React site, render one SSR-safe component in the root layout, pass the logged-in user if you have one, and test a production build across several routes. That avoids duplicate widgets, hydration errors and resets on navigation. Then put your effort into the content the assistant answers from. Create a free Talkmio account, download the React component and add it to your layout today.
