Titandocs

Routing

A data router where the active market lives in the URL and every redirect is loader-level.

src/router.tsx is a React Router v7 data router with lazy() route chunks.

PathBehaviour
/redirect → /trade/<default-market-slug>
/traderedirect → /trade/<default-market-slug>
/trade/:marketSlugTradePage. Loader validates the slug and redirects bad ones.
/portfolioPortfolioPage
/tlpdisabled — loader redirects to the default trade view
*catch-all → default trade view (no 404 page)

The market lives in the URL

There is no currentSymbol in any store. The active market is the route param, read through useCurrentMarket().

// switch markets from anywhere
navigate(`/trade/${slugOf(market)}`);

// read it anywhere below the route
const market = useCurrentMarket();
const ticker = useCurrentTicker();

Two sources of truth for "which market am I on" is the classic drift bug: the store says BTC, the URL says ETH, and which one wins depends on render order. Putting it in the URL makes links shareable and removes the drift by construction.

Loader-level redirects

Every redirect happens in a loader, before the lazy chunk downloads and before any element mounts. The user never sees a frame of the wrong page.

{
  path: 'trade/:marketSlug',
  loader: ({ params }) => {
    const market = findMarketBySlug(params.marketSlug);
    if (!market) throw redirect(`/trade/${slugOf(DEFAULT_MARKET)}`);
    return null;
  },
  lazy: () => import('./pages/trade'),
}

Slug validation is synchronous, so the MARKETS list must exist before any fetch. That is why MARKETS is a hardcoded seed rather than being loaded from GET /v1/markets — the engine config then overwrites leverage, decimals and fees once it arrives. See Markets & tokens.

Chunk-loading feedback

<NavigationIndicator> reads useNavigation() and shows progress while a lazy chunk downloads. Without it, clicking a nav tab on a cold cache looks like nothing happened.

Route errors

pages/error.tsx is the router errorElement. Its main job is not pretty failure — it is the chunk-load fallback. After a deploy, a client holding the old HTML requests a hashed chunk that no longer exists; the boundary catches it, reports to PostHog with a reference id, and offers a reload.

On this page