remix-flat-routes

repository·main·Indexed 21 days ago

https://github.com/kiliman/remix-flat-routes

A routing utility for Remix and React Router v7 that provides an enhanced version of the flat-routes convention. It supports hybrid routing with nested folders and colocation, extended filenames, and custom configurations for parameters and base paths. Includes a migrate-flat-routes CLI tool to convert existing nested folder structures to flat-files, flat-folders, or hybrid conventions.

Tokens
7K
Snippets
19
Records
34
Agent score
72%

What's inside remix-flat-routes

  1. Use Extended Route Filenames

    main

    In addition to standard names (index, route, page, layout), any file prefixed with an underscore _ is treated as the route file. This allows you to name the file after the route itself for better discoverability within a folder.

    Instead of _public.about/route.tsx, you can use _public.about/_about.tsx.

  2. How Hybrid Routes work

    main

    Hybrid routes allow you to use nested folders for route names while maintaining the colocation benefits of flat routes. This is useful for large apps with deep nesting or shared layouts (like _public or admin).

    Instead of repeating long prefixes in every filename, you can create top-level folders and nest routes within them. This avoids the some.really.long.route.edit/index.tsx pattern found in standard flat routes.

    # Example structure
    app/routes-hybrid
    ├── _public
    │   ├── _layout.tsx
    │   └── about
    │       └── _route.tsx
    └── users
        ├── $userId
        │   └── _route.tsx
        └── _layout.tsx
  3. Use the `flat-files` convention with nested folders

    main

    To treat a folder as a flat file (effectively flattening its contents into the parent's namespace), append the nestedDirectoryChar (default is +) to the folder name.

    Example: _auth+/forgot-password.tsx becomes _auth.forgot-password.tsx.

    You can include _layout.tsx inside these folders, and you do not need to provide separate _public.tsx or users.tsx files.

    # Example of folder with '+' suffix
    _auth+/
    ├── forgot-password.tsx
    └── login.tsx
    
    # Results in routes: /forgot-password and /login under the _auth namespace
  4. Understand the Flat Routes convention

    main

    The flat-routes convention allows you to define Remix/React Router routes using a flat file or folder structure instead of deeply nested directories. This makes the route tree easier to visualize by looking at the routes/ directory and simplifies refactoring.

    Flat Files Convention

    In this mode, routes are defined by filenames using dot notation. Dots represent URL segments or nesting levels.

    • about.tsx -> /about (Layout route)
    • about.contact.tsx -> /about/contact (Child of about.tsx)
    • about.index.tsx -> /about (Index route of about.tsx)
    • _auth.login.tsx -> /login (Pathless layout _auth.tsx provides context but doesn't add a segment to the URL)
    • users.$userId.tsx -> /users/:userId (URL parameter)
    • docs.$.tsx -> /docs/* (Splat route)

    Flat Folders Convention

    In this mode, each route is a folder named after the route, and the actual route component is placed in an index.tsx file within that folder. This allows you to colocate components, styles, and server code with the route.

    Example structure:

    routes/
      app.projects/
        index.tsx       <-- Layout file (app.projects.tsx)
        project-card.tsx
      app.projects.$id/
        index.tsx       <-- Route file (app.projects.$id.tsx)

    To avoid losing the route file in a list of colocated files, you can use these aliases for index.tsx:

    • _index.tsx
    • _layout.tsx
    • _route.tsx
    routes/
      _auth.forgot-password.tsx
      _auth.login.tsx
      _auth.tsx
      app.calendar.$day.tsx
      app.calendar.index.tsx
      app.calendar.tsx
      app.tsx
      app_.projects.$id.roadmap.tsx
      app_.projects.$id.roadmap[.pdf].tsx
  5. Override default parent layout matching

    main

    By default, flat-routes nests a route into the parent layout that has the longest matching prefix. For example, app.calendar.$day.tsx will nest inside app.calendar.tsx because app.calendar is the longest matching prefix.

    To override this and nest a route under a higher-level layout (like root.tsx) instead of its immediate prefix match, append a trailing underscore (_) to the segment that is the immediate child of the route you want to skip.

    Example:

    • app_.projects.$id.roadmap.tsx will not nest under app.tsx or app.projects.tsx. It will nest directly under root.tsx because the app_ segment breaks the prefix match chain.
  6. Customize param prefix, base path, and optional segments

    main

    You can extend the routing capabilities of remix-flat-routes using the following patterns:

    • Custom Param Prefix: Change the default $ to another character (e.g., ^) to avoid shell expansion issues. users.^userId.tsx becomes users/:userId.
    • Custom Base Path: Override the default / to prepend a specific path to all routes.
    • Optional Route Segments: Wrap a route name in parentheses to create an optional segment. parent.(optional).tsx becomes parent/optional?.
  7. Migrate existing routes to flat-routes

    main

    Use the migrate-flat-routes CLI tool to convert your existing Remix nested folder structure to the flat-routes convention.

    npx migrate-flat-routes <sourceDir> <targetDir> [options]

    Options:

    • --convention=<convention>: Specifies the target format:
      • flat-files: Migrates to flat files.
      • flat-folders: Migrates to flat directories with index.tsx files.
      • hybrid: Keeps folder structure but uses + suffix and _layout files.
    • --force: Overwrites the target directory if it already exists.

    Example:

    npx migrate-flat-routes ./app/routes ./app/flatroutes --convention=flat-folders

    Note: sourceDir and targetDir are relative to your project root.

    npx migrate-flat-routes ./app/routes ./app/flatroutes --convention=flat-folders
  8. Migrate to React Router v7 using the Remix Routes Adapter

    main

    React Router v7 uses a new routing configuration. To use your existing Remix file-based routes, install the adapter and wrap your routes.ts configuration with remixRoutesOptionAdapter.

    npm install -D @react-router/remix-routes-option-adapter
    npm install -D remix-flat-routes
    // app/routes.ts
    import { remixRoutesOptionAdapter } from "@react-router/remix-routes-option-adapter";
    import { flatRoutes } from "remix-flat-routes";
    
    export default remixRoutesOptionAdapter((defineRoutes) => {
      return flatRoutes("routes", defineRoutes, {
        ignoredRouteFiles: ['**/.*'], // Ignore dot files (like .DS_Store)
      });
    });
  9. Understand Route Parameter Syntax

    main

    remix-flat-routes converts file-based naming conventions into standard URL patterns. It supports several special segment types:

    File Segment PatternResulting URL SegmentDescription
    $id:idStandard dynamic parameter (using paramPrefixChar).
    $/*Catch-all parameter.
    ($id):id?Optional dynamic parameter.
    (segment)/segment?Optional static segment.
    _layout(skipped)Segments starting with _ are treated as layout segments and do not appear in the URL path.
    [bracketed](stripped)Square brackets are removed from the resulting path segments.
  10. How pathless layout routes work

    main

    Pathless layout routes are identified by filenames starting with a double underscore (__). These routes allow you to wrap child routes in a shared layout without adding a segment to the URL path.

    Key Behaviors:

    • Collision Handling: The library does not check for path collisions for pathless layout routes at the level where they are defined. This allows you to have multiple pathless layouts for the same URL segment if they reside in different subfolders.
    • Example Structure:
      • routes/account/__public/login.tsx -> /account/login (via __public layout)
      • routes/account/__private/orders.tsx -> /account/orders (via __private layout)

    However, collisions will still be detected if two different pathless routes attempt to claim the exact same child path, such as:

    • routes/parent/__pathless/foo.tsx
    • routes/parent/__pathless2/foo.tsx
  11. How the `defineRoutes` callback works

    main

    The defineRoutes function is the core mechanism for building your route tree. It receives a callback that allows you to define routes and their nesting.

    The defineRoute signature: defineRoute(path, file, children?)

    • path: The relative URL path for the route.
    • file: The absolute path to the route file.
    • children (optional): A function used to define nested routes under this parent. If the route is an index route, providing a children function will throw an error.

    Example Pattern:

    flatRoutes('app/routes', (defineRoute) => {
      defineRoute('about', 'app/routes/about.tsx', () => {
        // Nested routes go here
        defineRoute('team', 'app/routes/about/team.tsx');
      });
    });