This package enables you to define your routes using the flat-routes convention.

Overview

Remix Flat Routes

All Contributors

This package enables you to define your routes using the flat-routes convention. This is based on the gist by Ryan Florence

πŸ›  Installation

> npm install remix-flat-routes

βš™οΈ Configuration

Update your remix.config.js file and use the custom routes config option.

const { flatRoutes } = require('remix-flat-routes')
module.exports = {
  // ignore all files in routes folder
  ignoredRouteFiles: ['**/*'],
  routes: async defineRoutes => {
    return flatRoutes('routes', defineRoutes)
  },
}

NOTE: basePath should be relative to the app folder. If you want to use the routes folder, you will need to update the ignoreRouteFiles property to ignore all files: **/*

🚚 Migrating Existing Routes

You can now migrate your existing routes to the new flat-routes convention. Simply run:

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

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

NOTE:
  sourceDir and targetDir are relative to project root

Options:
  --convention=<convention>
    The convention to use when migrating.
      flat-files - Migrates all files to a flat directory structure.
      flat-folders - Migrates all files to a flat directory structure, but
        creates folders for each route.

πŸ”¨ Flat Routes Convention

routes/
  _auth.forgot-password.tsx
  _auth.login.tsx
  _auth.reset-password.tsx
  _auth.signup.tsx
  _auth.tsx
  _landing.about.tsx
  _landing.index.tsx
  _landing.tsx
  app.calendar.$day.tsx
  app.calendar.index.tsx
  app.calendar.tsx
  app.projects.$id.tsx
  app.projects.tsx
  app.tsx
  app_.projects.$id.roadmap.tsx
  app_.projects.$id.roadmap[.pdf].tsx

As React Router routes:

} /> } /> } /> } /> }> } /> } /> }> }> } /> } /> }> } /> } /> ">
<Routes>
  <Route element={<Auth />}>
    <Route path="forgot-password" element={<Forgot />} />
    <Route path="login" element={<Login />} />
    <Route path="reset-password" element={<Reset />} />
    <Route path="signup" element={<Signup />} />
  </Route>
  <Route element={<Landing />}>
    <Route path="about" element={<About />} />
    <Route index element={<Index />} />
  </Route>
  <Route path="app" element={<App />}>
    <Route path="calendar" element={<Calendar />}>
      <Route path=":day" element={<Day />} />
      <Route index element={<CalendarIndex />} />
    </Route>
    <Route path="projects" element={<Projects />}>
      <Route path=":id" element={<Project />} />
    </Route>
  </Route>
  <Route path="app/projects/:id/roadmap" element={<Roadmap />} />
  <Route path="app/projects/:id/roadmap.pdf" />
</Routes>

Individual explanations:

filename url nests inside of...
_auth.forgot-password.tsx /forgot-password _auth.tsx
_auth.login.tsx /login _auth.tsx
_auth.reset-password.tsx /reset-password _auth.tsx
_auth.signup.tsx /signup _auth.tsx
_auth.tsx n/a root.tsx
_landing.about.tsx /about _landing.tsx
_landing.index.tsx / _landing.tsx
_landing.tsx n/a root.tsx
app.calendar.$day.tsx /app/calendar/:day app.calendar.tsx
app.calendar.index.tsx /app/calendar app.calendar.tsx
app.projects.$id.tsx /app/projects/:id app.projects.tsx
app.projects.tsx /app/projects app.tsx
app.tsx /app root.tsx
app_.projects.$id.roadmap.tsx /app/projects/:id/roadmap root.tsx
app_.projects.$id.roadmap[.pdf].tsx /app/projects/:id/roadmap.pdf n/a (resource route)

Conventions

filename convention behavior
privacy.jsx filename normal route
pages.tos.jsx dot with no layout normal route, "." -> "/"
about.jsx filename with children parent layout route
about.contact.jsx dot child route of layout
about.index.jsx index filename index route of layout
about_.company.jsx trailing underscore url segment, no layout
_auth.jsx leading underscore layout nesting, no url segment
_auth.login.jsx leading underscore child of pathless layout route
users.$userId.jsx leading $ URL param
docs.$.jsx bare $ splat route
dashboard.route.jsx route suffix optional, ignored completely
_layout.jsx explict layout file optional, same as parent folder
_route.jsx explict route file optional, same as parent folder
investors/[index].jsx brackets escapes conventional characters

Justification

  • Make it easier to see the routes your app has defined - just pop open "routes/" and they are all right there. Since file systems typically sort folders first, when you have dozens of routes it's hard to see which folders have layouts and which don't today. Now all related routes are sorted together.

  • Decrease refactor/redesign friction - while code editors are pretty good at fixing up imports when you move files around, and Remix has the "~" import alias, it's just generally easier to refactor a code base that doesn't have a bunch of nested folders. Remix will no longer force this.

    Additionally, when redesigning the user interface, it's simpler to adjust the names of files rather than creating/deleting folders and moving routes around to change the way they nest.

  • Help apps migrate to Remix - Existing apps typically don't have a nested route folder structure like today's conventions. Moving to Remix is arduous because you have to deal with all of the imports.

  • Colocation - while the example is exclusively files, they are really just "import paths". So you could make a folder for a route instead and the index file will be imported, allowing all of a route's modules to live along side each other.

For example, these routes:

routes/
  _landing.about.tsx
  _landing.index.tsx
  _landing.tsx
  app.projects.tsx
  app.tsx
  app_.projects.$id.roadmap.tsx

Could be folders holding their own modules inside:

routes/
  _auth/
    _layout.tsx <- explicit layout file (same as _auth.tsx)
  _auth.forgot-password/
    _route.tsx  <- explicit route file (same as _auth.forgot-password.tsx)
  _auth.login/
    index.tsx   <- route files (same as _auth.login.tsx)
  _landing.about/
    index.tsx   <- route file (same as _landing.about.tsx)
    employee-profile-card.tsx
    get-employee-data.server.tsx
    team-photo.jpg
  _landing.index/
    index.tsx   <- route file (same as _landing.index.tsx)
    scroll-experience.tsx
  _landing/
    index.tsx   <- route file (same as _landing.tsx)
    header.tsx
    footer.tsx
  app/
    index.tsx   <- route file (same as app.tsx)
    primary-nav.tsx
    footer.tsx
  app_.projects.$id.roadmap/
    index.tsx   <- route file (same as app_.projects.$id.roadmap.tsx)
    chart.tsx
    update-timeline.server.tsx
  app.projects/
    _layout.tsx <- explicit layout file (sames as app.projects.tsx)
    project-card.tsx
    get-projects.server.tsx
    project-buttons.tsx
  app.projects.$id/
    _route.tsx  <- explicit route file (sames as app.projects.$id.tsx)

This is a bit more opinionated, but I think it's ultimately what most developers would prefer. Each route becomes its own "mini app" with all of it's dependencies together. With the routeIgnorePatterns option it's completely unclear which files are routes and which aren't.

😍 Contributors

Thanks goes to these wonderful people (emoji key):


Kiliman

πŸ’» πŸ“–

Ryan Florence

πŸ“–

This project follows the all-contributors specification. Contributions of any kind welcome!

Comments
  • Form submitting to incorrect routes

    Form submitting to incorrect routes

    After migrating to this library, my forms are submitting to incorrect routes.

    Here's a repro (the README has a lot more detail): https://stackblitz.com/edit/node-kh9dmu?file=README.md

    opened by nakleiderer 14
  • Trailing underscore at params affect useParams

    Trailing underscore at params affect useParams

    Hi, I'm trying out remix-flat-routes and when I use routes with trailing underscore at params, it affects the useParams as well as params in loader / action.

    I have to resort to creating a getParam abstraction but just wondering if there's a good way to solve this within the library itself.


    Just to clarify in case my description is not clear enough, for example, I have these routes:

    /app/$organizationSlug.tsx
    /app/$organizationSlug_/other.tsx` --> useParams() will return { organizationSlug_: "slug" }
    
    opened by alexluong 14
  • Update documentation for index route

    Update documentation for index route

    I think the documentation needs updated for an index route.

    For example, this will break because app.calendar and app.calendar.index refer to the same route:

    app.tsx
    app.calendar.tsx
    app.calendar.index.tsx
    

    But this works as intended:

    app.tsx
    app.calendar.tsx
    app.calendar._index.tsx
    
    opened by barbinbrad 6
  • Issue with layouts conflicting

    Issue with layouts conflicting

    Hey,

    Mentioned this in a discussion but thought it would be best to create a separate issue.

    After upgrading to 0.5.2 from 0.4.8, and using remix 1.9.0, my routes aren't valid apparently anymore

    Here is my file structure, have removed a load to show the problem ones:

    β”œβ”€β”€ admin._index.tsx
    β”œβ”€β”€ admin.artists.$id._profile._index.tsx
    β”œβ”€β”€ admin.artists.$id._profile.episodes
    β”œβ”€β”€ admin.artists.$id._profile.shows.tsx
    β”œβ”€β”€ admin.artists.$id._profile.ts    <-layout
    β”œβ”€β”€ admin.artists.$id.episodes.new.tsx
    β”œβ”€β”€ admin.artists.$id.episodes.tsx    <-layout
    β”œβ”€β”€ admin.artists.$id.shows.$showId._profile._index.tsx
    β”œβ”€β”€ admin.artists.$id.shows.$showId._profile.tsx   <-layout
    β”œβ”€β”€ admin.artists.$id.shows.$showId.tsx    <-layout
    β”œβ”€β”€ admin.artists.$id.shows.tsx
    β”œβ”€β”€ admin.artists.$id.tsx   <-layout
    β”œβ”€β”€ admin.tsx
    
    
    1.
    Error: Path "/admin/artists/:id" defined by route "pages/admin.artists.$id._profile" conflicts with route "pages/admin.artists.$id"
    
    2.
    Error: Path "/admin/artists/:id/shows/:showId" defined by route "pages/admin.artists.$id.shows.$showId._profile" conflicts with route "pages/admin.artists.$id.shows.$showId"
    
    3.
    Error: Path "/admin/artists/:id/episodes" defined by route "pages/admin.artists.$id.episodes" conflicts with route "pages/admin.artists.$id._profile.episodes"
    
    4.
    Error: Path "/admin/artists/:id/shows" defined by route "pages/admin.artists.$id.shows" conflicts with route "pages/admin.artists.$id._profile.shows"
    

    As mentioned this was working before, so wondering what's changed and what I would need to do to fix?

    Thanks!

    opened by JClackett 5
  • Hybrid Routes

    Hybrid Routes

    Based on the conversation on the Remix Flat Routes RFC discussion - is it correct that this library supports "hybrid routes" out of the box? I do not see anything referencing this in the README.

    I was attempting to use a hybrid of a "layout route" directory with normal flat route subdirectories. Does this work out of the box?

    Something like this:

    routes/
      _dashboard/
         _list/
            _indext.tsx
        _update/
            _index.tsx
    
    opened by stephen776 5
  • Add ability to ignore files

    Add ability to ignore files

    I'd like to colocate .css and .test.tsx files with my files in my routes. So I'd like to be able to ignore files as routes. Something like this config?

    module.exports = {
    	ignoredRouteFiles: ['**/*'],
    	routes: async defineRoutes => {
    		return flatRoutes('routes', defineRoutes, {
    			ignoredRouteFiles: ['**/.*', '**/*.css', '**/*.test.{js,jsx,ts,tsx}'],
    		})
    	},
    }
    
    opened by kentcdodds 5
  • Optional dynamic segments aren't allowed

    Optional dynamic segments aren't allowed

    I'm trying to do optional dynamic segments like this: /($foo)

    This spits out an error saying the route prefix must come first.

    Tried putting the $ first and got this for the output route:

    <Route path="reports/:(type)/:id" file="routes/reports_.$(type).$id/_route.tsx" />
    
    opened by brandonpittman 4
  • Conflicts with route when trying to nest routes

    Conflicts with route when trying to nest routes

    When trying to nest a round under a parent route it is returning an error:

    Error: Path "app/calendar" defined by route "routes/app.calendar/index" conflicts with route "routes/app.calendar.index/index"

    version: 0.4.4

    opened by shortnd 4
  • multiple params, no parent

    multiple params, no parent

    Awesome work! I love this.

    I tried it on the revamp the react router docs site and had this route config:

    app/routes
    β”œβ”€β”€ $lang.$ref
    β”‚Β Β  β”œβ”€β”€ $.tsx
    β”‚Β Β  └── index.tsx
    β”œβ”€β”€ $lang.$ref.tsx
    β”œβ”€β”€ healthcheck.tsx
    └── index.tsx
    

    Which creates this route tree with $ remix routes

    <Routes>
      <Route file="root.tsx">
        <Route path="healthcheck" file="routes/healthcheck.tsx" />
        <Route path=":lang/:ref" file="routes/$lang.$ref.tsx">
          <Route index file="routes/$lang.$ref/index.tsx" />
          <Route path="*" file="routes/$lang.$ref/$.tsx" />
        </Route>
        <Route index file="routes/index.tsx" />
      </Route>
    </Routes>
    

    I ran the migration script and it kicked out:

    app/flatroutes
    β”œβ”€β”€ $lang.$ref.$
    β”œβ”€β”€ $lang.$ref.index
    β”œβ”€β”€ $lang_.$ref
    β”œβ”€β”€ healthcheck
    └── index
    

    Which creates this incorrect route tree:

    <Routes>
      <Route file="root.tsx">
        <Route path=":lang/:ref" file="flatroutes/$lang_.$ref/index.tsx" />
        <Route path="healthcheck" file="flatroutes/healthcheck/index.tsx" />
        <Route index file="flatroutes/index/index.tsx" />
      </Route>
    </Routes>
    
    • Note the strange $lang_.$ref
    • Note the missing child routes under :lang/:ref

    I'm realizing my examples never handled the case where a single route defines multiple segments <Route path=":lang/:ref" />, and it looks like the migration script and flatroutes aren't handling it either!

    I changed my files to this, which I think is what the correct output of the migration script should be:

    app/flatroutes
    β”œβ”€β”€ $lang.$ref
    β”œβ”€β”€ $lang.$ref.$
    β”œβ”€β”€ $lang.$ref.index
    β”œβ”€β”€ healthcheck
    └── index
    

    But the route config has an even bigger disappearing act with that πŸ˜Άβ€πŸŒ«οΈ

    <Routes>
      <Route file="root.tsx">
        <Route path="healthcheck" file="flatroutes/healthcheck/index.tsx" />
        <Route index file="flatroutes/index/index.tsx" />
      </Route>
    </Routes>
    

    It should look like this:

    <Routes>
      <Route file="root.tsx">
        <Route path="healthcheck" file="routes/healthcheck.tsx" />
        <Route path=":lang/:ref" file="routes/$lang.$ref.tsx">
          <Route index file="routes/$lang.$ref/index.tsx" />
          <Route path="*" file="routes/$lang.$ref/$.tsx" />
        </Route>
        <Route index file="routes/index.tsx" />
      </Route>
    </Routes>
    

    The tricky bit here is when you see $lang.$ref and don't find a $lang to be the parent, we need to treat $lang.$ref as it's own route without needing a parent.

    Of course, if the app developer adds a $lang then the route config will completely change on them, and I'd consider that expected behavior.

    opened by ryanflorence 4
  • Trouble with optional route segments when in a folder

    Trouble with optional route segments when in a folder

    Having some trouble getting the new optional route segments working when nested in a folder:

    This gives the output expected:

    routes/
      hello.(thing).tsx
      index.tsx
    
    <Routes>
      <Route file="root.tsx">
        <Route path="hello/thing?" file="routes/hello.(thing).tsx" />
        <Route index file="routes/index.tsx" />
      </Route>
    </Routes>
    

    However if nested in a folder we lose that route entirely:

    routes/
      _folder+/
        hello.(thing).tsx
      index.tsx
    
    <Routes>
      <Route file="root.tsx">
        <Route index file="routes/index.tsx" />
      </Route>
    </Routes>
    

    Using remix v1.9.0 + remix-flat-routes v0.5.1

    opened by corygibbons 3
  • Does not respect configured appDirectory

    Does not respect configured appDirectory

    It seems like the appDirectory is hard-coded to app, so if you've changed it (for me it's set to remix/app), you get an error. Would be ideal if remix-flat-routes respected the appDirectory parameter.

    https://github.com/kiliman/remix-flat-routes/blob/1456a5b88b8e91c6288e0c50aeb5a508c1c72303/src/index.ts#L72

    opened by rogerchi 3
