# How Instatic Admin Routing Works: A Deep Dive into the Custom Router Implementation

> Discover how Instatic's custom admin router replaces react-router-dom using useSyncExternalStore for seamless browser history synchronization and parameterized routes.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: deep-dive
- Published: 2026-08-01

---

**Instatic implements a lightweight, custom router in `src/admin/lib/routing/` that replaces react-router-dom, using `useSyncExternalStore` to synchronize React with browser history while supporting parameterized routes and navigation transitions.**

Instatic's admin UI relies on a custom routing solution rather than external dependencies like react-router-dom. According to the CoreBunch/Instatic source code, this bespoke system lives entirely within the admin package and provides a minimal API surface covering declarative routes, navigation hooks, and SSR-safe components.

## Architecture Overview

The Instatic admin routing system exports all components and hooks from `@admin/lib/routing`. The design separates router implementation from state management utilities across three core files:

- **[`src/admin/lib/routing/Router.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/Router.tsx)** – Component implementations including `<Router>`, `<MemoryRouter>`, `<Routes>`, `<Route>`, `<Navigate>`, and `<Link>`.
- **[`src/admin/lib/routing/routerHooks.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/routerHooks.ts)** – Context definitions, path-matching logic (`matchPath`), and public hooks.
- **[`src/admin/lib/routing/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/index.ts)** – Clean re-exports for convenient imports.

The API provides browser and memory router variants, declarative route tables, and navigation hooks:

- **`<Router>`** – Subscribes to `popstate` and a custom `instatic:locationchange` event to keep React synchronized with `history.pushState` and `history.replaceState`.
- **`<MemoryRouter>`** – In-memory router used exclusively in test suites, maintaining its own `pathname` and `search` snapshot.
- **`<Routes>` / `<Route>`** – Declarative route matching where the first matching `<Route>` wins, supporting `:param` segments and catch-all `*` patterns.
- **`useLocation`** – Returns the current `{ pathname, search }`.
- **`useNavigate`** – Returns a navigation function accepting `(to, {replace?}) => void`.
- **`useParams`** – Provides URL parameters extracted from the matched `<Route>`.
- **`useInRouterContext`** – Boolean indicating router context presence for SSR-safe conditional rendering.

## Core Implementation Details

### Location Store and Browser Synchronization

The `<Router>` component in [`src/admin/lib/routing/Router.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/Router.tsx) utilizes React's `useSyncExternalStore` to bridge browser history with React state. It subscribes to two events: the standard `popstate` and the custom `instatic:locationchange`.

The subscription logic, defined in [`src/admin/lib/routing/routerHooks.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/routerHooks.ts), uses `browserSubscribe` to register listeners and `getBrowserSnapshot` to read `window.location.pathname + window.location.search`. For server-side rendering scenarios, `getServerSnapshot` returns `'/'` unconditionally, ensuring hydration matches.

### Navigation with Transitions

When navigation occurs programmatically or via user interaction, the `navigate(to, {replace})` function updates browser history using `pushState` or `replaceState`, then dispatches a `LOCATION_CHANGE_EVENT` inside `React.startTransition()`. According to the source code in [`src/admin/lib/routing/routerHooks.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/routerHooks.ts), this transition wrapper marks the subsequent render as low-priority, preventing UI flashes when lazy-loaded chunks are fetching.

### Memory Router for Testing

The `<MemoryRouter>` implementation maintains a `snapshot` state via `useState` and updates it via `setSnapshot(to)` wrapped in the same `startTransition` logic as the browser router. This ensures test behavior mirrors production navigation patterns exactly, as verified in [`src/__tests__/admin/routing.test.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/admin/routing.test.tsx).

### Route Matching Algorithm

Path matching resides in `matchPath(pattern, pathname)` within [`src/admin/lib/routing/routerHooks.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/routerHooks.ts). The algorithm compiles patterns once per render via `compilePattern`:

1. **Parameterized segments** – Strings starting with `:` convert to capture groups `([^/]+)` and populate the `paramNames` array.
2. **Wildcards** – The `*` character becomes a greedy match for any remaining path segments.
3. **Anchoring** – The pattern is escaped and anchored with `^` and `/?$` to ensure exact matches.

The resulting regular expression executes against the pathname, returning an object containing decoded parameters when matches occur.

## Route Rendering and Component Behavior

### Routes and Route Components

