AbsoluteJS

React SPA

Drive client-side sub-route navigation inside a React page using react-router. AbsoluteJS forwards the request URL into the page so SSR resolves the right route on every load — including refresh and deep-link entry.

#How It Works

React Router has battle-tested SSR support via StaticRouter on the server and BrowserRouter on the client. AbsoluteJS doesn't ship a wrapper — you install react-router and use it directly. The adapter's only job is to plumb the request URL into your page so the server-side router has something to match against.

1
request field on handleReactPageRequest input
pass the Elysia request through.
2
The handler reads new URL(request.url).pathname
and auto-injects it into props.url if you didn't already pass one.
3
Your page component receives url as a prop
and feeds it to <StaticRouter location={url}> for SSR.
4
On the client, url is undefined (no request to derive it from)
the page swaps to <BrowserRouter> which reads from window.location directly.

#Wildcard Route

Refresh on any sub-route hits the server with the actual URL. Register a wildcard pattern so the same handler responds for every sub-URL the page's router knows about:

TS
// backend/server.ts — register a wildcard route per page so refresh /
// deep-link on any sub-route hits the same handler. The native router
// then dispatches based on the URL.
new Elysia()
  .get('/dashboard', dashboardHandler)
  .get('/dashboard/*', dashboardHandler)
  .listen(3000);

#Page Handler

Pass request through to the page handler. The handler computes new URL(request.url).pathname and merges it into your page's props as url (only if you didn't supply your own url prop):

TS
// backend/server.ts
import { handleReactPageRequest } from '@absolutejs/absolute/react';
import { ReactSpa } from '../frontend/react/pages/ReactSpa';

const reactHandler = ({ request }: { request: Request }) =>
  handleReactPageRequest({
    Page: ReactSpa,
    index: asset(manifest, 'ReactSpaIndex'),
    props: { cssPath: asset(manifest, 'SpaCSS') },
    request, // ← URL auto-injected as props.url for the page
  });

new Elysia()
  .get('/react', reactHandler)
  .get('/react/*', reactHandler);

#Page Component

The page swaps between StaticRouter (server) and BrowserRouter (client). The url prop is undefined on the client, which is the cue to switch:

TSX
// frontend/react/pages/ReactSpa.tsx
import {
  BrowserRouter,
  Link,
  Route,
  Routes,
  StaticRouter,
  useLocation,
} from 'react-router';

type ReactSpaProps = {
  url?: string; // auto-injected from request by the page handler
  cssPath?: string;
};

export const ReactSpa = ({ url, cssPath }: ReactSpaProps) => {
  // <StaticRouter location> on the server, <BrowserRouter> on the client.
  // The url prop is undefined on the client — that's the cue to switch.
  const isServer = typeof window === 'undefined';
  const Router = isServer ? StaticRouter : BrowserRouter;
  const routerProps = isServer ? { location: url ?? '/' } : {};

  return (
    <html>
      <head>{cssPath && <link rel="stylesheet" href={cssPath} />}</head>
      <body>
        <Router {...routerProps}>
          <nav>
            <Link to="/react">Home</Link>
            <Link to="/react/settings">Settings</Link>
            <Link to="/react/profile">Profile</Link>
          </nav>
          <Routes>
            <Route path="/react" element={<HomeView />} />
            <Route path="/react/settings" element={<SettingsView />} />
            <Route path="/react/profile" element={<ProfileView />} />
          </Routes>
        </Router>
      </body>
    </html>
  );
};
StaticRouterserver-side router that takes a fixed location prop. The page handler injects the request pathname so SSR resolves the right route.
BrowserRouterclient-side router that reads from window.location. Used after hydration for client-side navigation via history.pushState.
Routes / Routedeclarative path matching. React Router v6+ uses specificity ranking — longest static prefix wins, no manual ordering required.
Linkclient-side navigation anchor. Renders a real <a href> for progressive enhancement; click is intercepted on same-origin URLs.
useLocation / useNavigatehooks for reading the current location and navigating programmatically.

In React Router v7, BrowserRouter, StaticRouter, Routes, Route, and Link all import from the react-router package — not react-router-dom, which is now a re-export shim.

#Redirects

React Router supports redirects via thrown Response objects from loaders and actions. In the AbsoluteJS adapter, catch the thrown Response in your Elysia handler and return it directly — Elysia treats it as the outbound response and the browser sees a real HTTP redirect. For pre-render redirects without a loader, perform the check in the route handler before calling handleReactPageRequest.