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, and children properties
  • Type definitions via FileRoutesByPath, FileRoutesById, and related interfaces
  • The exported routeTree object 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

  1. Detection: The TanStack Router generator scans src/web/src/routes/ for files using createFileRoute
  2. Analysis: Extracts route paths, parameters, and parent relationships from file locations
  3. Type synthesis: Generates TypeScript interfaces for route parameters and paths
  4. Tree assembly: Produces the routeTree export 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.

OpenSEO components interact with the router through type-safe APIs that leverage the generated route definitions.

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 params properties 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.ts with complete type definitions
  • Router initialization in src/router.tsx configures preload, error handling, and scroll behavior
  • Type-safe navigation via <Link> and useRouter prevents 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →