├── .gitignore ├── public └── favicon.png ├── app ├── entry.client.tsx ├── routes │ ├── 404.tsx │ └── index.tsx ├── entry.server.tsx └── root.tsx ├── remix.env.d.ts ├── styles ├── global.css └── index.css ├── remix.config.js ├── tsconfig.json ├── postcss.config.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | /public/build 6 | 7 | /app/styles -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-ebey/remix-css-modules/HEAD/public/favicon.png -------------------------------------------------------------------------------- /app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom"; 2 | import { RemixBrowser } from "remix"; 3 | 4 | ReactDOM.hydrate(, document); 5 | -------------------------------------------------------------------------------- /remix.env.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 Remix Software Inc. All rights reserved. 2 | /// 3 | /// -------------------------------------------------------------------------------- /styles/global.css: -------------------------------------------------------------------------------- 1 | :focus:not(:focus-visible) { 2 | outline: none; 3 | } 4 | 5 | body { 6 | font-family: sans-serif; 7 | } 8 | 9 | footer { 10 | text-align: center; 11 | color: #ccc; 12 | padding-top: 80px; 13 | } 14 | -------------------------------------------------------------------------------- /remix.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('@remix-run/dev/config').AppConfig} 3 | */ 4 | module.exports = { 5 | appDirectory: "app", 6 | browserBuildDirectory: "public/build", 7 | publicPath: "/build/", 8 | serverBuildDirectory: "build", 9 | devServerPort: 8002 10 | }; 11 | -------------------------------------------------------------------------------- /styles/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | * when the user visits this page, this style will apply, when they leave, it 3 | * will get unloaded, so don't worry so much about conflicting styles between 4 | * pages! 5 | */ 6 | 7 | .container { 8 | color: darkslategrey; 9 | background-color: lightgrey; 10 | } -------------------------------------------------------------------------------- /app/routes/404.tsx: -------------------------------------------------------------------------------- 1 | import type { MetaFunction } from "remix"; 2 | 3 | export let meta: MetaFunction = () => { 4 | return { title: "Ain't nothing here" }; 5 | }; 6 | 7 | export default function FourOhFour() { 8 | return ( 9 |
10 |

404