Owner
Kiliman
Kiliman
An adapter where you can define which function to run

Switch Functions An adapter where you can define which function to run Installation This is a Node.js module available through the npm registry. Befor

Badass Team 2 Jun 17, 2022
A RESP 'Redis Serialization Protocol' library implementation to generate a server, uses a similar approach to express to define you serer, making it easy and fast.

RESPRESS A RESP 'Redis Serialization Protocol' library implementation to generate a server, uses a similar approach to express to define you serer, ma

Yousef Wadi 9 Aug 29, 2022
This package enables you to mount your Remix app at a different path than root

Remix Mount Routes This package enables you to mount your Remix app at a different path than root. ?? Installation > npm install -D remix-mount-routes

Kiliman 26 Dec 17, 2022
Define tool for JS/TS

JSDef A Define tool for js/ts Install npm i jsdefn HOW TO Create Defs import { JSDef } from 'jsdefn' const $ = JSDef({ "A": "apple" }) Get define

LAZPbanahaker 2 Apr 18, 2022
This is a place to define better practices in code.

Better Practices Categories Angular General Programming GitHub Pages JavaScript Naming Conventions React Influence This guide is heavily influenced by

Bob Fornal 18 Sep 3, 2022
This package is an open source extension for MikroORM, which enables Nested Set Tree for your needs

