How Instatic's Custom Admin Router Replaces react-router-dom: A 150-Line Alternative

Instatic eliminates react-router-dom from its admin UI by implementing a ~150-line custom router in src/admin/lib/routing that uses history.pushState and a custom instatic:locationchange event, reducing the admin bundle by approximately 30 KB while maintaining API compatibility through useSyncExternalStore and React.startTransition.

Instatic replaces the industry-standard react-router-dom library with a purpose-built admin router optimized for its four-route single-page application. This custom implementation lives under src/admin/lib/routing and delivers a negligible bundle footprint compared to the ~30 KB gzipped cost of the full routing library. By stripping away unused features like nested routes and data loaders, Instatic achieves faster cold-load performance while preserving a familiar React Router API surface.

Why Instatic Replaced react-router-dom

The decision to drop react-router-dom centers on bundle size and feature elimination. The full library ships with sophisticated capabilities—including loaders, actions, nested layouts, and data routers—that Instatic's admin UI never utilizes. According to the CoreBunch/Instatic source code, importing react-router-dom would inflate the eager bundle by roughly 30 KB gzipped, while the custom admin router weighs in at approximately 150 lines of code with negligible impact on the final bundle size.

This lean approach targets the admin SPA's specific requirements: a flat route table handling only four to ten static paths. By avoiding the overhead of general-purpose routing logic, Instatic ensures ultra-fast cold-load performance for administrative interfaces.

API Surface and Component Parity

Despite the dramatic size reduction, Instatic's admin router maintains API compatibility with react-router-dom's essential components. In src/admin/lib/routing/Router.tsx, the implementation exposes familiar primitives including Router, MemoryRouter, Routes, Route, Navigate, and Link.

The hook implementations in src/admin/lib/routing/routerHooks.ts provide useLocation, useNavigate, useParams, and useInRouterContext, ensuring that developers can write routing logic using the same patterns as standard React Router applications. This surface-level parity allows for intuitive adoption without learning a new API paradigm.

Mount the router at the admin entry point as follows:

import { Router } from '@admin/lib/routing';
import { AdminRoutes } from './router';

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

Custom Navigation Implementation

Unlike react-router-dom's internal state management, Instatic's router relies on native browser APIs. Navigation occurs through history.pushState and history.replaceState, complemented by a custom instatic:locationchange event dispatched whenever the location updates.

Components subscribe to location changes using useSyncExternalStore, which reads the current location from the browser's history state without triggering full React re-render loops. This approach, implemented in src/admin/lib/routing/Router.tsx, provides efficient synchronization between the URL and React component tree while maintaining compatibility with React 18 concurrent features.

Smooth Transitions with React.startTransition

A critical performance advantage over standard react-router-dom behavior involves transition handling. Instatic wraps every navigation call in React.startTransition, allowing the UI to continue displaying the previous page while lazy-loaded admin workspace chunks download in the background.

This prevents the flashing <Suspense> fallback that typically occurs during route transitions in standard React Router applications. When navigating between admin sections, users experience seamless transitions rather than jarring loading states, as the router coordinates with React's concurrent rendering to prioritize user experience.

For programmatic navigation, use the admin-specific hook:

import { useAdminNavigate } from '@admin/lib/useAdminNavigate';

function SaveButton() {
  const navigate = useAdminNavigate();

  const handleSave = async () => {
    await saveData();
    navigate('/admin/content');
  };

  return <button onClick={handleSave}>Save</button>;
}

Architectural Constraints and Route Limitations

The admin router deliberately imposes strict limitations to maintain its minimal footprint. It supports only flat route tables—nested routes are explicitly prohibited. Route matching recognizes static segments, :param placeholders, and a catch-all * wildcard, but disallows optional segments and regex-style patterns.

These constraints, documented in docs/reference/admin-router.md, prevent accidental complexity while satisfying the admin UI's fixed route set. The router is encapsulated behind the @admin/lib/routing barrel export, with strict rules forbidding imports from core or module code to ensure routing logic remains confined to the admin interface.

Defining Routes and Testing

Route definitions follow a familiar JSX pattern similar to react-router-dom, as implemented in src/admin/router.tsx:

import { Routes, Route, Navigate } from '@admin/lib/routing';
import AdminEntry from './AdminEntry';

export function AdminRoutes() {
  return (
    <Routes>
      <Route path="/" element={<Navigate to="/admin/dashboard" replace />} />
      <Route path="/admin/dashboard" element={<AdminEntry section="dashboard" />} />
      <Route path="/admin/*" element={<Navigate to="/admin/dashboard" replace />} />
    </Routes>
  );
}

For testing scenarios, the MemoryRouter component simulates navigation without manipulating the real browser history:

import { MemoryRouter, Routes, Route } from '@admin/lib/routing';
import { render, screen } from '@testing-library/react';

render(
  <MemoryRouter initialEntries={['/admin/dashboard']}>
    <Routes>
      <Route path="/admin/dashboard" element={<div>Dashboard</div>} />
    </Routes>
  </MemoryRouter>
);

expect(screen.getByText('Dashboard')).toBeInTheDocument();

Summary

  • Instatic's custom admin router replaces react-router-dom with a ~150-line implementation located in src/admin/lib/routing, eliminating ~30 KB from the admin bundle.
  • The router uses history.pushState and a custom instatic:locationchange event for navigation, subscribed via useSyncExternalStore.
  • Every navigation is wrapped in React.startTransition to enable smooth lazy-loaded transitions without <Suspense> fallback flashing.
  • The API surface mirrors react-router-dom with components like Router, Routes, Route, and hooks like useNavigate, but supports only flat routes with static segments, :param placeholders, and * wildcards.
  • Strict encapsulation behind @admin/lib/routing prevents usage outside the admin UI, ensuring architectural boundaries remain intact.

Frequently Asked Questions

What specific react-router-dom features does Instatic's router omit?

Instatic's implementation intentionally omits nested route configurations, data loaders, actions, and regex-based path matching. It supports only flat route tables with static segments, :param placeholders, and catch-all * wildcards. This limitation keeps the codebase at approximately 150 lines while satisfying the admin SPA's simple routing requirements.

How does the custom router handle browser history without react-router-dom?

The router manipulates browser history directly through the native history.pushState and history.replaceState APIs. When navigation occurs, it dispatches a custom instatic:locationchange event that notifies all subscribed components. Hooks like useLocation leverage useSyncExternalStore to read from this external history source, ensuring synchronization with React's rendering cycle without the overhead of React Router's internal state machine.

Can I use the Instatic admin router in non-admin parts of the application?

No. The admin router is strictly encapsulated behind the @admin/lib/routing barrel export and is forbidden in core or module code according to the project architecture rules defined in docs/reference/admin-router.md. This constraint ensures that the admin-specific routing logic remains isolated and prevents accidental coupling between the admin interface and other application modules.

Why does Instatic use React.startTransition for navigation?

Instatic wraps navigation calls in React.startTransition to prioritize keeping the current UI visible while loading lazy-loaded route components in the background. This prevents the jarring flash of a <Suspense> fallback that typically occurs during route transitions in standard react-router-dom applications, creating a smoother user experience when navigating between admin sections with heavy asynchronous dependencies.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →