├── 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 | --------------------------------------------------------------------------------