Skip to content

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

modules-page-routing

Utility library for React Router v7 / v8 - Automatically build routes from file-system with support for modules and API handlers.

Features

  • 🎯 Type-safe - Full TypeScript support with comprehensive type definitions
  • 📁 File-based routing - Automatically build routes from directory structure
  • 🧩 Module-based - Organize code by independent modules
  • 🔌 API routes - Support for building API handlers (global and module-scoped)
  • 📦 ESM & CommonJS - Dual module support for maximum compatibility
  • ⚡ Zero dependencies - Only peer dependencies on React and React Router v7 or v8

Installation

npm install modules-page-routing

Peer Dependencies:

npm install react react-router

Usage

1. Directory Structure

Organize your code using a module-based structure:

app/
├── modules/
│   ├── __root.tsx          # Shared layout for all modules
│   ├── __homepage.tsx      # Homepage (index route)
│   ├── auth/
│   │   └── pages/
│   │       ├── _layout.tsx       # Layout for auth module
│   │       ├── sign-in.tsx       # /auth/sign-in
│   │       ├── sign-up.tsx       # /auth/sign-up
│   │       └── forgot-password.tsx
│   ├── admin/
│   │   └── pages/
│   │       ├── _layout.tsx       # Layout for admin module
│   │       ├── index.tsx         # /admin (index route)
│   │       ├── users/
│   │       │   ├── index.tsx     # /admin/users
│   │       │   └── [id].tsx      # /admin/users/:id
│   │       └── settings.tsx      # /admin/settings
│   └── shop/
│       ├── api/
│       │   ├── products.ts            # API: /api/shop/products
│       │   └── products.$id.ts        # API: /api/shop/products/:id
│       └── pages/
│           ├── index.tsx          # /shop
│           ├── products/
│           │   ├── index.tsx      # /shop/products
│           │   └── [id].tsx       # /shop/products/:id
│           └── cart.tsx           # /shop/cart
├── api/
│   └── v1/
│       ├── users.ts               # API: /api/v1/users
│       ├── users.$id.ts           # API: /api/v1/users/:id
│       └── products.search.ts     # API: /api/v1/products/search
└── routes.ts

2. routes.ts File

import {
  index,
  layout,
  prefix,
  type RouteConfig,
} from '@react-router/dev/routes';
import { flatRoutes } from '@react-router/fs-routes';
import {
  buildApiRouteConfig,
  buildApiModuleRouteConfig,
  buildGlobRouteConfig,
  type GlobModules,
} from 'modules-page-routing';

// Import all files in modules/*/pages/*, modules/*/api/*, and api/**/*
const globTree = import.meta.glob('./modules/**/pages/**/*.{tsx,ts}');
const apiTree = import.meta.glob('./api/**/*.ts');
const apiModuleTree = import.meta.glob('./modules/**/api/**/*.ts');

// Build React Router RouteConfig from glob
const moduleRoutes = buildGlobRouteConfig(globTree as GlobModules);
const apiRoutes = buildApiRouteConfig(apiTree);
const apiModuleRoutes = buildApiModuleRouteConfig(apiModuleTree);

const routes = [
  // All module pages are nested under a shared layout
  layout('modules/__root.tsx', [
    index('modules/__homepage.tsx'),
    ...moduleRoutes,
  ]),

  // API routes: global (/api/v1/...) + module-scoped (/api/<module>/...)
  ...prefix('api', [...apiRoutes, ...apiModuleRoutes]),

  // Routes from app/routes/** (optional - if you still want traditional file-based routing)
  ...(await flatRoutes({
    rootDirectory: 'routes',
  })),
];

export default routes satisfies RouteConfig;

3. File Naming Conventions

