createRouter
Builds the theme's TanStack router with the engine's defaults: a data cache, SSR hydration, a loading skeleton, and the error and 404 pages.
import { createRouter, CreateRouterOptions, RouterInitialContext } from '@salla.sa/twilight-theme-engine/tanstack';In plain words
A router decides which page to show for an address, loads that page's data and moves between pages without reloading. TanStack Start asks your theme for one by calling getRouter() in app/router.tsx.
createRouter(routeTree) builds it with what the engine needs already switched on: a data cache (a TanStack Query QueryClient), a skeleton while a page loads, the engine's 404 and error pages, and the code that carries the server's data into the browser. routeTree is the file the build generates from your routes, app/routeTree.gen.ts.
On the server a new router is built for every request. In the browser you keep the first one, so the data cache survives page changes.
Signature
function createRouter(routeTree: any, options?: CreateRouterOptions): Router
interface CreateRouterOptions {
defaultPendingComponent?: (() => ReactNode) | false; // default <PageSkeleton />; false: none
defaultPendingMs?: number; // default 100
defaultPendingMinMs?: number; // default 200
defaultStaleTime?: number; // default Infinity
history?: RouterHistory;
}
interface RouterInitialContext { // the context every route receives
queryClient: QueryClient;
setLocation: (location: Partial<TwilightLocation>) => void;
}Try it live
createRouter() built for this very page, read back from the running app.Try this: look at rewrite: on localhost and the preview host it is on, because there the store is the first segment of every URL.import { createRouter } from '@salla.sa/twilight-theme-engine/tanstack';
import { routeTree } from './routeTree.gen';
// One router in the browser (keeps the QueryClient cache), a fresh one per server request.
let clientRouter: ReturnType<typeof createRouter> | null = null;
export function getRouter() {
if (typeof window !== 'undefined' && clientRouter) return clientRouter;
const router = createRouter(routeTree, {
defaultPendingMs: 100,
defaultPendingMinMs: 200,
});
if (typeof window !== 'undefined') clientRouter = router;
return router;
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof getRouter>;
}
}
Example
import { createRouter } from '@salla.sa/twilight-theme-engine/tanstack';
import {
registerHomeComponents,
DefaultHomeComponents,
} from '@salla.sa/twilight-theme-engine/routes/home';
import { routeTree } from './routeTree.gen';
registerHomeComponents(DefaultHomeComponents);
// One router in the browser, so the QueryClient cache survives navigations.
// The server builds a fresh one per request through getRouter().
let clientRouter: ReturnType<typeof createRouter> | null = null;
export function getRouter() {
if (typeof window !== 'undefined' && clientRouter) return clientRouter;
const router = createRouter(routeTree, {
defaultPendingMs: 100,
defaultPendingMinMs: 200,
});
if (typeof window !== 'undefined') clientRouter = router;
return router;
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof getRouter>;
}
}
How it behaves
Fixed, not options:
defaultPreload: 'intent'(hovering or focusing a link loads the next page's data), scroll restoration withinstantbehaviour,trailingSlash: 'never', the lazyNotFoundPageas the 404 page, and an error component that renders the engineErrorPageas 404, 401, 400 or 500 depending on the error.Each call creates a new
QueryClient(queries:staleTime60 s,gcTime5 min,retry1, no refetch on window focus; mutations: no retry) and writes it into the twilight context, where loaders read it asgetTwilightContext().queryClient.It installs the TanStack head adapter for the whole app (
setHeadAdapter), soresolveHead()from/utils/headreturns TanStack'smetaandlinksarrays from then on.On localhost and
preview.salla.designit adds arewrite: the store's username is removed from each URL before matching and added to every href the router builds. Your routes stay/{-$locale}/cart.For SSR it wires TanStack Query's integration (the cache travels with the HTML) and, in the browser, copies settings, locale, auth token, location, page and route id from the root match into the twilight context before head functions run again.
In the browser it keeps the context in step: when a page's loaders finish it stores their
pageobject and passes it toSalla.config.set('page', …); when a navigation resolves it updates location, route id and settings, and dispatches the SDK eventroute::changed.RouterInitialContextis the root route's context type:beforeLoadand loaders receivecontext.queryClient. The engine's type declarations already register it with TanStack'sRouterContext.
Gotchas
Build it once in the browser. Every call creates a new
QueryClientand makes it the context's client, so agetRouter()that returns a new router on each call throws away the cache the page was hydrated with. Keep theclientRoutervariable of the example.defaultStaleTimeisInfinity: going back to a page you already visited shows the data its loader returned then, without running the loader again, until TanStack drops the cached match (its defaultgcTime, 5 minutes) or you callrouter.invalidate(). Pass a number, such asdefaultStaleTime: 30_000, when pages must refetch on return.On the server it throws
[Twilight] No request context…unlesstwilightMiddleware()ran first, because it writes the newQueryClientinto the request context.
Related
Creates the root route of app/routes/__root.tsx with the engine's store-settings loading, global head tags and "Store Unavailable" error page.
twilightMiddlewareThe request middleware every theme lists first in app/start.ts; it opens the private, per-request context the rest of the engine reads.
registerHomeComponentsTells the engine which component draws each home page block, by the block path the Salla API sends. Call it once at startup.
shouldDehydrateQuery, isProductPreviewQueryThe engine's rule for which cached queries travel inside server-rendered HTML: successful and still-loading ones, never list-card previews.