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.
| 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:
Check route tree commit history: git log src/routeTree.gen.ts
Inspect route tree content for path values
Verify file naming follows conventions
Test production build locally: yarn build && yarn preview
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
Create/modify route files in src/routes/
Pre-commit hook runs automatically:
Generates route tree
Stages routeTree.gen.ts
Runs linting
Commit includes both route files and route tree
CI/CD builds with up-to-date configuration
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!