├── app
├── routes
│ ├── login.tsx
│ ├── about-us.tsx
│ ├── contact.tsx
│ ├── home.tsx
│ ├── logout.tsx
│ ├── signup.tsx
│ ├── calendar.tsx
│ ├── project-new.tsx
│ ├── calendar-day.tsx
│ ├── project-edit.tsx
│ ├── project-print.tsx
│ ├── project-details.tsx
│ ├── project-settings.tsx
│ ├── dashboard-home.tsx
│ ├── project-collaborators.tsx
│ └── project-task.tsx
├── layouts
│ ├── auth.tsx
│ ├── public.tsx
│ ├── calendar.tsx
│ ├── projects.tsx
│ └── project-details.tsx
├── tailwind.css
├── routes.ts
└── root.tsx
├── .gitignore
├── public
└── favicon.ico
├── screenshots
├── complaints
│ ├── too-far.png
│ ├── a-step-backwards.png
│ ├── discord-question.png
│ ├── familiarity-bias.png
│ ├── too-far-response.png
│ ├── why-file-based-routing.png
│ └── why-file-based-routing-answer.png
└── praise
│ ├── route-helper.png
│ ├── bholmes-praise-1.png
│ ├── bholmes-praise-2.png
│ └── route-helper-inspiration.png
├── vite.config.ts
├── react-router.config.ts
├── trees
├── v3-flat.txt
├── v3.txt
├── v1.txt
├── v2.txt
├── routes.rb
├── nextjs.txt
└── nextjs-conventions.txt
├── tsconfig.json
├── package.json
└── README.md
/app/routes/login.tsx:
--------------------------------------------------------------------------------
1 | export default function Page() {
2 | return
Login
;
3 | }
4 |
--------------------------------------------------------------------------------
/app/routes/about-us.tsx:
--------------------------------------------------------------------------------
1 | export default function Page() {
2 | return About us
;
3 | }
4 |
--------------------------------------------------------------------------------
/app/routes/contact.tsx:
--------------------------------------------------------------------------------
1 | export default function Page() {
2 | return Contact
;
3 | }
4 |
--------------------------------------------------------------------------------
/app/routes/home.tsx:
--------------------------------------------------------------------------------
1 | export default function Page() {
2 | return Public home
;
3 | }
4 |
--------------------------------------------------------------------------------
/app/routes/logout.tsx:
--------------------------------------------------------------------------------
1 | export default function Page() {
2 | return Logout
;
3 | }
4 |
--------------------------------------------------------------------------------
/app/routes/signup.tsx:
--------------------------------------------------------------------------------
1 | export default function Page() {
2 | return Signup
;
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
3 | build/
4 | .react-router/
5 | .env
6 | .DS_Store
7 |
8 | .cursor/
--------------------------------------------------------------------------------
/app/routes/calendar.tsx:
--------------------------------------------------------------------------------
1 | export default function Page() {
2 | return Calendar home
;
3 | }
4 |
--------------------------------------------------------------------------------
/app/routes/project-new.tsx:
--------------------------------------------------------------------------------
1 | export default function Page() {
2 | return New project
;
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/screenshots/complaints/too-far.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/complaints/too-far.png
--------------------------------------------------------------------------------
/screenshots/praise/route-helper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/praise/route-helper.png
--------------------------------------------------------------------------------
/screenshots/praise/bholmes-praise-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/praise/bholmes-praise-1.png
--------------------------------------------------------------------------------
/screenshots/praise/bholmes-praise-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/praise/bholmes-praise-2.png
--------------------------------------------------------------------------------
/screenshots/complaints/a-step-backwards.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/complaints/a-step-backwards.png
--------------------------------------------------------------------------------
/screenshots/complaints/discord-question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/complaints/discord-question.png
--------------------------------------------------------------------------------
/screenshots/complaints/familiarity-bias.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/complaints/familiarity-bias.png
--------------------------------------------------------------------------------
/screenshots/complaints/too-far-response.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/complaints/too-far-response.png
--------------------------------------------------------------------------------
/screenshots/praise/route-helper-inspiration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/praise/route-helper-inspiration.png
--------------------------------------------------------------------------------
/screenshots/complaints/why-file-based-routing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/complaints/why-file-based-routing.png
--------------------------------------------------------------------------------
/screenshots/complaints/why-file-based-routing-answer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brookslybrand/route-convention-evolution/HEAD/screenshots/complaints/why-file-based-routing-answer.png
--------------------------------------------------------------------------------
/app/routes/calendar-day.tsx:
--------------------------------------------------------------------------------
1 | import type { Route } from "./+types/calendar-day";
2 |
3 | export default function Page({ params }: Route.ComponentProps) {
4 | return Calendar day {params.day}
;
5 | }
6 |
--------------------------------------------------------------------------------
/app/routes/project-edit.tsx:
--------------------------------------------------------------------------------
1 | import type { Route } from "./+types/project-edit";
2 |
3 | export default function Page({ params }: Route.ComponentProps) {
4 | return Edit project {params.projectId}
;
5 | }
6 |
--------------------------------------------------------------------------------
/app/layouts/auth.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router";
2 |
3 | export default function Page() {
4 | return (
5 | <>
6 | Auth layout
7 |
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/routes/project-print.tsx:
--------------------------------------------------------------------------------
1 | import type { Route } from "./+types/project-print";
2 |
3 | export default function Page({ params }: Route.ComponentProps) {
4 | return Print project {params.projectId}
;
5 | }
6 |
--------------------------------------------------------------------------------
/app/layouts/public.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router";
2 |
3 | export default function Page() {
4 | return (
5 | <>
6 | Public layout
7 |
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/routes/project-details.tsx:
--------------------------------------------------------------------------------
1 | import type { Route } from "./+types/project-details";
2 |
3 | export default function Page({ params }: Route.ComponentProps) {
4 | return Project details {params.projectId}
;
5 | }
6 |
--------------------------------------------------------------------------------
/app/layouts/calendar.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router";
2 |
3 | export default function Page() {
4 | return (
5 | <>
6 | Calendar layout
7 |
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/layouts/projects.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router";
2 |
3 | export default function Page() {
4 | return (
5 | <>
6 | Projects layout
7 |
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/routes/project-settings.tsx:
--------------------------------------------------------------------------------
1 | import type { Route } from "./+types/project-settings";
2 |
3 | export default function Page({ params }: Route.ComponentProps) {
4 | return Project {params.projectId} settings
;
5 | }
6 |
--------------------------------------------------------------------------------
/app/routes/dashboard-home.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router";
2 |
3 | export default function Page() {
4 | return (
5 |
6 |
Dashboard
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/layouts/project-details.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router";
2 |
3 | export default function Page() {
4 | return (
5 | <>
6 | Project details layout
7 |
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/routes/project-collaborators.tsx:
--------------------------------------------------------------------------------
1 | import type { Route } from "./+types/project-collaborators";
2 |
3 | export default function Page({ params }: Route.ComponentProps) {
4 | return Project {params.projectId} collaborators
;
5 | }
6 |
--------------------------------------------------------------------------------
/app/routes/project-task.tsx:
--------------------------------------------------------------------------------
1 | import type { Route } from "./+types/project-task";
2 |
3 | export default function Page({ params }: Route.ComponentProps) {
4 | return (
5 |
6 | Project {params.projectId} task {params.taskId}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { reactRouter } from "@react-router/dev/vite";
2 | import { defineConfig } from "vite";
3 | import tsconfigPaths from "vite-tsconfig-paths";
4 | import tailwindcss from "@tailwindcss/vite";
5 |
6 | export default defineConfig({
7 | optimizeDeps: {
8 | include: ["react", "react-dom"],
9 | },
10 | plugins: [tailwindcss(), tsconfigPaths(), reactRouter()],
11 | });
12 |
--------------------------------------------------------------------------------
/app/tailwind.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | @custom-variant dark (&:where(.dark, .dark *));
4 |
5 | @theme {
6 | --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif,
7 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
8 | }
9 |
10 | html,
11 | body {
12 | @apply bg-white dark:bg-gray-950;
13 |
14 | @media (prefers-color-scheme: dark) {
15 | color-scheme: dark;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/react-router.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "@react-router/dev/config";
2 |
3 | declare module "react-router" {
4 | interface Future {
5 | unstable_middleware: true;
6 | }
7 | }
8 |
9 | export default {
10 | // Config options...
11 | // Server-side render by default, to enable SPA mode set this to `false`
12 | ssr: true,
13 | future: {
14 | unstable_splitRouteModules: "enforce",
15 | unstable_optimizeDeps: true,
16 | unstable_viteEnvironmentApi: true,
17 | unstable_middleware: true,
18 | unstable_subResourceIntegrity: true,
19 | },
20 | } satisfies Config;
21 |
--------------------------------------------------------------------------------
/trees/v3-flat.txt:
--------------------------------------------------------------------------------
1 | app
2 | ├── app.css
3 | ├── data.ts
4 | ├── layouts
5 | │ ├── auth.tsx
6 | │ ├── calendar.tsx
7 | │ ├── project-details.tsx
8 | │ ├── projects.tsx
9 | │ └── public.tsx
10 | ├── routes
11 | │ ├── about-us.tsx
12 | │ ├── calendar-day.tsx
13 | │ ├── calendar.tsx
14 | │ ├── contact.tsx
15 | │ ├── dashboard-home.tsx
16 | │ ├── home.tsx
17 | │ ├── login.tsx
18 | │ ├── logout.tsx
19 | │ ├── project-collaborators.tsx
20 | │ ├── project-details.tsx
21 | │ ├── project-edit.tsx
22 | │ ├── project-new.tsx
23 | │ ├── project-print.tsx
24 | │ ├── project-settings.tsx
25 | │ ├── project-task.tsx
26 | │ └── signup.tsx
27 | ├── root.tsx
28 | └── routes.ts
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "**/*",
4 | "**/.server/**/*",
5 | "**/.client/**/*",
6 | ".react-router/types/**/*"
7 | ],
8 | "compilerOptions": {
9 | "lib": ["DOM", "DOM.Iterable", "ES2022"],
10 | "types": ["node", "vite/client"],
11 | "target": "ES2022",
12 | "module": "ES2022",
13 | "moduleResolution": "bundler",
14 | "jsx": "react-jsx",
15 | "rootDirs": [".", "./.react-router/types"],
16 | "baseUrl": ".",
17 | "paths": {
18 | "~/*": ["./app/*"]
19 | },
20 | "esModuleInterop": true,
21 | "verbatimModuleSyntax": true,
22 | "noEmit": true,
23 | "resolveJsonModule": true,
24 | "skipLibCheck": true,
25 | "strict": true
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/trees/v3.txt:
--------------------------------------------------------------------------------
1 | app/
2 | ├── layouts
3 | │ ├── auth.tsx
4 | │ ├── calendar.tsx
5 | │ ├── project-details.tsx
6 | │ ├── projects.tsx
7 | │ └── public.tsx
8 | ├── routes/
9 | │ ├── about-us.tsx
10 | │ ├── auth-layout.tsx
11 | │ ├── calendar-day.tsx
12 | │ ├── calendar-layout.tsx
13 | │ ├── calendar.tsx
14 | │ ├── contact.tsx
15 | │ ├── dashboard-home.tsx
16 | │ ├── home.tsx
17 | │ ├── login.tsx
18 | │ ├── logout.tsx
19 | │ ├── project-collaborators.tsx
20 | │ ├── project-details-layout.tsx
21 | │ ├── project-details.tsx
22 | │ ├── project-edit.tsx
23 | │ ├── project-new.tsx
24 | │ ├── project-print.tsx
25 | │ ├── project-settings.tsx
26 | │ ├── project-task.tsx
27 | │ ├── projects-layout.tsx
28 | │ ├── public-layout.tsx
29 | │ └── signup.tsx
30 | └── root.tsx
31 |
--------------------------------------------------------------------------------
/trees/v1.txt:
--------------------------------------------------------------------------------
1 | app/
2 | ├── routes/
3 | │ ├── __auth/
4 | │ │ ├── login.tsx
5 | │ │ ├── logout.tsx
6 | │ │ └── signup.tsx
7 | │ ├── __public/
8 | │ │ ├── about-us.tsx
9 | │ │ ├── contact.tsx
10 | │ │ └── index.tsx
11 | │ ├── dashboard/
12 | │ │ ├── calendar/
13 | │ │ │ ├── $day.tsx
14 | │ │ │ └── index.tsx
15 | │ │ ├── projects/
16 | │ │ │ ├── $projectId/
17 | │ │ │ │ ├── collaborators.tsx
18 | │ │ │ │ ├── edit.tsx
19 | │ │ │ │ ├── index.tsx
20 | │ │ │ │ ├── settings.tsx
21 | │ │ │ │ └── tasks.$taskId.tsx
22 | │ │ │ ├── $projectId.tsx
23 | │ │ │ └── new.tsx
24 | │ │ ├── calendar.tsx
25 | │ │ ├── index.tsx
26 | │ │ └── projects.tsx
27 | │ ├── __auth.tsx
28 | │ ├── __public.tsx
29 | │ └── dashboard.projects.$projectId.print.tsx
30 | └── root.tsx
--------------------------------------------------------------------------------
/trees/v2.txt:
--------------------------------------------------------------------------------
1 | app/
2 | ├── routes/
3 | │ ├── _auth.login.tsx
4 | │ ├── _auth.logout.tsx
5 | │ ├── _auth.signup.tsx
6 | │ ├── _auth.tsx
7 | │ ├── _public._index.tsx
8 | │ ├── _public.about-us.tsx
9 | │ ├── _public.contact.tsx
10 | │ ├── _public.tsx
11 | │ ├── dashboard._index.tsx
12 | │ ├── dashboard.calendar._index.tsx
13 | │ ├── dashboard.calendar.$day.tsx
14 | │ ├── dashboard.calendar.tsx
15 | │ ├── dashboard.projects.$projectId._index.tsx
16 | │ ├── dashboard.projects.$projectId.collaborators.tsx
17 | │ ├── dashboard.projects.$projectId.edit.tsx
18 | │ ├── dashboard.projects.$projectId.settings.tsx
19 | │ ├── dashboard.projects.$projectId.tasks.$taskId.tsx
20 | │ ├── dashboard.projects.$projectId.tsx
21 | │ ├── dashboard.projects.new.tsx
22 | │ ├── dashboard.projects.tsx
23 | │ └── dashboard_.projects.$projectId.print.tsx
24 | └── root.tsx
--------------------------------------------------------------------------------
/trees/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | scope :auth do
3 | get 'login', to: 'auth#login'
4 | get 'logout', to: 'auth#logout'
5 | get 'signup', to: 'auth#signup'
6 | end
7 |
8 | scope :public do
9 | get 'about-us', to: 'public#about_us'
10 | get 'contact', to: 'public#contact'
11 | root to: 'public#index'
12 | end
13 |
14 | scope :dashboard do
15 | resources :calendar, only: [:index] do
16 | get ':day', to: 'calendar#day', on: :collection
17 | end
18 |
19 | resources :projects, only: [:index, :new] do
20 | member do
21 | get 'collaborators'
22 | get 'edit'
23 | get 'settings'
24 | get 'print'
25 | end
26 |
27 | resources :tasks, only: [:show], param: :task_id
28 | end
29 |
30 | root to: 'dashboard#index'
31 | end
32 |
33 | root to: 'public#index'
34 | end
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "route-contention-evolution",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "build": "react-router build",
8 | "dev": "react-router dev",
9 | "start": "react-router-serve ./build/server/index.js",
10 | "typecheck": "react-router typegen && tsc"
11 | },
12 | "dependencies": {
13 | "@react-router/node": "7.5.0",
14 | "isbot": "^5.1.26",
15 | "react": "^19.1.0",
16 | "react-dom": "^19.1.0",
17 | "react-router": "7.5.0"
18 | },
19 | "devDependencies": {
20 | "@react-router/dev": "7.5.0",
21 | "@react-router/serve": "7.5.0",
22 | "@tailwindcss/vite": "^4.1.4",
23 | "@types/node": "^20",
24 | "@types/react": "^19.1.2",
25 | "@types/react-dom": "^19.1.2",
26 | "tailwindcss": "^4.1.4",
27 | "typescript": "^5.8.3",
28 | "vite": "6.2.6",
29 | "vite-tsconfig-paths": "^5.1.4"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/trees/nextjs.txt:
--------------------------------------------------------------------------------
1 | app/
2 | ├── (auth)/
3 | │ ├── login/
4 | │ │ └── page.tsx
5 | │ ├── logout/
6 | │ │ └── page.tsx
7 | │ ├── signup/
8 | │ │ └── page.tsx
9 | │ └── layout.tsx
10 | ├── (public)/
11 | │ ├── about-us/
12 | │ │ └── page.tsx
13 | │ ├── contact/
14 | │ │ └── page.tsx
15 | │ ├── page.tsx
16 | │ └── layout.tsx
17 | ├── dashboard/
18 | │ ├── calendar/
19 | │ │ ├── [day]/
20 | │ │ │ └── page.tsx
21 | │ │ └── page.tsx
22 | │ ├── projects/
23 | │ │ ├── [projectId]/
24 | │ │ │ ├── collaborators/
25 | │ │ │ │ └── page.tsx
26 | │ │ │ ├── edit/
27 | │ │ │ │ └── page.tsx
28 | │ │ │ ├── page.tsx
29 | │ │ │ ├── settings/
30 | │ │ │ │ └── page.tsx
31 | │ │ │ └── tasks/
32 | │ │ │ └── [taskId]/
33 | │ │ │ └── page.tsx
34 | │ │ ├── [projectId]/
35 | │ │ │ └── page.tsx
36 | │ │ ├── new/
37 | │ │ │ └── page.tsx
38 | │ │ └── page.tsx
39 | │ ├── calendar/
40 | │ │ └── page.tsx
41 | │ ├── page.tsx
42 | │ ├── projects/
43 | │ │ └── page.tsx
44 | │ └── layout.tsx
45 | ├── dashboard/
46 | │ └── projects/
47 | │ └── [projectId]/
48 | │ └── print/
49 | │ └── page.tsx
50 | └── layout.tsx
--------------------------------------------------------------------------------
/app/routes.ts:
--------------------------------------------------------------------------------
1 | import type { RouteConfig } from "@react-router/dev/routes";
2 | import { route, layout, index } from "@react-router/dev/routes";
3 |
4 | export default [
5 | layout("layouts/auth.tsx", [
6 | route("login", "routes/login.tsx"),
7 | route("logout", "routes/logout.tsx"),
8 | route("signup", "routes/signup.tsx"),
9 | ]),
10 | layout("layouts/public.tsx", [
11 | index("routes/home.tsx"),
12 | route("about-us", "routes/about-us.tsx"),
13 | route("contact", "routes/contact.tsx"),
14 | ]),
15 | route("dashboard", "routes/dashboard-home.tsx"),
16 | route("dashboard/calendar", "layouts/calendar.tsx", [
17 | index("routes/calendar.tsx"),
18 | route(":day", "routes/calendar-day.tsx"),
19 | ]),
20 | route("dashboard/projects", "layouts/projects.tsx", [
21 | route(":projectId", "layouts/project-details.tsx", [
22 | index("routes/project-details.tsx"),
23 | route("collaborators", "routes/project-collaborators.tsx"),
24 | route("edit", "routes/project-edit.tsx"),
25 | route("settings", "routes/project-settings.tsx"),
26 | route("tasks/:taskId", "routes/project-task.tsx"),
27 | ]),
28 | route("new", "routes/project-new.tsx"),
29 | ]),
30 | route("dashboard/projects/:projectId/print", "routes/project-print.tsx"),
31 | ] satisfies RouteConfig;
32 |
--------------------------------------------------------------------------------
/trees/nextjs-conventions.txt:
--------------------------------------------------------------------------------
1 | app/
2 | ├── (marketing)/ # Route group for marketing pages
3 | │ ├── about/
4 | │ │ ├── page.tsx # Main about page
5 | │ │ ├── loading.tsx # Loading state while data fetches
6 | │ │ └── error.tsx # Error boundary for this segment
7 | │ ├── blog/
8 | │ │ ├── [slug]/
9 | │ │ │ ├── page.tsx # Dynamic blog post page
10 | │ │ │ └── not-found.tsx # Custom 404 for invalid slugs
11 | │ │ └── page.tsx
12 | │ └── layout.tsx # Shared layout for marketing pages
13 | ├── (app)/ # Route group for authenticated app
14 | │ ├── dashboard/
15 | │ │ ├── layout.tsx # Dashboard layout with sidebar
16 | │ │ ├── loading.tsx # Loading state for dashboard
17 | │ │ ├── error.tsx # Error boundary for dashboard
18 | │ │ └── page.tsx
19 | │ ├── settings/
20 | │ │ ├── layout.tsx # Settings layout
21 | │ │ ├── page.tsx
22 | │ │ └── profile/
23 | │ │ ├── page.tsx
24 | │ │ └── loading.tsx
25 | │ └── layout.tsx # App-wide authenticated layout
26 | ├── @modal/ # Parallel route for modals
27 | │ ├── (.)dashboard/ # Intercepted route for dashboard modals
28 | │ │ └── projects/
29 | │ │ └── [id]/
30 | │ │ └── page.tsx # Modal for project details
31 | │ └── layout.tsx # Modal layout
32 | ├── @sidebar/ # Parallel route for sidebar
33 | │ └── default.tsx # Default sidebar component
34 | ├── api/ # API routes directory
35 | │ ├── webhooks/
36 | │ │ └── route.ts # Webhook handler
37 | │ └── users/
38 | │ └── route.ts # Users API endpoint
39 | ├── docs/ # Example of catch-all routes
40 | │ ├── [...slug]/
41 | │ │ └── page.tsx # Catch-all route for docs pages
42 | │ └── page.tsx # Docs landing page
43 | ├── layout.tsx # Root layout
44 | ├── loading.tsx # Root loading state
45 | ├── not-found.tsx # Global 404 page
46 | ├── page.tsx # Home page
47 | └── template.tsx # Root template (re-renders on navigation)
--------------------------------------------------------------------------------
/app/root.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Links,
3 | Meta,
4 | Scripts,
5 | ScrollRestoration,
6 | Outlet,
7 | Link,
8 | href,
9 | } from "react-router";
10 |
11 | import "./tailwind.css";
12 |
13 | import type { Route } from "./+types/root";
14 |
15 | export let links: Route.LinksFunction = () => [
16 | { rel: "preconnect", href: "https://fonts.googleapis.com" },
17 | {
18 | rel: "preconnect",
19 | href: "https://fonts.gstatic.com",
20 | crossOrigin: "anonymous",
21 | },
22 | {
23 | rel: "stylesheet",
24 | href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
25 | },
26 | ];
27 |
28 | const urls = [
29 | { path: href("/"), label: "Home" },
30 | { path: href("/about-us"), label: "About Us" },
31 | { path: href("/contact"), label: "Contact" },
32 | { path: href("/login"), label: "Login" },
33 | { path: href("/logout"), label: "Logout" },
34 | { path: href("/signup"), label: "Signup" },
35 | { path: href("/dashboard"), label: "Dashboard" },
36 | { path: href("/dashboard/calendar"), label: "Calendar" },
37 | {
38 | path: href("/dashboard/calendar/:day", { day: "1" }),
39 | label: "Calendar Day 1",
40 | },
41 | {
42 | path: href("/dashboard/projects/:projectId", { projectId: "1" }),
43 | label: "Project 1",
44 | },
45 | {
46 | path: href("/dashboard/projects/:projectId/collaborators", {
47 | projectId: "1",
48 | }),
49 | label: "Project 1 Collaborators",
50 | },
51 | {
52 | path: href("/dashboard/projects/:projectId/edit", { projectId: "1" }),
53 | label: "Edit Project 1",
54 | },
55 | {
56 | path: href("/dashboard/projects/:projectId/settings", { projectId: "1" }),
57 | label: "Project 1 Settings",
58 | },
59 | {
60 | path: href("/dashboard/projects/:projectId/tasks/:taskId", {
61 | projectId: "1",
62 | taskId: "1",
63 | }),
64 | label: "Project 1 Task 1",
65 | },
66 | { path: href("/dashboard/projects/new"), label: "New Project" },
67 | {
68 | path: href("/dashboard/projects/:projectId/print", { projectId: "1" }),
69 | label: "Print Project 1",
70 | },
71 | ];
72 |
73 | export default function App() {
74 | return (
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
Navigation
85 |
86 | {urls.map((link) => (
87 | -
88 |
89 | {link.label}
90 |
91 |
92 | ))}
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | );
103 | }
104 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Remix & React Router Routing Convention Evolution
2 |
3 | This is an example of the new routing convention coming to React Router v7.
4 |
5 | It starts with [this upgrade example from the Remix Docs](https://remix.run/docs/en/main/start/v2#upgrading-to-the-new-convention) and shows how to create the same in [routes.ts](./app/routes.ts).
6 |
7 | We don't have official docs yet because we're still building, but [the initial PR has some](https://github.com/remix-run/react-router/pull/11773).
8 |
9 | ## Convention file trees
10 |
11 | ### Remix v1 (folder routes)
12 |
13 | ```
14 | app/
15 | ├── routes/
16 | │ ├── __auth/
17 | │ │ ├── login.tsx
18 | │ │ ├── logout.tsx
19 | │ │ └── signup.tsx
20 | │ ├── __public/
21 | │ │ ├── about-us.tsx
22 | │ │ ├── contact.tsx
23 | │ │ └── index.tsx
24 | │ ├── dashboard/
25 | │ │ ├── calendar/
26 | │ │ │ ├── $day.tsx
27 | │ │ │ └── index.tsx
28 | │ │ ├── projects/
29 | │ │ │ ├── $projectId/
30 | │ │ │ │ ├── collaborators.tsx
31 | │ │ │ │ ├── edit.tsx
32 | │ │ │ │ ├── index.tsx
33 | │ │ │ │ ├── settings.tsx
34 | │ │ │ │ └── tasks.$taskId.tsx
35 | │ │ │ ├── $projectId.tsx
36 | │ │ │ └── new.tsx
37 | │ │ ├── calendar.tsx
38 | │ │ ├── index.tsx
39 | │ │ └── projects.tsx
40 | │ ├── __auth.tsx
41 | │ ├── __public.tsx
42 | │ └── dashboard.projects.$projectId.print.tsx
43 | └── root.tsx
44 | ```
45 |
46 | ### Remix v2 (flat routes)
47 |
48 | ```
49 | app/
50 | ├── routes/
51 | │ ├── _auth.login.tsx
52 | │ ├── _auth.logout.tsx
53 | │ ├── _auth.signup.tsx
54 | │ ├── _auth.tsx
55 | │ ├── _public._index.tsx
56 | │ ├── _public.about-us.tsx
57 | │ ├── _public.contact.tsx
58 | │ ├── _public.tsx
59 | │ ├── dashboard._index.tsx
60 | │ ├── dashboard.calendar._index.tsx
61 | │ ├── dashboard.calendar.$day.tsx
62 | │ ├── dashboard.calendar.tsx
63 | │ ├── dashboard.projects.$projectId._index.tsx
64 | │ ├── dashboard.projects.$projectId.collaborators.tsx
65 | │ ├── dashboard.projects.$projectId.edit.tsx
66 | │ ├── dashboard.projects.$projectId.settings.tsx
67 | │ ├── dashboard.projects.$projectId.tasks.$taskId.tsx
68 | │ ├── dashboard.projects.$projectId.tsx
69 | │ ├── dashboard.projects.new.tsx
70 | │ ├── dashboard.projects.tsx
71 | │ └── dashboard_.projects.$projectId.print.tsx
72 | └── root.tsx
73 | ```
74 |
75 | ## React Router v7
76 |
77 | **Flat files structure**
78 |
79 | ```ts
80 | // routes.ts
81 |
82 | export const routes: RoutesConfig = [
83 | layout("routes/auth-layout.tsx", [
84 | route("/login", "routes/login.tsx"),
85 | route("/logout", "routes/logout.tsx"),
86 | route("/signup", "routes/signup.tsx"),
87 | ]),
88 | layout("routes/public-layout.tsx", [
89 | route("/", "routes/home.tsx"),
90 | route("/about-us", "routes/about-us.tsx"),
91 | route("/contact", "routes/contact.tsx"),
92 | ]),
93 | route("/dashboard", "routes/dashboard-home.tsx"),
94 | route("/dashboard/calendar", "routes/calendar-layout.tsx", [
95 | index("routes/calendar.tsx"),
96 | route(":day", "routes/calendar-day.tsx"),
97 | ]),
98 | route("/dashboard/projects", "routes/projects-layout.tsx", [
99 | route(":projectId", "routes/project-details-layout.tsx", [
100 | index("routes/project-details.tsx"),
101 | route("collaborators", "routes/project-collaborators.tsx"),
102 | route("edit", "routes/project-edit.tsx"),
103 | route("settings", "routes/project-settings.tsx"),
104 | route("tasks/:taskId", "routes/project-task.tsx"),
105 | ]),
106 | route("new", "routes/project-new.tsx"),
107 | ]),
108 | route("/dashboard/projects/:projectId/print", "routes/project-print.tsx"),
109 | ];
110 | ```
111 |
112 | ```
113 | app
114 | ├── app.css
115 | ├── data.ts
116 | ├── layouts
117 | │ ├── auth.tsx
118 | │ ├── calendar.tsx
119 | │ ├── project-details.tsx
120 | │ ├── projects.tsx
121 | │ └── public.tsx
122 | ├── routes
123 | │ ├── about-us.tsx
124 | │ ├── calendar-day.tsx
125 | │ ├── calendar.tsx
126 | │ ├── contact.tsx
127 | │ ├── dashboard-home.tsx
128 | │ ├── home.tsx
129 | │ ├── login.tsx
130 | │ ├── logout.tsx
131 | │ ├── project-collaborators.tsx
132 | │ ├── project-details.tsx
133 | │ ├── project-edit.tsx
134 | │ ├── project-new.tsx
135 | │ ├── project-print.tsx
136 | │ ├── project-settings.tsx
137 | │ ├── project-task.tsx
138 | │ └── signup.tsx
139 | ├── root.tsx
140 | └── routes.ts
141 | ```
142 |
143 | **Separate layout files**
144 |
145 | ```ts
146 | // routes.ts
147 |
148 | export const routes: RoutesConfig = [
149 | layout("layouts/auth.tsx", [
150 | route("/login", "routes/login.tsx"),
151 | route("/logout", "routes/logout.tsx"),
152 | route("/signup", "routes/signup.tsx"),
153 | ]),
154 | layout("layouts/public.tsx", [
155 | route("/", "routes/home.tsx"),
156 | route("/about-us", "routes/about-us.tsx"),
157 | route("/contact", "routes/contact.tsx"),
158 | ]),
159 | route("/dashboard", "routes/dashboard-home.tsx"),
160 | route("/dashboard/calendar", "layouts/calendar.tsx", [
161 | index("routes/calendar.tsx"),
162 | route(":day", "routes/calendar-day.tsx"),
163 | ]),
164 | route("/dashboard/projects", "layouts/projects.tsx", [
165 | route(":projectId", "layouts/project-details.tsx", [
166 | index("routes/project-details.tsx"),
167 | route("collaborators", "routes/project-collaborators.tsx"),
168 | route("edit", "routes/project-edit.tsx"),
169 | route("settings", "routes/project-settings.tsx"),
170 | route("tasks/:taskId", "routes/project-task.tsx"),
171 | ]),
172 | route("new", "routes/project-new.tsx"),
173 | ]),
174 | route("/dashboard/projects/:projectId/print", "routes/project-print.tsx"),
175 | ];
176 | ```
177 |
178 | ```
179 | app/
180 | ├── layouts
181 | │ ├── auth.tsx
182 | │ ├── calendar.tsx
183 | │ ├── project-details.tsx
184 | │ ├── projects.tsx
185 | │ └── public.tsx
186 | ├── routes/
187 | │ ├── about-us.tsx
188 | │ ├── auth-layout.tsx
189 | │ ├── calendar-day.tsx
190 | │ ├── calendar-layout.tsx
191 | │ ├── calendar.tsx
192 | │ ├── contact.tsx
193 | │ ├── dashboard-home.tsx
194 | │ ├── home.tsx
195 | │ ├── login.tsx
196 | │ ├── logout.tsx
197 | │ ├── project-collaborators.tsx
198 | │ ├── project-details-layout.tsx
199 | │ ├── project-details.tsx
200 | │ ├── project-edit.tsx
201 | │ ├── project-new.tsx
202 | │ ├── project-print.tsx
203 | │ ├── project-settings.tsx
204 | │ ├── project-task.tsx
205 | │ ├── projects-layout.tsx
206 | │ ├── public-layout.tsx
207 | │ └── signup.tsx
208 | └── root.tsx
209 | ```
210 |
--------------------------------------------------------------------------------