11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], 3 | "compilerOptions": { 4 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 5 | "esModuleInterop": true, 6 | "jsx": "react-jsx", 7 | "moduleResolution": "node", 8 | "target": "ES2019", 9 | "strict": true, 10 | "paths": { 11 | "~/*": ["./app/*"] 12 | }, 13 | 14 | // Remix takes care of building everything in `remix build`. 15 | "noEmit": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | let path = require("path"); 2 | let fsp = require("fs/promises"); 3 | 4 | module.exports = { 5 | plugins: [ 6 | require("postcss-modules")({ 7 | getJSON: async (cssFilename, json, outputFilename) => { 8 | console.log(cssFilename, json, outputFilename); 9 | await fsp 10 | .mkdir(path.dirname(outputFilename), { recursive: true }) 11 | .catch(() => {}); 12 | await fsp.writeFile( 13 | `${outputFilename.replace(/\.css$/, ".json")}`, 14 | JSON.stringify(json) 15 | ); 16 | }, 17 | }), 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOMServer from "react-dom/server"; 2 | import type { EntryContext } from "remix"; 3 | import { RemixServer } from "remix"; 4 | 5 | export default function handleRequest( 6 | request: Request, 7 | responseStatusCode: number, 8 | responseHeaders: Headers, 9 | remixContext: EntryContext 10 | ) { 11 | let markup = ReactDOMServer.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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "remix-app-template", 4 | "description": "", 5 | "license": "", 6 | "scripts": { 7 | "build:css": "postcss styles/**/*.css --dir app/styles --env production", 8 | "build:remix": "remix build", 9 | "build": "run-s build:css build:remix", 10 | "dev:css": "postcss styles/**/*.css --dir app/styles -w", 11 | "dev:remix": "remix run", 12 | "dev": "run-p dev:*", 13 | "postinstall": "remix setup node && npm run build:css", 14 | "start": "remix-serve build" 15 | }, 16 | "dependencies": { 17 | "@remix-run/react": "^0.19.2", 18 | "@remix-run/serve": "^0.19.2", 19 | "react": "^17.0.2", 20 | "react-dom": "^17.0.2", 21 | "react-router-dom": "6.0.0-beta.6", 22 | "remix": "^0.19.2" 23 | }, 24 | "devDependencies": { 25 | "@remix-run/dev": "^0.19.2", 26 | "@types/react": "^17.0.24", 27 | "@types/react-dom": "^17.0.9", 28 | "npm-run-all": "^4.1.5", 29 | "postcss": "^8.3.9", 30 | "postcss-cli": "^9.0.1", 31 | "postcss-modules": "^4.2.2", 32 | "typescript": "^4.1.2" 33 | }, 34 | "engines": { 35 | "node": ">=14" 36 | }, 37 | "sideEffects": false 38 | } 39 | -------------------------------------------------------------------------------- /app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import type { MetaFunction, LinksFunction, LoaderFunction } from "remix"; 2 | import { useLoaderData } from "remix"; 3 | import { Link } from "react-router-dom"; 4 | 5 | import stylesUrl from "../styles/index.css"; 6 | import styles from "../styles/index.json"; 7 | 8 | export let meta: MetaFunction = () => { 9 | return { 10 | title: "Remix Starter", 11 | description: "Welcome to remix!" 12 | }; 13 | }; 14 | 15 | export let links: LinksFunction = () => { 16 | return [{ rel: "stylesheet", href: stylesUrl }]; 17 | }; 18 | 19 | export let loader: LoaderFunction = async () => { 20 | return { message: "this is awesome 😎" }; 21 | }; 22 | 23 | export default function Index() { 24 | let data = useLoaderData(); 25 | 26 | return ( 27 |
28 |

Welcome to Remix!

29 |

30 | Check out the docs to get 31 | started. 32 |

33 |

Message from the loader: {data.message}

34 |

35 | Link to 404 not found page. Clicking this 36 | link will land you in your root CatchBoundary component. 37 |

38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Remix! 2 | 3 | - [Remix Docs](https://docs.remix.run) 4 | - [Customer Dashboard](https://remix.run/dashboard) 5 | 6 | ## Development 7 | 8 | From your terminal: 9 | 10 | ```sh 11 | npm run dev 12 | ``` 13 | 14 | This starts your app in development mode, rebuilding assets on file changes. 15 | 16 | ## Deployment 17 | 18 | First, build your app for production: 19 | 20 | ```sh 21 | npm run build 22 | ``` 23 | 24 | Then run the app in production mode: 25 | 26 | ```sh 27 | npm start 28 | ``` 29 | 30 | Now you'll need to pick a host to deploy it to. 31 | 32 | ### DIY 33 | 34 | If you're familiar with deploying node applications, the built-in Remix app server is production-ready. 35 | 36 | Make sure to deploy the output of `remix build` 37 | 38 | - `build/` 39 | - `public/build/` 40 | 41 | ### Using a Template 42 | 43 | When you ran `npm init remix` there were a few choices for hosting. You can run that again to create a new project, then copy over your `app/` folder to the new project that's pre-configured for your target server. 44 | 45 | ```sh 46 | cd .. 47 | # create a new project, and pick a pre-configured host 48 | npm init remix 49 | cd my-new-remix-app 50 | # remove the new project's app (not the old one!) 51 | rm -rf app 52 | # copy your app over 53 | cp -R ../my-old-remix-app/app app 54 | ``` 55 | -------------------------------------------------------------------------------- /app/root.tsx: -------------------------------------------------------------------------------- 1 | import type { LinksFunction, LoaderFunction } from "remix"; 2 | import { 3 | Meta, 4 | Links, 5 | Scripts, 6 | useLoaderData, 7 | LiveReload, 8 | useCatch 9 | } from "remix"; 10 | import { Outlet } from "react-router-dom"; 11 | 12 | import stylesUrl from "./styles/global.css"; 13 | 14 | export let links: LinksFunction = () => { 15 | return [{ rel: "stylesheet", href: stylesUrl }]; 16 | }; 17 | 18 | export let loader: LoaderFunction = async () => { 19 | return { date: new Date() }; 20 | }; 21 | 22 | function Document({ 23 | children, 24 | title 25 | }: { 26 | children: React.ReactNode; 27 | title?: string; 28 | }) { 29 | return ( 30 | 31 | 32 | 33 | 34 | {title ? {title} : null} 35 | 36 | 37 | 38 | 39 | {children} 40 | 41 | {process.env.NODE_ENV === "development" && } 42 | 43 | 44 | ); 45 | } 46 | 47 | export default function App() { 48 | let data = useLoaderData(); 49 | 50 | return ( 51 | 52 | 53 |
54 |

This page was rendered at {data.date.toLocaleString()}

55 |
56 |
57 | ); 58 | } 59 | 60 | export function CatchBoundary() { 61 | let caught = useCatch(); 62 | 63 | switch (caught.status) { 64 | case 401: 65 | case 404: 66 | return ( 67 | 68 |

69 | {caught.status} {caught.statusText} 70 |

71 |
72 | ); 73 | 74 | default: 75 | throw new Error( 76 | `Unexpected caught response with status: ${caught.status}` 77 | ); 78 | } 79 | } 80 | 81 | export function ErrorBoundary({ error }: { error: Error }) { 82 | console.error(error); 83 | 84 | return ( 85 | 86 |

App Error

87 |
{error.message}
88 |

89 | Replace this UI with what you want users to see when your app throws 90 | uncaught errors. 91 |

92 |
93 | ); 94 | } 95 | --------------------------------------------------------------------------------