Skip to content

Instantly share code, notes, and snippets.

@ryanflorence
Last active May 20, 2024 19:27
Show Gist options
  • Save ryanflorence/0dcc52c2332c2f6e1b52925195f87baf to your computer and use it in GitHub Desktop.
Save ryanflorence/0dcc52c2332c2f6e1b52925195f87baf to your computer and use it in GitHub Desktop.

New File System Route Conventions

Our current route conventions grew organically and then Jamie hit us with some solid ideas as a catalyst for this. We feel like we have a better idea of what folks need out of the convention especially as React Router apps are being migrated to Remix.

Goals

There are several goals with these changes:

  • Make it easier to see the routes your app has defined
  • Allow co-location of code with routes
  • Decrease refactor/redesign friction
  • Help apps migrate to Remix

Example

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
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/
  _landing.about/
    index.tsx
    employee-profile-card.tsx
    get-employee-data.server.tsx
    team-photo.jpg
  _landing.index/
    index.tsx
    scroll-experience.tsx
  _landing/
    index.tsx
    header.tsx
    footer.tsx
  app.projects/
    project-card.tsx
    get-projects.server.tsx
    project-buttons.tsx
  app/
    index.tsx
    primary-nav.tsx
    footer.tsx
  app_.projects.$id.roadmap/
    index.tsx
    chart.tsx
    update-timeline.server.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.

Migration Path

You can actually just use remix.config.js right now if somebody writes the code:

const flatRoutes = require("remix-flat-routes");
exports.routes = defineRoutes => {
  // can't use `routes` folder right now, so point to a different folder
  flatRoutes("screens", defineRoutes);
}
@kiliman
Copy link

kiliman commented Apr 18, 2022

I've got a working version of the flatRoutes convention, although still WIP 🚧.

image

<Routes>
  <Route file="root.tsx">
    <Route file="screens/_auth.tsx">
      <Route path="forgot-password" file="screens/_auth.forgot-password.tsx" />
      <Route path="login" file="screens/_auth.login.tsx" />
      <Route path="reset-password" file="screens/_auth.reset-password.tsx" />
      <Route path="signup" file="screens/_auth.signup.tsx" />
    </Route>
    <Route file="screens/_landing.tsx">
      <Route path="about" file="screens/_landing.about.tsx" />
      <Route index file="screens/_landing.index.tsx" />
    </Route>
    <Route path="app" file="screens/app.tsx">
      <Route path="calendar" file="screens/app.calendar.tsx">
        <Route path=":day" file="screens/app.calendar.$day.tsx" />
        <Route index file="screens/app.calendar.index.tsx" />
      </Route>
      <Route path="projects" file="screens/app.projects.tsx">
        <Route path=":id" file="screens/app.projects.$id.tsx" />
      </Route>
    </Route>
    <Route path="app/projects/:id/roadmap" file="screens/app_.projects.$id.roadmap.tsx" />
    <Route path="app/projects/:id/roadmap.pdf" file="screens/app_.projects.$id.roadmap[.pdf].tsx" />
  </Route>
</Routes>

I even support the nested routes convention where it only processes index.tsx files, so you can nest components, client/server modules, and css/images without them being treated as routes. Although I need to work on the layout files for this convention.

image

<Routes>
  <Route file="root.tsx">
    <Route file="nestedroutes/_landing.tsx">
      <Route path="about" index file="nestedroutes/_landing.about/index.tsx" />
      <Route index file="nestedroutes/_landing.index/index.tsx" />
    </Route>
    <Route path="app" file="nestedroutes/app.tsx">
      <Route path="projects" file="nestedroutes/app.projects.tsx" />
    </Route>
    <Route path="app/projects/:id/roadmap" index file="nestedroutes/app_.projects.$id.roadmap/index.tsx" />
  </Route>
</Routes>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment