Build an SPA
Use build an spa with the current published Askr packages.
@askrjs/askr0.0.59
How to use build an spa
Create one route registry, mount it with createSPA, and keep browser-only services behind lifecycle-owned components.
- Import the published @askrjs/askr entrypoint.
- Keep build an spa configuration next to the component, route, or server composition root that owns it.
- Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { createSPA } from '@askrjs/askr/boot';
import { createRouteRegistry, route } from '@askrjs/askr/router';
const registry = createRouteRegistry(() => {
route('/', DashboardPage);
route('/projects/:projectId', ProjectPage);
});
await createSPA({ root: document.getElementById('app')!, registry });Goal and architecture
A client-only SPA mounts once, owns routing in the browser, and never touches the server beyond static asset delivery and whatever APIs it calls at runtime. You register routes with `registerRoutes()` or build a registry via `createRouteRegistry(() => { ... })`, then hand that to `createSPA({ root: '#app', registry })`. `@askrjs/vite`'s `askr()` plugin is the only build-time piece you need — there's no server plugin, no `askrServer()` entry, and no document markers to manage.
Implementation
Define routes with `route(path, Component)` or the nested `page()`/`group()`/`index()` helpers inside your registration function, and read the matched params straight from the component's first argument — Askr infers `RoutePathParams<Path>` from the literal path string, so `route('/users/{id}', UserPage)` gives `UserPage` a typed `id: string` without you writing the type by hand. State inside components is `state()`/`derive()`: read through the getter, write through the setter, no proxies. Wire up `<Link>` or `navigate()` for transitions and `Outlet()` in any layout component that needs to render nested routes.
Failure states
Wrap render-time failures in `ErrorBoundary` with a `fallback` — it logs the underlying error and can reset when your app state changes, which matters in an SPA where a bad route param or a malformed API response shouldn't take down the whole shell. Register a `fallback(Component)` route for unmatched paths so users don't hit a blank screen on a typo'd URL. If a route has an `auth` policy and the resolver returns `deny`/`redirect`, make sure you've actually mounted with `auth` options on `createSPA`, or those policies never run.
Verification
Click through every route you registered and confirm the URL bar, browser back/forward, and scroll position all behave — `scrollRestoration` on `SPAConfig` is opt-in, so verify it's doing what you expect rather than assuming a default. Force a render-time error in a leaf component and confirm the nearest `ErrorBoundary` catches it instead of a blank page. Finally check `getManifest()` against your actual route tree; a stale manifest is the most common source of "routes work in dev but not in the built app."