MikroORM nested set This package is an open source extension for MikroORM, which enables Nested Set Tree for your needs Disclaimer For now, this packa

Kamil Fronczak 5 Dec 15, 2022
Package fetcher is a bot messenger which gather npm packages by uploading either a json file (package.json) or a picture representing package.json. To continue...

package-fetcher Ce projet contient un boilerplate pour un bot messenger et l'executable Windows ngrok qui va permettre de crΓ©er un tunnel https pour c

AILI Fida Aliotti Christino 2 Mar 29, 2022
Remix enables you to build fantastic user experiences for the web and feel happy with the code that got you there. In this workshop, we'll look at some more advanced use cases when building Remix applications.

?? Advanced Remix Workshop Remix enables you to build fantastic user experiences for the web and feel happy with the code that got you there. In this

Frontend Masters 167 Dec 9, 2022
Remix enables you to build fantastic user experiences for the web and feel happy with the code that got you there. Get a jumpstart on Remix with this workshop.

?? Remix Fundamentals Build Better websites with Remix Remix enables you to build fantastic user experiences for the web and feel happy with the code

Frontend Masters 204 Dec 25, 2022
BOOTFLAT is an open source Flat UI KIT based on Bootstrap 3.3.0 CSS framework

BOOTFLAT is an open source Flat UI KIT based on Bootstrap 3.3.0 CSS framework. It provides a faster, easier and less repetitive way for web developers to create elegant web apps.

