Add Bily to the root route and track every later framework navigation once.
Mount one Bily component in the existing root route. This supports React Router framework mode and Remix without replacing the document shell, providers, error boundaries, or route outlet.Do not add a raw Bily script tag. init() loads the script for you.
Choose the import that matches the framework. Both versions follow the same route contract.
React Router framework mode
Remix
app/bily-tracking.tsx
import { useEffect, useRef } from "react";import { useLocation } from "react-router";import { init, track } from "@bilyai/js";const BILY_SCRIPT_URL = import.meta.env.VITE_BILY_SCRIPT_URL;export function BilyTracking() { const location = useLocation(); const previousUrl = useRef<string | null>(null); useEffect(() => { if (!BILY_SCRIPT_URL) { throw new Error("VITE_BILY_SCRIPT_URL is required"); } init({ scriptUrl: BILY_SCRIPT_URL }); }, []); useEffect(() => { const currentUrl = window.location.href; if (previousUrl.current === null) { previousUrl.current = currentUrl; return; } if (currentUrl === previousUrl.current) return; previousUrl.current = currentUrl; track("PageView", { sourceUrl: currentUrl, pageTitle: document.title, }); }, [location.pathname, location.search, location.hash]); return null;}
app/bily-tracking.tsx
import { useEffect, useRef } from "react";import { useLocation } from "@remix-run/react";import { init, track } from "@bilyai/js";const BILY_SCRIPT_URL = import.meta.env.VITE_BILY_SCRIPT_URL;export function BilyTracking() { const location = useLocation(); const previousUrl = useRef<string | null>(null); useEffect(() => { if (!BILY_SCRIPT_URL) { throw new Error("VITE_BILY_SCRIPT_URL is required"); } init({ scriptUrl: BILY_SCRIPT_URL }); }, []); useEffect(() => { const currentUrl = window.location.href; if (previousUrl.current === null) { previousUrl.current = currentUrl; return; } if (currentUrl === previousUrl.current) return; previousUrl.current = currentUrl; track("PageView", { sourceUrl: currentUrl, pageTitle: document.title, }); }, [location.pathname, location.search, location.hash]); return null;}
The initial effect starts the SDK. The browser script records the initial page automatically. The second effect remembers that URL and sends only later location changes.Repeated init() calls with the same URL are safe, and the URL guard keeps development checks from duplicating route events.
Add <BilyTracking /> inside the default root component, where useLocation() already has router context.
React Router framework mode
Remix
app/root.tsx
import { Outlet } from "react-router";import { BilyTracking } from "./bily-tracking";export default function App() { return ( <> <BilyTracking /> <Outlet /> </> );}
app/root.tsx
import { Outlet } from "@remix-run/react";import { BilyTracking } from "./bily-tracking";export default function App() { return ( <> <BilyTracking /> <Outlet /> </> );}
These root examples show only the mount point. Keep your existing Layout, document elements, metadata, links, scripts, providers, hydration support, error boundaries, and other root-route behavior.