How TanStack Router Manages Routes in OpenSEO: A Complete Architecture Guide
TanStack Router manages routes in OpenSEO through file-based routing with compile-time code generation, creating a type-safe route tree that maps folder structure directly to URL paths with support for dynamic parameters and nested layouts.
OpenSEO, an open-source SEO management platform built by every-app, implements its navigation system using TanStack Router — a modern, file-based routing solution for React applications. This article examines how route management works in the OpenSEO codebase, from file organization through runtime navigation and server-side rendering.
File-Based Route Structure
The foundation of TanStack Router in OpenSEO follows a convention-over-configuration approach. Routes are defined as files in src/web/src/routes/, where the folder hierarchy directly determines the URL structure.
Route File Conventions
| Pattern | File Path | Resulting URL |
|---|---|---|
| Static route | src/web/src/routes/about.tsx |
/about |
| Dynamic segment | src/web/src/routes/_project/p/$projectId/audit.tsx |
/p/:projectId/audit |
| Layout wrapper | src/web/src/routes/_app.tsx |
Parent layout (no URL segment) |
| Nested routes | src/web/src/routes/_project/p/$projectId/settings.tsx |
/p/:projectId/settings |
The $ prefix denotes dynamic route parameters. In the example above, $projectId becomes a typed parameter accessible via Route.useParams().
Creating a Route Definition
Each route file exports a Route object created with createFileRoute:
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/p/$projectId/reports").component(() => {
const { projectId } = Route.useParams();
return (
<div>
<h1>SEO Reports</h1>
<p>Project ID: {projectId}</p>
</div>
);
});
The string passed to createFileRoute must match the file's path relative to the routes directory. This creates compile-time validation — if the path mismatches, TypeScript reports an error.
Code Generation and the Route Tree
TanStack Router's power comes from compile-time code generation. The build process analyzes all route files and produces src/routeTree.gen.ts, a generated file containing the complete route hierarchy.
Generated Route Tree Structure
The routeTree.gen.ts file (auto-generated, never manually edited) contains:
- Route metadata with
id,path,getParentRoute, andchildrenproperties - Type definitions via
FileRoutesByPath,FileRoutesById, and related interfaces - The exported
routeTreeobject consumed by the router
Each route is assembled through generated update() calls that establish parent-child relationships:
// From routeTree.gen.ts (generated)
const ProjectAuditRoute = AuditImport.update({
id: "/p/$projectId/audit",
path: "/p/$projectId/audit",
getParentRoute: () => ProjectRoute,
}) as any;
This hierarchical structure enables nested routing — parent routes can provide shared layouts, data loaders, and error boundaries while child routes render specific content.
Build-Time Generation Process
- Detection: The TanStack Router generator scans
src/web/src/routes/for files usingcreateFileRoute - Analysis: Extracts route paths, parameters, and parent relationships from file locations
- Type synthesis: Generates TypeScript interfaces for route parameters and paths
- Tree assembly: Produces the
routeTreeexport with complete navigation map
Regeneration occurs automatically during development and as part of the production build.
Router Initialization in OpenSEO
The generated route tree connects to the runtime router in src/router.tsx:
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
export function getRouter() {
const router = createTanStackRouter({
routeTree,
defaultPreload: "intent",
defaultErrorComponent: DefaultCatchBoundary,
defaultNotFoundComponent: () => <NotFound />,
scrollRestoration: true,
});
return router;
}
Key Configuration Options
| Option | Value | Purpose |
|---|---|---|
routeTree |
Generated import | Complete route map from build process |
defaultPreload |
"intent" |
Fetch data on hover/focus, not immediately |
defaultErrorComponent |
DefaultCatchBoundary |
Global error handling for all routes |
defaultNotFoundComponent |
NotFound |
404 fallback for unmatched routes |
scrollRestoration |
true |
Automatic scroll position management |
The intent-based preloading strategy optimizes performance by deferring data fetching until the user signals navigation intent (hovering or focusing a link), reducing unnecessary network requests.
Programmatic Navigation and Links
OpenSEO components interact with the router through type-safe APIs that leverage the generated route definitions.
Using the Link Component
import { Link } from "@tanstack/react-router";
function ProjectNavigation({ projectId }: { projectId: string }) {
return (
<nav>
<Link
to="/p/$projectId/audit"
params={{ projectId }}
>
Audit
</Link>
<Link
to="/p/$projectId/backlinks"
params={{ projectId }}
activeProps={{ className: "active" }}
>
Backlinks
</Link>
<Link
to="/p/$projectId/settings"
params={{ projectId }}
>
Settings
</Link>
</nav>
);
}
The params prop receives strict typing based on the target route's dynamic segments. TypeScript enforces that projectId is provided and correctly typed as string.
Programmatic Navigation with useRouter
import { useRouter } from "@tanstack/react-router";
function QuickProjectSwitcher() {
const router = useRouter();
const handleSelect = (projectId: string) => {
router.navigate({
to: "/p/$projectId",
params: { projectId },
replace: false // add to history stack
});
};
return <ProjectDropdown onSelect={handleSelect} />;
}
The navigate method accepts the same type-safe parameters as Link, with additional options for replace, state, and resetScroll.
Type Safety and Developer Experience
TanStack Router's generated types eliminate an entire class of routing bugs common in React applications.
Compile-Time Guarantees
- Route existence: Invalid route paths cause TypeScript errors
- Parameter validation: Missing or mistyped
paramsproperties are caught at build - Search params: URL query parameters are fully typed when defined
- Loader data: Data returned from route loaders is typed for consuming components
Example Error Prevention
// ✅ Valid — TypeScript accepts
<Link to="/p/$projectId/audit" params={{ projectId: "123" }}>
// ❌ Error — missing required parameter
<Link to="/p/$projectId/audit" params={{}}>
// ❌ Error — typo in route path
<Link to="/p/$projectId/audt" params={{ projectId: "123" }}>
Server-Side Rendering and Data Loading
OpenSEO leverages TanStack Router's capabilities for SSR/SSG through route-level data loaders.
Loader Functions in Route Files
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/p/$projectId").component(ProjectPage);
// Loader fetches data before component renders
Route.loader = async ({ params }) => {
const project = await fetchProject(params.projectId);
const metrics = await fetchProjectMetrics(params.projectId);
return { project, metrics };
};
function ProjectPage() {
// Loader data is immediately available, no suspense needed for initial render
const { project, metrics } = Route.useLoaderData();
return (
<div>
<h1>{project.name}</h1>
<MetricsCard data={metrics} />
</div>
);
}
On the server, loaders execute before HTML generation. On the client, they may re-execute based on caching strategies or navigation events. The statically generated routeTree ensures the server knows which loaders to run for each URL.
Summary
- File-based routing in
src/web/src/routes/maps folder structure to URLs automatically - Compile-time generation produces
src/routeTree.gen.tswith complete type definitions - Router initialization in
src/router.tsxconfigures preload, error handling, and scroll behavior - Type-safe navigation via
<Link>anduseRouterprevents runtime routing errors - Nested routes with
_prefix layouts enable shared UI and data loading patterns - SSR support through route loaders that execute before rendering on server and client
Frequently Asked Questions
How do I add a new route in OpenSEO?
Create a file in src/web/src/routes/ using createFileRoute. The file path determines the URL—use $ for dynamic parameters and _ prefix for layout wrappers. Run the development server to trigger regeneration of routeTree.gen.ts.
What happens if routeTree.gen.ts is out of sync?
TypeScript will report errors in src/router.tsx and any component using route-specific types. The file regenerates automatically in development, or you can run the generator manually with the TanStack CLI.
Why does OpenSEO use "intent" preloading instead of "viewport" or "render"?
The "intent" strategy balances performance and responsiveness. It fetches data when users hover or focus links rather than immediately on render (wasteful) or when links enter the viewport (aggressive). This matches OpenSEO's dashboard-heavy interface where users explore multiple projects sequentially.
Can I override the global error boundary for specific routes?
Yes. While src/router.tsx sets defaultErrorComponent, individual routes can export their own errorComponent property. This allows project-specific error UIs while maintaining the global DefaultCatchBoundary as a fallback.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →