Add a Voice AI Agent to React Router (root.tsx Guide)
Add the AnveVoice voice agent to React Router v7: root.tsx Layout in framework mode, or index.html in declarative and data mode, with SPA-mode notes.
How to add AnveVoice to a React Router app
In React Router v7 framework mode, add the AnveVoice script to the Layout export in app/root.tsx. In declarative or data mode, edit the Vite index.html instead. The route code never needs to touch it. Steps checked against the official documentation on 2026-10-14.
At a glance
- Framework mode: Layout export in app/root.tsx, after Scripts
- Declarative or data mode: index.html at the project root (Vite)
- SPA mode (ssr: false): Root route is pre-rendered into index.html at build time
- Coding needed: One script element
Before you start
Get your embed ID at app.anvevoice.app (Free plan, $0, no card). React Router v7 can run in three modes, according to its documentation: Declarative (BrowserRouter), Data (createBrowserRouter with RouterProvider) and Framework (a Vite plugin with route modules, plus SPA, SSR and static rendering). Where the tag goes depends on which one your project uses. Open the project and look for app/root.tsx and a react-router.config.ts file. If they exist you are in framework mode. If your entry is src/main.tsx rendering a router into an element with id root, you are in declarative or data mode.
Framework mode: the Layout in root.tsx
React Router's root route can export an optional Layout component that renders the document shell: html, head, body, Meta, Links, ScrollRestoration and Scripts. The docs explain it exists so the shell is not duplicated across the root component, HydrateFallback and ErrorBoundary, and so React does not remount shell elements when switching between them. A script placed in this shell is present on every page, including error pages. Put the AnveVoice element at the end of the body, after Scripts, so the framework's own hydration code gets priority.
- 1. Open app/root.tsx — This is the root route module that your whole app inherits from.
- 2. Add the script to Layout — Render a script element with the async attribute at the end of body, as shown. Replace YOUR_EMBED_ID. Code: // app/root.tsx import { Links, Meta, Outlet, Scripts, ScrollRestoration, } from "react-router"; export function Layout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta /> <Links /> </head> <body> {children} <ScrollRestoration /> <Scripts /> <script src="https://anvevoice.app/embed.js?embedId=YOUR_EMBED_ID" async /> </body> </html> ); } export default function App() { return <Outlet />; }
- 3. Restart the dev server — Run your dev command again and load a page; the button appears bottom-right.
React 19 and the async script
React 19 gives special treatment to script elements that have both src and async set: React may move them to the document head and de-duplicates identical ones. That suits a site-wide tag, and AnveVoice also ignores a second copy on the same page. React's docs note that adding onLoad or onError props disables that special treatment, so keep those off the element.
SPA mode (ssr: false)
If you set ssr to false in react-router.config.ts, React Router's SPA documentation says it still renders your root route at build time to produce index.html. Because Layout belongs to the root route, the AnveVoice tag is baked into that file. That also means code in Layout runs on the server during the build, so do not reference window there; a plain script element is safe.
Declarative and data mode: index.html
These modes mount a client-only React tree from a Vite index.html. A script element created by client-side React in older React versions does not execute, so do not put the tag inside a component. Add it to index.html instead, where the browser loads it directly.
- 1. Open index.html — It sits at the project root in a Vite project and contains the root div and the module script for main.tsx.
- 2. Add the tag before the closing body tag — Place the AnveVoice script after the module script. Code: <!-- index.html (project root, Vite) --> <!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>My App</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> <script src="https://anvevoice.app/embed.js?embedId=YOUR_EMBED_ID" async></script> </body> </html>
Check that it works
- 1. Run a production build — Run react-router build (framework mode) or vite build, then serve the output.
- 2. Inspect the HTML — View the source of a page and search for anvevoice.app/embed.js.
- 3. Test voice — Click the round microphone button, allow microphone access and ask a question. Navigate to another route and confirm the button stays.
Common problems
- Tag missing in framework mode: you added it to the default component rather than the Layout export, so error pages miss it, or you edited a different root file.
- Tag not executing in declarative mode: it was rendered by a component. Move it to index.html.
- Duplicate button: the tag is in both root.tsx and index.html. Keep one.
- Microphone blocked: serve the site over HTTPS and avoid a Permissions-Policy header that disables the microphone.
Sources
- React Router Docs: root.tsx (Layout export) (https://reactrouter.com/api/framework-conventions/root.tsx) — checked 2026-10-14
- React Router Docs: SPA mode (https://reactrouter.com/how-to/spa) — checked 2026-10-14
- React Router Docs: Picking a mode (https://reactrouter.com/start/modes) — checked 2026-10-14
- React Docs: script component (https://react.dev/reference/react-dom/components/script) — checked 2026-10-14
Related
- How to add AnveVoice to a React app
- How to add AnveVoice to a Next.js app
- How to add AnveVoice to a site hosted on Netlify
- How to add AnveVoice to a Vue app
Try AnveVoice on your site
Free plan: $0, 50,000 tokens a month, no card. Get your embed ID in the dashboard, then paste one tag.