Module Pages

  • index.tsx → Index route of the module/folder

    • modules/admin/pages/index.tsx → /admin
    • modules/admin/pages/users/index.tsx → /admin/users
  • _layout.tsx → Layout wrapper for child routes

    • modules/admin/pages/_layout.tsx → Layout for all routes in /admin/*
    • modules/admin/pages/users/_layout.tsx → Layout for /admin/users/*
  • [param].tsx or $param.tsx → Dynamic route parameter (works for folders too)

    • modules/admin/pages/users/[id].tsx → /admin/users/:id
    • modules/admin/pages/users/$id.tsx → /admin/users/:id (native React Router syntax)
    • modules/shop/pages/products/$slug/reviews.tsx → /shop/products/:slug/reviews
  • [...rest].tsx or $.tsx → Catch-all/splat route

    • modules/docs/pages/[...path].tsx → /docs/*
    • modules/docs/pages/$.tsx → /docs/*
  • _not-found.tsx → 404 page for the module

    • modules/admin/pages/_not-found.tsx → 404 for /admin/*
  • _error.tsx → Error boundary for the module (used by buildGlobRoutes runtime)

    • modules/admin/pages/_error.tsx → Error boundary for /admin/*
    • modules/admin/pages/users/_error.tsx → Error boundary for /admin/users/*
  • _loading.tsx → Loading fallback for the module (replaces default Suspense fallback)

    • modules/admin/pages/_loading.tsx → Loading UI for /admin/*
    • modules/admin/pages/users/_loading.tsx → Loading UI for /admin/users/*
  • _hydrate-fallback.tsx → SSR hydration fallback (used by buildGlobRoutes runtime, maps to React Router's hydrateFallbackElement)

    • modules/admin/pages/_hydrate-fallback.tsx → Hydration UI for /admin/*
    • modules/admin/pages/users/_hydrate-fallback.tsx → Hydration UI for /admin/users/*
  • _<folder> or _<file>.tsx → Parentless route (escapes parent layout)

    • modules/auth/pages/_login/index.tsx → /auth/login (without auth layout)
    • modules/admin/pages/_fullscreen-editor.tsx → /admin/fullscreen-editor (without admin layout)
    • Useful for modal pages, login overlays, or pages that need a different layout
  • (group) → Route group (organizational only, no URL segment)

    • modules/admin/pages/(dashboard)/overview.tsx → /admin/overview
    • modules/admin/pages/(settings)/profile.tsx → /admin/profile
    • Supports _layout.tsx inside route groups for shared layouts within the group
    • Example structure:
      admin/pages/
      ├── (dashboard)/
      │   ├── _layout.tsx    # Layout for dashboard group
      │   ├── index.tsx      # /admin
      │   └── analytics.tsx  # /admin/analytics
      └── (settings)/
          ├── _layout.tsx    # Layout for settings group
          ├── profile.tsx    # /admin/profile
          └── security.tsx   # /admin/security
      

API Routes

API routes use a different convention:

  • filename.ts → Single segment

    • api/v1/users.ts → /api/v1/users
  • filename.segment.ts → Multiple segments (using .)

    • api/v1/products.search.ts → /api/v1/products/search
    • api/v1/orders.export.csv.ts → /api/v1/orders/export/csv
  • filename.$param.ts → Dynamic parameter (using $ prefix)

    • api/v1/users.$id.ts → /api/v1/users/:id
    • api/v1/posts.$slug.comments.ts → /api/v1/posts/:slug/comments
    • api/v1/users/$id/posts.ts → /api/v1/users/:id/posts (folders too)
  • filename.$.ts → Catch-all/splat

    • api/v1/files.$.ts → /api/v1/files/*

Module-scoped API Routes

API handlers can also live inside each module's api/ directory, co-located with pages:

  • modules/auth/api/login.ts → /api/auth/login
  • modules/shop/api/products.ts → /api/shop/products
  • modules/shop/api/products.$id.ts → /api/shop/products/:id
  • modules/admin/api/v1/users.ts → /api/admin/v1/users

Same naming conventions apply (. for segments, $ for params, _ for parentless, (group) for groups).

4. Component Examples

Module Layout (modules/admin/pages/_layout.tsx)

import { Outlet } from 'react-router';

export default function AdminLayout() {
  return (
    <div className='admin-layout'>
      <aside>
        <nav>
          <a href='/admin'>Dashboard</a>
          <a href='/admin/users'>Users</a>
          <a href='/admin/settings'>Settings</a>
        </nav>
      </aside>
      <main>
        <Outlet /> {/* Render nested routes */}
      </main>
    </div>
  );
}

Module Page (modules/admin/pages/users/[id].tsx)

import { useParams } from 'react-router';

export default function UserDetail() {
  const { id } = useParams();

  return (
    <div>
      <h1>User Detail: {id}</h1>
      {/* Your component code */}
    </div>
  );
}

API Handler (api/v1/users.$id.ts)

import type { LoaderFunctionArgs } from 'react-router';

export async function loader({ params }: LoaderFunctionArgs) {
  const userId = params.id;

  // Fetch user data
  const user = await fetchUser(userId);

  return Response.json(user);
}

export async function action({ request, params }: LoaderFunctionArgs) {
  const userId = params.id;
  const data = await request.json();

  // Update user
  await updateUser(userId, data);

  return Response.json({ success: true });
}

