├── public └── favicon.ico ├── .gitignore ├── app ├── styles │ ├── index.css │ └── global.css ├── tsconfig.json ├── routes │ ├── 404.tsx │ └── index.tsx ├── entry.client.tsx ├── entry.server.tsx └── root.tsx ├── .npmrc.example ├── pm2.config.js ├── package.json ├── server.js ├── remix.config.js └── README.md /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remix-run/starter-express/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | 4 | /.cache/ 5 | /build/ 6 | /public/build/ 7 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 4 | "esModuleInterop": true, 5 | "jsx": "react-jsx", 6 | "moduleResolution": "node", 7 | "target": "es2019", 8 | "strict": true, 9 | "skipLibCheck": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/routes/404.tsx: -------------------------------------------------------------------------------- 1 | import type { MetaFunction } from "@remix-run/react"; 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 | -------------------------------------------------------------------------------- /.npmrc.example: -------------------------------------------------------------------------------- 1 | # Swap out "" below with the key you get from logging in 2 | # to https://remix.run. If this is a public repo, you'll want to move this 3 | # line into ~/.npmrc to keep it private. 4 | //npm.remix.run/:_authToken= 5 | 6 | # This line tells npm where to find @remix-run packages. 7 | @remix-run:registry=https://npm.remix.run 8 | -------------------------------------------------------------------------------- /app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom"; 2 | import { RemixBrowser as Remix } from "@remix-run/react"; 3 | 4 | // @types/react-dom says the 2nd argument to ReactDOM.hydrate() must be a 5 | // `Element | DocumentFragment | null` but React 16 allows you to pass the 6 | // `document` object as well. This is a bug in @types/react-dom that we can 7 | // safely ignore for now. 8 | // @ts-ignore 9 | ReactDOM.hydrate(, document); 10 | -------------------------------------------------------------------------------- /pm2.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: "Express", 5 | script: "server.js", 6 | watch: ["build/assets.json"], 7 | watch_options: { 8 | followSymlinks: false 9 | }, 10 | env: { 11 | NODE_ENV: "development" 12 | } 13 | }, 14 | { 15 | name: "Remix", 16 | script: "remix run2", 17 | ignore_watch: ["."], 18 | env: { 19 | NODE_ENV: "development" 20 | } 21 | } 22 | ] 23 | }; 24 | -------------------------------------------------------------------------------- /app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOMServer from "react-dom/server"; 2 | import { RemixServer as Remix } from "@remix-run/react"; 3 | import type { EntryContext } from "@remix-run/node"; 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 | return new Response("" + markup, { 16 | status: responseStatusCode, 17 | headers: { 18 | ...Object.fromEntries(responseHeaders), 19 | "Content-Type": "text/html" 20 | } 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "remix-starter-express", 4 | "scripts": { 5 | "postinstall": "npm run build", 6 | "build": "remix build2", 7 | "dev": "pm2-dev pm2.config.js", 8 | "start": "node server.js" 9 | }, 10 | "dependencies": { 11 | "@remix-run/express": "^0.15.1", 12 | "@remix-run/node": "^0.15.1", 13 | "@remix-run/react": "^0.15.1", 14 | "compression": "^1.7.4", 15 | "express": "^4.17.1", 16 | "morgan": "^1.10.0", 17 | "react": "^17.0.1", 18 | "react-dom": "^17.0.1", 19 | "react-router-dom": "^6.0.0-beta.0" 20 | }, 21 | "devDependencies": { 22 | "@remix-run/dev": "^0.15.1", 23 | "@types/react": "^17.0.1", 24 | "@types/react-dom": "^17.0.0", 25 | "pm2": "^4.5.4", 26 | "typescript": "^4.1.2" 27 | }, 28 | "sideEffects": false, 29 | "engines": { 30 | "node": "12" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | MetaFunction, 3 | LinksFunction, 4 | LoaderFunction 5 | } from "@remix-run/react"; 6 | import { useRouteData } from "@remix-run/react"; 7 | 8 | import stylesUrl from "../styles/index.css"; 9 | 10 | export let meta: MetaFunction = () => { 11 | return { 12 | title: "Remix Starter", 13 | description: "Welcome to remix!" 14 | }; 15 | }; 16 | 17 | export let links: LinksFunction = () => { 18 | return [{ rel: "stylesheet", href: stylesUrl }]; 19 | }; 20 | 21 | export let loader: LoaderFunction = async () => { 22 | return { message: "this is awesome 😎" }; 23 | }; 24 | 25 | export default function Index() { 26 | let data = useRouteData(); 27 | 28 | return ( 29 |
30 |

Welcome to Remix!

31 |

32 | Check out the docs to get 33 | started. 34 |

35 |

Message from the loader: {data.message}

