React Router and Remix
Mount Docent in a React Router or Remix app with useReactRouterAdapter, built from the useNavigate and useLocation hooks you already have.
React Router 7 and Remix 2 share one adapter, because they share the hooks it is built from.
The mount
import { useLocation, useNavigate } from 'react-router'
import { Docent } from '@usedocent/react'
import { useReactRouterAdapter } from '@usedocent/react/react-router'
import { PRESENCE_LOADERS } from '@usedocent/mascot/library/loaders'
export const Guide = ({ userId, children }: { userId: string; children: React.ReactNode }) => {
const router = useReactRouterAdapter(useNavigate(), useLocation())
return (
<Docent
appId={import.meta.env.VITE_DOCENT_APP_ID}
publishableKey={import.meta.env.VITE_DOCENT_PUBLISHABLE_KEY}
apiUrl="https://api.usedocent.com"
user={{ id: userId }}
router={router}
loadPresence={(slug) => PRESENCE_LOADERS[slug]?.() ?? Promise.reject(new Error(slug))}
loadAudio={(vendor) =>
vendor === 'openai' ? import('@usedocent/sdk-audio-live') : import('@usedocent/sdk-audio')
}
voice="tap"
>
{children}
</Docent>
)
}Render it inside the router, in a component the router renders, so the two hooks have a router above them. A root layout route is the usual place.
The adapter
useReactRouterAdapter(
navigate: (path: string) => void,
location: { pathname: string; search?: string },
): RouterAdapterIt takes the hook results rather than importing react-router, so it adds no dependency and works
with whichever version you are on. The current path it reports is pathname plus search, which
is what Docent's route matching expects.
Call it on every render with the live useLocation() result, as above. The adapter keeps the
latest one in a ref and returns a stable object, so it neither goes stale nor remounts the guide.
Remix
Remix 2 re-exports the same hooks from @remix-run/react, so only the import line changes:
import { useLocation, useNavigate } from '@remix-run/react'Everything else is identical. The component is browser-only, so mount it in a route component rather than anywhere that runs during the server render of a document.
Without the adapter
router is optional. Omitted, the SDK navigates with history.pushState and dispatches a
popstate event. Pass the adapter whenever you have a router anyway: going through the router's
own navigate is what keeps its loaders, transitions and scroll restoration in the loop. See
Vite with React for what the fallback is for.
Next.js
Mount Docent in a Next.js App Router app, with the client boundary, useNextAdapter, the environment variables, and where the component belongs.
Vite with React
Mount Docent in a Vite and React app with no router. The SDK falls back to pushState, and this page says what that means for navigation.
