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