Mario Brusarosco

tanstack router root route 404

In the ground since Sun Nov 02 2025

Last watered inSun Nov 02 2025

Related Topics

Referece Links

The Problem

Root route (/) returns 404 in production but works perfectly on localhost. The twist? The custom 404 page appears (not CloudFront's), meaning the app loads successfully but client-side routing is broken.

Root Causes

Issue 1: Stale Route Tree in Production

TanStack Router uses a generated file src/routeTree.gen.ts that contains all route configuration. This file:

  • Is generated by @tanstack/router-plugin during development
  • Must be committed to git
  • Was not being regenerated during CI/CD builds

The problem:

  • Route tree generates during vite dev but not during vite build
  • If you update routes but don't regenerate and commit the tree, production deploys with outdated routing

Evidence:

1# Local route tree was recently updated
2$ ls -lh src/routeTree.gen.ts
3-rw-rw-r-- 1 mario mario 2.1M Nov 2 16:29 src/routeTree.gen.ts
4
5# But git showed it was last committed weeks ago
6$ git log --oneline -1 -- src/routeTree.gen.ts
7158e6dd [FIX] Lint errors # Old commit!

Issue 2: Incorrect File Naming Convention

The root index route was named src/routes/index.route.tsx instead of src/routes/index.tsx.

The impact:

1// WRONG - Generated from index.route.tsx
2const IndexRouteRoute = IndexRouteRouteImport.update({
3 id: '/',
4 path: '', // ❌ Empty path doesn't match '/'
5 getParentRoute: () => rootRouteImport,
6})
7
8// CORRECT - Generated from index.tsx
9const IndexRoute = IndexRouteImport.update({
10 id: '/',
11 path: '/', // ✅ Matches root URL
12 getParentRoute: () => rootRouteImport,
13})

An empty path: '' means the route won't match /, causing the router to fall through to the notFoundComponent.

The Solution

Part 1: Automate Route Generation

Install TanStack Router CLI:

1yarn add -D @tanstack/router-cli

Add pre-commit hook:

1# .husky/pre-commit
2yarn generate-routes
3git add src/routeTree.gen.ts
4yarn lint-staged

Benefits:

  • Route tree regenerates automatically before every commit
  • Updated route tree always committed with route changes
  • Production always has latest routing configuration
  • No manual intervention needed

Part 2: Fix File Naming

1mv src/routes/index.route.tsx src/routes/index.tsx

TanStack Router's conventions:

  • index.tsx → Root index route with path: '/'
  • index.route.tsx → Treated differently, generates path: ''

TanStack Router File Naming Conventions

Standard Route Files

| File Name | Generated Route | Path | |-----------|----------------|------| | index.tsx | Root index | / | | about.tsx | About page | /about | | posts.tsx | Posts page | /posts | | posts/index.tsx | Posts index | /posts | | posts/$id.tsx | Post detail | /posts/:id |

Layout Routes (Pathless)

| File Name | Generated Route | Path | |-----------|----------------|------| | _layout.tsx | Layout wrapper | No path (wraps children) | | _auth.tsx | Auth layout | No path (wraps children) |

Special Suffixes

| Suffix | Purpose | Note | |--------|---------|------| | .lazy.tsx | Code-split route | Lazy-loaded on access | | .route.tsx | Alternative syntax | ⚠️ Avoid for index routes |

🎓 Key Learnings

Build-Time Route Generation

Unlike routers that discover routes at runtime, TanStack Router:

  • Scans src/routes/ during development
  • Generates static routeTree.gen.ts file
  • This file must be committed
  • Production uses committed file, not live generation

Critical implication: Update routes without committing the tree = broken production.

Development vs Production Behavior

Development:

  • Vite plugin watches file changes
  • Route tree regenerates automatically
  • Hot module replacement works
  • Everything feels "magic" ✨

Production:

  • Uses committed route tree file
  • No live generation
  • Stale route tree = broken routes
  • No HMR to mask issues

Best practice: Always test production builds locally:

1yarn build --mode demo
2yarn preview

Debugging Client-Side Routing Issues

Signs it's routing (not server/CDN):

  • ✅ App loads (you see your UI)
  • ✅ Network tab shows successful requests (200/304)
  • ✅ You see custom 404 page (not server's 404)
  • beforeLoad hooks don't run
  • ❌ Console logs in route files don't appear

Debug steps:

  1. Check route tree commit history: git log src/routeTree.gen.ts
  2. Inspect route tree content for path values
  3. Verify file naming follows conventions
  4. Test production build locally: yarn build && yarn preview
  5. Compare deployed bundle with local build

Prevention Strategy

Automated Pre-Commit Hook

1# .husky/pre-commit
2yarn generate-routes
3git add src/routeTree.gen.ts
4yarn lint-staged

CI/CD Validation (Recommended)

1# .github/workflows/pr-validation.yml
2- name: Check route tree is up-to-date
3 run: |
4 yarn generate-routes
5 git diff --exit-code src/routeTree.gen.ts || \
6 (echo "Route tree is out of date! Run 'yarn generate-routes' and commit." && exit 1)

Ideal Development Workflow

  1. Create/modify route files in src/routes/
  2. Pre-commit hook runs automatically:
    • Generates route tree
    • Stages routeTree.gen.ts
    • Runs linting
  3. Commit includes both route files and route tree
  4. CI/CD builds with up-to-date configuration
  5. Production works! ✅

Summary

Problem: Root route 404 in production
Cause 1: Stale route tree file in git
Cause 2: Wrong file naming (index.route.tsx vs index.tsx)
Solution 1: Auto-generate routes on pre-commit
Solution 2: Rename to index.tsx
Result: Routes work in production

Debug Time: ~2 hours
Fix Time: 5 minutes
Lesson: File naming conventions and build-time generation are critical!