API Reference

buildGlobRouteConfig(globModules: GlobModules): RouteConfigNode[]

Builds React Router RouteConfig from Vite import.meta.glob().

Parameters:

  • globModules: Object returned from import.meta.glob('./modules/**/pages/**/*.{tsx,ts}')

Returns: Array of RouteConfigNode to use with React Router config

Conventions:

  • Files in modules/<module>/pages/**/* → URL /<module>/**/*
  • _layout.tsx → Layout wrapper with <Outlet />
  • index.tsx → Index route
  • [param] or $param → Dynamic parameter
  • [...rest] or $ → Catch-all route
  • _not-found.tsx → 404 handler
  • _<folder> or _<file>.tsx → Parentless route (escapes parent layout)

buildApiRouteConfig(globModules: Record<string, unknown>): RouteConfigNode[]

Builds API route config from a global api/ directory.

Parameters:

  • globModules: Object returned from import.meta.glob('./api/**/*.ts')

Returns: Array of RouteConfigNode to use with prefix('api', ...)

Conventions:

  • api/v1/users.ts → /api/v1/users
  • api/v1/users.$id.ts → /api/v1/users/:id
  • api/v1/products.search.ts → /api/v1/products/search
  • . in filename → additional path segment
  • $ prefix (filename segment or folder) → dynamic parameter; bare $ → catch-all
  • _ prefix in folder/filename → Parentless route (stripped from URL)

buildApiModuleRouteConfig(globModules: Record<string, unknown>): RouteConfigNode[]

Builds API route config from module-scoped api/ directories. Each module can co-locate its API handlers alongside its pages.

Parameters:

  • globModules: Object returned from import.meta.glob('./modules/**/api/**/*.ts')

Returns: Array of RouteConfigNode to use with prefix('api', ...)

Mapping: modules/<module>/api/<rest> → /api/<module>/<rest>

Examples:

  • modules/auth/api/login.ts → /api/auth/login
  • modules/admin/api/users.$id.ts → /api/admin/users/:id
  • modules/shop/api/orders.export.ts → /api/shop/orders/export

Conventions: Same as buildApiRouteConfig — . for segments, $ for params, _ for parentless, (group) for route groups.

CLI

Visualize and validate your route tree from the terminal.

Route Tree

npx modules-page-routing routes --dir ./app
Page Routes

  /admin                             modules/admin/pages/_layout.tsx
  ├── (index)                        modules/admin/pages/index.tsx
  ├── /users                         modules/admin/pages/users/index.tsx
  ├── /users/:id                     modules/admin/pages/users/[id].tsx
  ├── /settings                      modules/admin/pages/settings.tsx
  └── /*                             modules/admin/pages/_not-found.tsx
  /auth                              modules/auth/pages/_layout.tsx
  ├── /sign-in                       modules/auth/pages/sign-in.tsx
  └── /sign-up                       modules/auth/pages/sign-up.tsx

API Routes

  /api/shop/products                 modules/shop/api/products.ts
  /api/shop/products/:id             modules/shop/api/products.$id.ts

Use --json for machine-readable output.

Validate

npx modules-page-routing validate --dir ./app

Checks for:

Rule Severity Description
duplicate-path error Two files resolve to the same URL
dynamic-conflict error [id].tsx and [slug].tsx (or $slug.tsx) in the same directory
catchall-shadow warn Catch-all * route may shadow siblings
orphan-special-file warn _error.tsx/_loading.tsx without _layout.tsx
empty-module warn Module has no page files
missing-index warn Has _layout.tsx but no index.tsx

Exits with code 1 if any errors are found — useful in CI.

TypeScript Support

This library is written entirely in TypeScript with full type definitions:

import type { GlobModules, RouteConfigNode } from 'modules-page-routing';

Benefits

✅ Automatic - No need to manually define routes, just create files following conventions

✅ Module-based - Organize code by features/modules, easy to scale

✅ Type-safe - Full TypeScript support

✅ Flexible - Can be combined with traditional file-based routing

✅ Nested layouts - Support for nested layouts with _layout.tsx

✅ API routes - Build both UI routes and API endpoints the same way

Real-world Example

See complete examples at: https://github.com/sonicname/modules-page-routing/tree/development/example

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request or create an Issue on GitHub.

Repository

https://github.com/sonicname/modules-page-routing

If you encounter any issues or have questions, please create an issue on the GitHub repository.

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages