🧩 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 description

  • loader() — Fetches server data

  • action() — Handles POST or form requests

  • A 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!