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.
props.url if you didn't already pass one.<StaticRouter location={url}> for SSR.<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:
// 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):
// 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:
// 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>
);
};location prop. The page handler injects the request pathname so SSR resolves the right route.window.location. Used after hydration for client-side navigation via history.pushState.<a href> for progressive enhancement; click is intercepted on same-origin URLs.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.