36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const compression = require("compression"); 3 | const morgan = require("morgan"); 4 | const { createRequestHandler } = require("@remix-run/express"); 5 | 6 | let app = express(); 7 | 8 | // Responses should be served with compression to minimize total network bytes. 9 | // However, where this compression happens can vary wildly depending on your stack 10 | // and infrastructure. Here we are compressing all our Express responses for the 11 | // purpose of this starter repository, but feel free to (re)move it or change it. 12 | app.use(compression()); 13 | 14 | app.use(express.static("public")); 15 | 16 | if (process.env.NODE_ENV === "development") { 17 | app.use(morgan("dev")); 18 | } 19 | 20 | app.all( 21 | "*", 22 | createRequestHandler({ 23 | build: require("./build"), 24 | getLoadContext() { 25 | // Whatever you return here will be passed as `context` to your loaders 26 | // and actions. 27 | } 28 | }) 29 | ); 30 | 31 | let port = process.env.PORT || 3000; 32 | 33 | app.listen(port, () => { 34 | console.log(`Express server started on http://localhost:${port}`); 35 | }); 36 | -------------------------------------------------------------------------------- /app/root.tsx: -------------------------------------------------------------------------------- 1 | import type { LinksFunction, LoaderFunction } from "@remix-run/react"; 2 | import { Meta, Links, Scripts, useRouteData } from "@remix-run/react"; 3 | import { Outlet } from "react-router-dom"; 4 | 5 | import stylesUrl from "./styles/global.css"; 6 | 7 | export let links: LinksFunction = () => { 8 | return [{ rel: "stylesheet", href: stylesUrl }]; 9 | }; 10 | 11 | export let loader: LoaderFunction = async () => { 12 | return { date: new Date() }; 13 | }; 14 | 15 | export default function App() { 16 | let data = useRouteData(); 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |

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

30 |
31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | export function ErrorBoundary({ error }: { error: Error }) { 38 | return ( 39 | 40 | 41 | 42 | Oops! 43 | 44 | 45 |
46 |

App Error

47 |
{error.message}
48 |

49 | Replace this UI with what you want users to see when your app throws 50 | uncaught errors. The file is at app/root.tsx. 51 |

52 |
53 | 54 | 55 | 56 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /remix.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * The path to the `app` directory, relative to remix.config.js. Defaults to 4 | * "app". All code in this directory is part of your app and will be compiled 5 | * by Remix. 6 | * 7 | */ 8 | appDirectory: "app", 9 | 10 | /** 11 | * A hook for defining custom routes based on your own file conventions. This 12 | * is not required, but may be useful if you have custom/advanced routing 13 | * requirements. 14 | */ 15 | // routes(defineRoutes) { 16 | // return defineRoutes(route => { 17 | // route( 18 | // // The URL path for this route. 19 | // "/pages/one", 20 | // // The path to this route's component file, relative to `appDirectory`. 21 | // "pages/one", 22 | // // Options: 23 | // { 24 | // // The path to this route's data module, relative to `dataDirectory`. 25 | // loader: "...", 26 | // // The path to this route's styles file, relative to `appDirectory`. 27 | // styles: "..." 28 | // } 29 | // ); 30 | // }); 31 | // }, 32 | 33 | /** 34 | * The path to the browser build, relative to remix.config.js. Defaults to 35 | * `public/build`. The browser build contains all public JavaScript and CSS 36 | * files that are created when building your routes. 37 | */ 38 | browserBuildDirectory: "public/build", 39 | 40 | /** 41 | * The URL prefix of the browser build with a trailing slash. Defaults to 42 | * `/build/`. 43 | */ 44 | publicPath: "/build/", 45 | 46 | /** 47 | * The path to the server build directory, relative to remix.config.js. 48 | * Defaults to `build`. The server build is a collection of JavaScript modules 49 | * that are created from building your routes. They are used on the server to 50 | * generate HTML. 51 | */ 52 | serverBuildDirectory: "build", 53 | 54 | /** 55 | * The port to use when running `remix run`. Defaults to 8002. 56 | */ 57 | devServerPort: 8002 58 | }; 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Please Don't Use 2 | 3 | > Please use `npm init remix` instead of this starter repo to create a new Remix app. 4 | > This repository was archived on April 29, 2021. 5 | 6 | # Remix Starter for Express 7 | 8 | Welcome to Remix! 9 | 10 | This is a starter repo for using [Remix](https://remix.run) with [Express](http://expressjs.com/). 11 | 12 | ## Development 13 | 14 | After cloning the repo, rename `.npmrc.example` to `.npmrc` and insert the license key you get from [logging in to your dashboard at remix.run](https://remix.run). 15 | 16 | > Note: if this is a public repo, you'll probably want to move the line with your key into `~/.npmrc` to keep it private. 17 | 18 | Then, install all dependencies using `npm`: 19 | 20 | ```sh 21 | $ npm install 22 | ``` 23 | 24 | Your `@remix-run/*` dependencies will come from the Remix package registry. 25 | 26 | Once everything is installed, start the app in development mode with the following command: 27 | 28 | ```sh 29 | $ npm run dev 30 | ``` 31 | 32 | This will run a few processes concurrently that will dynamically rebuild as your source files change. To see your changes, refresh the browser. 33 | 34 | > Note: Hot module reloading is coming soon, which will allow you to see your changes without refreshing. 35 | 36 | ## Production 37 | 38 | To run the app in production mode, you'll need to build it first. 39 | 40 | ```sh 41 | $ npm run build 42 | $ npm start 43 | ``` 44 | 45 | This will start a single HTTP server process that will serve the app from the files generated in the build step. 46 | 47 | ## Documentation 48 | 49 | Detailed documentation for Remix [is available at remix.run](https://remix.run/dashboard/docs). 50 | 51 | ## Project Structure 52 | 53 | All application source code is found in the `app` directory. This includes your application entry points for both server rendering (see `app/entry.server.tsx`) and the browser (see `app/entry.client.tsx`), as well as your root component and routes (see `app/root.tsx` and `app/routes`). 54 | 55 | Everything in the `public` directory is served by `express.static`. 56 | 57 | ## Don't want TypeScript? 58 | 59 | The [`no-typescript` branch](https://github.com/remix-run/starter-express/tree/no-typescript) is a version of this same starter template that uses plain JavaScript instead of TypeScript for all code in `app`. 60 | --------------------------------------------------------------------------------