bootflat 4.3k Dec 25, 2022
Ordered lists, flat or nested, multiple formats ordered lists.

logseq-plugin-ol ζœ‰εΊεˆ—θ‘¨οΌŒε•ηΊ§ζˆ–ε€šηΊ§γ€ε€šη§ζ ·εΌηš„ζœ‰εΊεˆ—θ‘¨γ€‚ Ordered lists, flat or nested, multiple formats ordered lists. 使用展瀺 (Usage) εœ¨ζƒ³θ¦ε±•η€ΊδΈΊζœ‰εΊεˆ—θ‘¨ηš„ε—δΈŠζ·»εŠ δΈ€δΈͺδ»₯ #.ol εΌ€ε€΄ηš„ζ ‡η­Ύε°±ε―δ»₯δΊ†γ€‚ζœ‰

Seth Yuan 25 Jan 1, 2023
Lying flat is a 20 NFT collection on a custom marketplace built on Zora's protocol

Lying flat is an NFT Marketplace powered by ZORA ?? ?? ?? The codebase is open for everyone to use it as a boilerplate, customize it and deploy their

javvvs 17 Sep 20, 2022
Simple, Fast, Secure, Flat-File CMS

Bludit Simple, Fast and Flexible CMS. Bludit is a web application to build your own website or blog in seconds, it's completely free and open source.

