Skip to Content

Navigation & Routing

Route synchronisation runs in both directions, and a complete integration handles both:

  • iFrame → your page — the player navigates inside the iFrame, and your browser URL must follow, so the page survives a refresh and the back button works. The iFrame sends navigation.
  • your page → iFrame — you drive the iFrame to a section, either on initial load or from your own navigation UI. You send iframe-navigation.

If you use the integration module, most of this is already handled. Read Automatic handling first and only implement manually what you actually need.

Automatic handling with the integration module

init() configures routing, and mount() sends the initial iframe-navigation for you. The module also subscribes to navigation itself and calls history.replaceState(), honouring your prefix and blacklist.

The routing options of init():
integration.init({ languages: ["en", "de"], prefix: "sports", blacklistedRoutes: ["/mini-iframe"], });
OptionDefaultEffect
languages—Supported language codes. Used to strip the language segment from paths
prefix'sports'URL prefix for the sportsbook section on your site
blacklistedRoutes['/mini-iframe']Exact route paths to ignore when syncing. Not applied as published — see below
isNavigationCaptureDisabledfalseSet true to stop the module from touching your URL, and handle navigation yourself
Important

A blacklistedRoutes array you pass to init() is discarded in the published source, leaving /mini-iframe as the only excluded route. Matching is also exact rather than prefix-based. The fix, and why both happen, are in Integration Module → init().

Set isNavigationCaptureDisabled: true when your own router must own the URL. Then subscribe to navigation manually, as below.

iFrame → your page: navigation

Sent whenever the route changes inside the iFrame.

interface NavigationMessage { type: "navigation"; path: string; query: Object; }
Message example
{ type: 'navigation', path: '/live/tennis', query: { tab: 'all' } }

path contains the iFrame route — for example /live/tableTennis or /events/yurii-volkov-andrii-troian-11691674.

Example:
import integration from "path_to/integration"; integration.on("navigation", ({ path, query }) => { // update your router });

Note the payload is flat — path and query sit directly on the message, not under value.

Updating your URL

Use the History replaceState API  through your framework’s router.

Tip

Add a route prefix to everything you receive from the iFrame, so iFrame routes cannot collide with your own. With the prefix /sport, the routes above become /sport/live/tableTennis and /sport/events/yurii-volkov-andrii-troian-11691674.

Your page → iFrame: iframe-navigation

mount() sends this on initial page load for you, so the iFrame can build its history from the visited path. You send it yourself only to drive navigation, as below.

Example:
import integration from "services/integration"; integration.emit({ path: "/basketball/live" }, "iframe-navigation");

The same message also lets you drive navigation inside the iFrame. If you build your own sports sidebar, its links can move the iFrame to the matching section, giving you full control over how navigation is presented.

The iFrame cannot open links in the top window itself. Cross-origin iframes are blocked from calling window.top.location.replace() or window.top.open(), so the iFrame delegates to your page instead — this is what banner clicks use.

Example:
import integration from "path_to/integration"; integration.on("openLink", ({ url, target }) => { window.open(url, target); });
  • url — absolute URL of the destination page.
  • target — navigation target, '_self', '_blank', and so on.

If you do not handle openLink, banner clicks inside the iFrame silently do nothing.

React helper hook

If your site uses React, this useNavigation hook wraps the path juggling above. The utility functions work outside React too — see the JSDoc comments.

import { useCallback } from "react"; import { useLocation, useNavigate } from "react-router-dom"; const IFRAME_PREFIX = "sports"; /** * Extracts the language from the given path. * * @param {string} path - The URL path. * @param {Array} languages - Array of available languages. * @returns {string|undefined} - The language code if found, otherwise undefined. */ export const getUrlLang = (path, languages) => { return path.split("/").find((segment) => languages.includes(segment)); }; /** * Prepares the iframe path by removing language and prefix segments. * * @param {string} path - The URL path. * @param {Array} languages - Array of available language codes. * @param {string} fallbackLang - The default language code. * @returns {string} - The prepared iframe path (removes lang and prefix) */ const prepareIframePath = (path, languages, fallbackLang) => { const lang = getUrlLang(path, languages) || fallbackLang; const regex = new RegExp(`${IFRAME_PREFIX}/${lang}/?|/${lang}/?`, "g"); return path.replace(regex, ""); }; /** * Prepares the client path by concatenating prefix, language and iframe path segments. * * @param {string} path - The iframe path. * @param {Array} languages - Array of available language codes. * @param {string} fallbackLang - The default language code. * @returns {string} - The prepared client path. */ const prepareClientPath = (path, languages, fallbackLang) => { const lang = getUrlLang(path, languages) || fallbackLang; const preparedIframePath = prepareIframePath(path, languages, fallbackLang); return preparedIframePath === "/" ? preparedIframePath : `/${IFRAME_PREFIX}/${lang}${preparedIframePath}`; }; /** * Custom hook for client-iframe navigation. * * @param {string} lang - The current language code. * @param {Array} langList - Array of supported language codes. * @returns {Object} - Object containing navigation functions and paths. */ export const useNavigation = (lang, langList) => { const navigateByPath = useNavigate(); const { pathname } = useLocation(); const iframePath = prepareIframePath(pathname, langList, lang); const clientPath = prepareClientPath(pathname, langList, lang); /** * Navigates by path. * * @param {Object} options - Navigation options. * @param {string} options.path - The path to navigate to. * @param {string} [options.language=lang] - The language code to use. */ const navigate = useCallback( ({ path, language = lang }) => { navigateByPath(prepareClientPath(path, langList, language), { replace: true, }); }, [lang, navigateByPath, langList], ); return { navigateByPath, navigate, iframePath, clientPath, }; }; export default useNavigation;
Note

The hook’s methods return paths for both the parent site and the iframe, with the sport prefix taken into account.

Hook integration in App.jsx file simplified example
// ...imports etc import integration from "services/integration"; const prepareUrl = (lang, path = "") => innerAppHost + `/${lang}` + path; function App() { const { lang, langCodes } = useLanguageService(); // any language service const { navigate, iframePath } = useNavigation(lang, langCodes); const routerRef = useRef({ prevPath: iframePath }); // Own the URL ourselves, so the module does not also rewrite it. useEffect(() => { integration.init({ languages: langCodes, isNavigationCaptureDisabled: true, }); }, [langCodes]); useEffect(() => { const subscription = integration.on("navigation", ({ path }) => { if (!path || path === routerRef.current.prevPath) return; routerRef.current.prevPath = path; navigate({ path }); }); return () => subscription.off(); }, [navigate]); useEffect(() => { const { prevPath } = routerRef.current; integration.mount("sportsbook", prepareUrl(lang, prevPath)); navigate({ path: prevPath, language: lang }); }, [lang, navigate]); return <div id="sportsbook" />; } export default App;
Important

Two details make this work. isNavigationCaptureDisabled: true stops the module from rewriting the URL, since this example’s own router owns it — leave it out and both will fight over history. And the navigation subscription is registered in an effect that returns subscription.off(), so a re-render replaces the listener instead of stacking another one on top.