The `<Routes>` component gathers all `<Route>` children using `collectRouteChildren`, then iterates through them calling `matchPath` for each until finding a match. The matching `<Route>`'s `element` prop renders inside a `RouteContext.Provider` that supplies extracted parameters to descendant components.

This implementation supports nested routes through the `*` wildcard pattern, allowing admin sections like site editing to define sub-routes without declaring every path at the top level.

### Link Component Behavior

The `<Link>` component checks for router context via `use(RouterContext)`. When context exists, it intercepts left-clicks without modifier keys, prevents default browser navigation, and calls `ctx.navigate(to, {replace})`. If the router context is absent—such as during server-side rendering or when rendered outside a provider—it emits a standard anchor tag, letting the browser handle navigation normally.

## Practical Usage Examples

Implementing the router at your admin entry point requires wrapping the application in `<Router>` and defining routes within `<Routes>`:

```tsx
// app.tsx – top-level admin entry point
import { Router, Routes, Route, Link, Navigate } from '@admin/lib/routing';
import Dashboard from './pages/Dashboard';
import SiteEditor from './pages/site/SiteEditor';

export default function AdminApp() {
  return (
    <Router>
      <nav>
        <Link to="/dashboard">Dashboard</Link>
        <Link to="/site">Site editor</Link>
      </nav>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/site/*" element={<SiteEditor />} />
        {/* catch-all redirect */}
        <Route path="*" element={<Navigate to="/dashboard" replace />} />
      </Routes>
    </Router>
  );
}

```

Accessing route parameters and programmatic navigation within page components:

```tsx
// Inside a page component – using params and navigation programmatically
import { useParams, useNavigate } from '@admin/lib/routing';

export function ComponentEdit() {
  const { componentId } = useParams<{ componentId: string }>();
  const navigate = useNavigate();

  const save = async () => {
    // …save logic…
    navigate(`/site/components/${componentId}`, { replace: true });
  };

  return (
    <div>
      <h1>Edit component {componentId}</h1>
      <button onClick={save}>Save</button>
    </div>
  );
}

```

## Summary

- Instatic admin routing replaces react-router-dom with a custom implementation in `src/admin/lib/routing/` optimized for bundle size and specific admin UI needs.
- The system uses `useSyncExternalStore` to synchronize React components with browser history events (`popstate` and custom `instatic:locationchange`).
- Navigation updates occur inside `startTransition` to prioritize rendering and prevent loading state flashes.
- Route matching supports `:param` segments and `*` wildcards through a regex-based `matchPath` function compiled per render.
- The architecture cleanly separates components ([`Router.tsx`](https://github.com/CoreBunch/Instatic/blob/main/Router.tsx)) from hooks and logic ([`routerHooks.ts`](https://github.com/CoreBunch/Instatic/blob/main/routerHooks.ts)), preserving Fast Refresh during development.
- SSR-safe fallbacks ensure `<Link>` renders as standard anchor tags when router context is unavailable.

## Frequently Asked Questions

### How does Instatic's custom router differ from react-router-dom?

Instatic's router in `src/admin/lib/routing/` provides a minimal subset of react-router-dom features specifically tailored for the admin interface. It eliminates unused features to reduce bundle size while maintaining essential capabilities like parameterized routes, nested routing via wildcards, and programmatic navigation. The implementation uses `useSyncExternalStore` directly rather than the internal history libraries used by react-router-dom.

### What is the purpose of the `instatic:locationchange` custom event?

The `instatic:locationchange` event serves as a communication mechanism between the imperative navigation API and React's declarative rendering. When `navigate()` calls `history.pushState` or `history.replaceState`, it dispatches this custom event inside `startTransition`, notifying the `useSyncExternalStore` subscription in `<Router>` to trigger a re-render with the new location data.

### How does the router handle server-side rendering (SSR)?

During SSR, `getServerSnapshot` returns `'/'` as the default pathname, ensuring the server and client generate matching markup for hydration. Components like `<Link>` detect the absence of router context via `useInRouterContext` and render as standard `<a>` tags, allowing the browser to handle navigation normally when JavaScript hasn't yet hydrated.

### Where is the route matching logic implemented in the source code?

The `matchPath` function and pattern compilation logic reside in [`src/admin/lib/routing/routerHooks.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/routerHooks.ts). This file contains the regex generation for `:param` segments and `*` wildcards, along with parameter extraction and decoding logic used by both the `<Routes>` component and the `useParams` hook.