BLUDIT 1.1k Dec 30, 2022
Flat and simple color-picker library. No dependencies, no jquery.

Flat and simple color-picker Fully Featured demo Features Simple: The interface is straight forward and easy to use. Practical: Multiple color represe

Ivan Matveev 15 Nov 14, 2022
🎨 Flat, simple, multi-themed, responsive and hackable Color-Picker library.

?? Flat, simple, multi-themed, responsive and hackable Color-Picker library. No dependencies, no jQuery. Compatible with all CSS Frameworks e.g. Bootstrap, Materialize. Supports alpha channel, rgba, hsla, hsva and more!

Simon 3.9k Dec 27, 2022
Simple utils to pack arrays, objects and strings to a flat object (and back again).

packrup Simple utils to pack (and unpack) arrays and strings to a flat object. Status: In Development Please report any issues ?? Made possible by my

Harlan Wilton 15 Dec 23, 2022
🍎 A simple application which enables you to sync readings from your Withings scale to a Notion page.

weight-logger weight-logger is a simple application which enables you to sync readings from your Withings scale to a Notion page. Preview Installation

Juri Adams 2 Jan 14, 2022
SafeCycleβ€”a tool that keeps cyclists safe. Gone are days of weaving through busy city streets, SafeCycle finds all the bike routes for you to ensure a smooth ride wherever you want to go.

Inspiration Bikingβ€”an everyday form of travel for students and professionals across the globe. On-campus, back home, and with the people that we know

Ryan Hu 2 May 2, 2022