🧩 Organizing Routes with Page-Based Colocation in React Router (Framework Mode)
Introduction
I recently built a fullstack restaurant reservation web app using React Router framework mode. One of the most important design decisions was how to organize the project’s file structure.
React Router allows colocating UI and backend logic per route. I took this idea further by grouping everything related to a single page — components, types, constants, server logic — under one route folder.
This article shows how that structure looks and why it worked well in practice.
🔗 Live Demo: https://my-booking.tech/
📂 GitHub Repo: https://github.com/YuichiNabeshima/my-booking
👉 Previous article: https://dev.to/yuichi_nabeshima_canada/building-a-full-stack-web-app-only-with-react-router-1fah
What Is Colocation?
Colocation means putting related files close together.
Instead of separating files by type (like all components in one folder, all hooks in another), we group files by feature or page. So everything for /top
, for example, lives under routes/top/
.
This helps keep responsibilities clear and makes the codebase easier to navigate.
Example Directory Structure (with comments)
Here’s an example file structure for a typical page like /top
.
(I’m using top/
here just as a placeholder to keep things simple.)
.
└── app/
├── routes/
│ ├── top/ # Route folder for "/top" page
│ │ ├── .server/ # Server-only logic (e.g. DB access, API calls)
│ │ ├── components/ # Page-local UI components
│ │ │ ├── Page.tsx # Top-level page component rendered by route.tsx
│ │ │ └── articleList/ # Nested component group for article list
│ │ │ ├── ArticleList.tsx
│ │ │ ├── useArticles.ts
│ │ │ └── article/
│ │ │ └── Article.tsx
│ │ ├── constants/ # Page-specific constants
│ │ ├── schemas/ # Zod schemas for validation
│ │ ├── types/ # Type definitions (e.g. props)
│ │ ├── utils/ # Pure helper functions
│ │ └── route.tsx # Main route file: meta, loader, action
│ └── about/
├── .server/ # Global server logic
├── components/ # Shared UI components
├── constants/ # Global constants
├── types/ # Global types
└── utils/ # Global utilities
How Routing Is Set Up
React Router maps URL paths to files in the routes/
directory. Here's a basic route config:
export default [
index('routes/top/route.tsx'),
route('about', 'routes/about/route.tsx'),
];
Each route.tsx
file typically exports the following:
meta()
— Sets the
and descriptionloader()
— Fetches server dataaction()
— Handles POST or form requestsA default React component — usually rendering
Page.tsx
// routes/top/route.tsx
export function meta() {
return [
{ title: 'Top Page' },
{ name: 'description', content: 'Main landing page' },
];
}
export async function loader() {
// Fetch server-side data
}
export async function action() {
// Handle form submissions
}
export default function Route() {
return <Page />;
}
My Implementation Strategy
When building out pages, I colocate everything related to that route inside the same folder: components, hooks, constants, and even backend logic.
Here’s the principle I follow:
“Colocate first. Extract later.”
That means:
If something is only used by one page, it stays in that page’s folder.
If I realize it’s reusable, I promote it to a shared folder like
/components
or/utils
.
This helps me avoid overengineering and lets patterns emerge naturally as the app grows.
Benefits and Tradeoffs
✅ Benefits
Easy to understand — All code for a feature is in one place
Simple to scale — You don’t need complex abstractions early
Great for onboarding — New developers can find everything quickly
⚠ Tradeoffs
Some duplication early on
Requires discipline to refactor and extract shared code
But in my experience, these tradeoffs are worth it. The simplicity and clarity really help on real-world teams.
Conclusion
Using page-based colocation with React Router made development more straightforward and intuitive for me. It helps create a clean, scalable, and understandable project layout.
If you're using React Router for fullstack development, I recommend trying this approach in your next project.
👉 See the full source code on GitHub
Thanks for reading!