├── app ├── routes │ ├── $.tsx │ ├── page3.tsx │ └── index.tsx ├── entry.client.tsx ├── old-app │ ├── pages │ │ ├── page-4 │ │ │ └── index.js │ │ └── page-2 │ │ │ └── index.js │ └── app.js ├── entry.server.tsx └── root.tsx ├── .gitignore ├── .eslintrc ├── remix.env.d.ts ├── public └── favicon.ico ├── remix.config.js ├── tsconfig.json ├── package.json └── README.md /app/routes/$.tsx: -------------------------------------------------------------------------------- 1 | export { default } from "~/old-app/app"; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | /public/build 6 | .env 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@remix-run/eslint-config", "@remix-run/eslint-config/node"] 3 | } 4 | -------------------------------------------------------------------------------- /remix.env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/incremental-react-router-to-remix-upgrade-path/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import { RemixBrowser } from "@remix-run/react"; 2 | import { hydrate } from "react-dom"; 3 | 4 | hydrate(, document); 5 | -------------------------------------------------------------------------------- /remix.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('@remix-run/dev').AppConfig} 3 | */ 4 | module.exports = { 5 | ignoredRouteFiles: ["**/.*"], 6 | // appDirectory: "app", 7 | // assetsBuildDirectory: "public/build", 8 | // serverBuildPath: "build/index.js", 9 | // publicPath: "/build/", 10 | }; 11 | -------------------------------------------------------------------------------- /app/old-app/pages/page-4/index.js: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | export function PageFour() { 4 | return ( 5 |
6 |
7 |

Page 4

8 | 9 | Go Home (remix) 10 |
11 | Go to page 2 (old) 12 |
13 | Go to page 3 (remix) 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/routes/page3.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@remix-run/react"; 2 | 3 | export default function Page3() { 4 | return ( 5 |
6 |
7 |

Page 3 (remix)

8 | 9 | Go home (remix) 10 |
11 | Go to page 3 (remix) 12 |
13 | Go to page 4 (old) 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/old-app/pages/page-2/index.js: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | export function PageTwo() { 4 | return ( 5 |
6 |
7 |

Page 2 (old)

8 | 9 | Go home (remix) 10 |
11 | Go to page 3 (remix) 12 |
13 | Go to page 4 (old) 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@remix-run/react"; 2 | 3 | export default function Page3() { 4 | return ( 5 |
6 |
7 |

Home (remix)

8 | 9 | Go to page 2 (old) 10 |
11 | Go to page 3 (remix) 12 |
13 | Go to page 4 (old) 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/old-app/app.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Routes, Route } from "react-router-dom"; 4 | import { PageFour } from "./pages/page-4"; 5 | import { PageTwo } from "./pages/page-2"; 6 | 7 | function App() { 8 | return ( 9 |
10 |
Old app
11 | 12 | } /> 13 | } /> 14 | 15 |
16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], 3 | "compilerOptions": { 4 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 5 | "isolatedModules": true, 6 | "esModuleInterop": true, 7 | "jsx": "react-jsx", 8 | "moduleResolution": "node", 9 | "resolveJsonModule": true, 10 | "target": "ES2019", 11 | "strict": true, 12 | "baseUrl": ".", 13 | "paths": { 14 | "~/*": ["./app/*"] 15 | }, 16 | "noEmit": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "allowJs": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | import type { EntryContext } from "@remix-run/node"; 2 | import { RemixServer } from "@remix-run/react"; 3 | import { renderToString } from "react-dom/server"; 4 | 5 | export default function handleRequest( 6 | request: Request, 7 | responseStatusCode: number, 8 | responseHeaders: Headers, 9 | remixContext: EntryContext 10 | ) { 11 | let markup = renderToString( 12 | 13 | ); 14 | 15 | responseHeaders.set("Content-Type", "text/html"); 16 | 17 | return new Response("" + markup, { 18 | status: responseStatusCode, 19 | headers: responseHeaders, 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /app/root.tsx: -------------------------------------------------------------------------------- 1 | import type { MetaFunction } from "@remix-run/node"; 2 | import { 3 | Links, 4 | LiveReload, 5 | Meta, 6 | Outlet, 7 | Scripts, 8 | ScrollRestoration, 9 | } from "@remix-run/react"; 10 | 11 | export const meta: MetaFunction = () => ({ 12 | charset: "utf-8", 13 | title: "New Remix App", 14 | viewport: "width=device-width,initial-scale=1", 15 | }); 16 | 17 | export default function App() { 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remix-template-remix", 3 | "private": true, 4 | "description": "", 5 | "license": "", 6 | "sideEffects": false, 7 | "scripts": { 8 | "build": "remix build", 9 | "dev": "remix dev", 10 | "start": "remix-serve build" 11 | }, 12 | "dependencies": { 13 | "@remix-run/node": "^1.4.3", 14 | "@remix-run/react": "^1.4.3", 15 | "@remix-run/serve": "^1.4.3", 16 | "react": "^17.0.2", 17 | "react-dom": "^17.0.2", 18 | "react-router-dom": "^6.3.0" 19 | }, 20 | "devDependencies": { 21 | "@remix-run/dev": "^1.4.3", 22 | "@remix-run/eslint-config": "^1.4.3", 23 | "@types/react": "^17.0.24", 24 | "@types/react-dom": "^17.0.9", 25 | "eslint": "^8.11.0", 26 | "typescript": "^4.5.5" 27 | }, 28 | "engines": { 29 | "node": ">=14" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Router + Remix 2 | 3 | This is an example of how you can incrementally migrate to Remix if you're using React Router. Here's the basic idea: 4 | 5 | 1. Upgrade your React Router site to the latest version of React Router ([find out how to do this iteratively](https://www.npmjs.com/package/react-router-dom-v5-compat)). 6 | 2. Install Remix and set up the conventional files of `app/{root,entry.client,entry.server}.tsx` 7 | 3. Move all your existing code into a directory within the `app` directory (like `app/old-app` for example). 8 | 4. Remove `` from your `App` 9 | 5. Create a `app/routes/$.tsx` file with just `export { default } from "~/old-app/app";` (or whatever file has the root component for your existing React Router app). 10 | 6. Remove all your old webpack build stuff and use the `remix` CLI instead. Your builds are now outrageously fast. 11 | 7. Everything should work at this point (_and_ it'll be server rendered too!!). Commit + push! 12 | 8. Over time, move old routes to the `routes` directory until you bring everything over. 13 | 9. You're done! 14 | 15 | We'll have better docs and even videos about this in the future, but this is a pretty darn solid and iterative approach with quick wins and a clear path. 16 | --------------------------------------------------------------------------------