# How TanStack Router Manages Routes in OpenSEO: A Complete Architecture Guide

> Discover how TanStack Router manages routes in OpenSEO. Explore file-based routing, compile-time code generation, and type-safe route trees for efficient URL management.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: architecture
- Published: 2026-09-02

---

**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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`:

```tsx
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`](https://github.com/every-app/open-seo/blob/main/src/routeTree.gen.ts), a generated file containing the complete route hierarchy.

### Generated Route Tree Structure

The [`routeTree.gen.ts`](https://github.com/every-app/open-seo/blob/main/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:

```typescript
// 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`](https://github.com/every-app/open-seo/blob/main/src/router.tsx):

```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

```tsx
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

```tsx
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

```tsx
// ✅ 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

```tsx
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`](https://github.com/every-app/open-seo/blob/main/src/routeTree.gen.ts) with complete type definitions
- **Router initialization** in [`src/router.tsx`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/routeTree.gen.ts).

### What happens if routeTree.gen.ts is out of sync?

TypeScript will report errors in [`src/router.tsx`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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.