# How the Instatic Admin Router Replaces react-router-dom: Custom Routing for the Admin Shell

> Discover how the Instatic admin router replaces react-router-dom with a dependency-free, query-string-only routing system. Experience faster admin shells without full page reloads.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-07-29

---

**The Instatic admin router replaces react-router-dom with a lightweight, query-string-only routing system implemented in [`src/admin/lib/routing/Router.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/Router.tsx) and [`routerHooks.ts`](https://github.com/CoreBunch/Instatic/blob/main/routerHooks.ts), providing a compatible API through React context while avoiding external dependencies and full page reloads.**

The CoreBunch/Instatic repository implements a custom in-house routing solution for its administrative interface. Instead of bundling the popular `react-router-dom` library, the project maintains a tiny replacement located in `src/admin/lib/routing/` that manipulates only the query string, keeping URLs stable while enabling seamless internal navigation.

## The Architecture Decision

The admin shell intentionally avoids `react-router-dom` to reduce bundle size and eliminate compatibility concerns. According to the source comments in [`src/admin/lib/routing/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/index.ts), this is described as a "*Tiny in-house router for the admin app. Replaces react-router-dom for the admin shell.*" The router is built from scratch to integrate tightly with Instatic’s state management and permission systems, offering a minimal surface area that covers only the navigation needs of the admin panel.

## Core Implementation Files

The routing system is split across three primary files to separate concerns between the router engine and its React API.

### Router.tsx: The Root Component

Located at [`src/admin/lib/routing/Router.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/Router.tsx), this file exports the core `<Router>` component. The component listens to browser `popstate` events and drives navigation by providing a context that tracks the current location. Unlike traditional routers that manipulate the full URL pathname, this implementation restricts changes to the query string, leaving the base path untouched.

### routerHooks.ts: The API Surface

The [`src/admin/lib/routing/routerHooks.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/routerHooks.ts) file implements the hook interface that mirrors `react-router-dom`. It exports `useNavigate`, `useLocation`, `useParams`, and a `Link` component. These hooks read from the context provided by `<Router>`, allowing the rest of the admin codebase to navigate programmatically and declaratively without importing external routing libraries.

### index.ts: Public Interface

[`src/admin/lib/routing/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/lib/routing/index.ts) serves as the public entry point, re-exporting the `<Router>` component and all hooks. The file header explicitly documents the module's purpose: internal admin navigation should use this router instead of any external routing solution.

## Query-String-Only Navigation Strategy

The router's defining characteristic is its exclusive use of the query string for state management. When navigating, the router calls `window.history.replaceState` or `pushState` to update only the search parameters (e.g., `?page=123`), never the pathname. This design prevents full page reloads while keeping the admin UI's URL stable, which is essential for maintaining the shell's state during internal navigation.

## API Compatibility with react-router-dom

Despite being a custom implementation, the router exposes a familiar API to minimize the learning curve for developers accustomed to `react-router-dom`:

- **`useNavigate`** – Returns a function to programmatically update the query string.
- **`useLocation`** – Provides access to the current location object, where `location.search` contains the active query parameters.
- **`useParams`** – Extracts route parameters from the query string.
- **`Link`** – A component that renders anchor tags triggering query-string navigation without reloading the page.

## Implementation Examples

The following patterns demonstrate how the admin codebase utilizes the custom router.

Wiring the router into the admin application:

```tsx
// src/admin/App.tsx
import { Router } from '@admin/lib/routing';
import { AdminRoutes } from './routes';

export default function AdminApp() {
  return (
    <Router>
      <AdminRoutes />
    </Router>
  );
}

```

Navigating programmatically from a panel component:

```tsx
// src/admin/pages/site/SomePanel.tsx
import { useNavigate } from '@admin/lib/routing';

export function SomePanel() {
  const navigate = useNavigate();

  const openEditor = (pageId: string) => {
    // Updates only the query string, e.g. ?page=123
    navigate({ search: `?page=${pageId}` });
  };

  return <button onClick={() => openEditor('abc')}>Edit page</button>;
}

```

Using the Link component for declarative navigation:

```tsx
// src/admin/components/SidebarLink.tsx
import { Link } from '@admin/lib/routing';

export function SidebarLink({ to, children }: { to: string; children: React.ReactNode }) {
  // `to` is a query string like "?page=home"
  return <Link to={to}>{children}</Link>;
}

```

Reading the current route state from a custom hook:

```tsx
// src/admin/pages/site/hooks/useCurrentPage.ts
import { useLocation } from '@admin/lib/routing';

export function useCurrentPage() {
  const location = useLocation();
  // location.search holds the query string used by the admin router
  const params = new URLSearchParams(location.search);
  return params.get('page');
}

```

## Preventing react-router-dom Imports

To ensure the custom router remains the sole navigation method for the admin interface, the project includes an architectural guard at [`src/__tests__/architecture/admin-router-usage.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/architecture/admin-router-usage.test.ts). This test suite verifies that no files in the admin codebase import from `react-router-dom`, enforcing the architectural decision at build time.

## Summary

- The Instatic admin router is a custom-built replacement for `react-router-dom` located in `src/admin/lib/routing/`.
- It splits functionality between [`Router.tsx`](https://github.com/CoreBunch/Instatic/blob/main/Router.tsx) (context provider) and [`routerHooks.ts`](https://github.com/CoreBunch/Instatic/blob/main/routerHooks.ts) (React hooks).
- Navigation is restricted to query-string changes only, avoiding pathname manipulation and full page reloads.
- The API mirrors `react-router-dom` with `useNavigate`, `useLocation`, `useParams`, and `Link`.
- An architecture test prevents accidental imports of `react-router-dom` throughout the admin codebase.

## Frequently Asked Questions

### Why does Instatic use a custom router instead of react-router-dom?

Instatic implements a custom router to minimize bundle size and avoid the overhead of a full-featured routing library for the admin shell's specific needs. The custom solution is tightly integrated with the application's state management and permission systems, providing only the functionality required for internal navigation.

### How does the Instatic router handle navigation without changing the pathname?

The router exclusively manipulates the query string using the History API (`window.history.replaceState` or `pushState`). When you call `navigate({ search: '?page=123' })`, it updates only the search portion of the URL, leaving the pathname intact. This prevents full page reloads while allowing the application to track internal state via URL parameters.

### Is the custom router API compatible with react-router-dom hooks?

Yes, the custom router intentionally exposes a compatible API surface. Functions like `useNavigate`, `useLocation`, and `useParams`, along with the `Link` component, follow the same patterns and signatures as their `react-router-dom` counterparts, allowing developers familiar with that library to work immediately with Instatic's routing system.

### How does the project prevent accidental imports of react-router-dom?

The repository includes a dedicated architecture test at [`src/__tests__/architecture/admin-router-usage.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/architecture/admin-router-usage.test.ts) that scans the admin codebase and fails if any file imports from `react-router-dom`. This automated enforcement ensures the architectural boundary remains intact as the codebase evolves.