├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .stylelintrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── app ├── entry.client.tsx ├── entry.server.tsx ├── root.tsx └── routes │ └── index.tsx ├── package.json ├── public └── favicon.ico ├── remix.config.js ├── remix.env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── twind.config.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@remix-run/eslint-config', 'prettier'], 3 | plugins: ['prettier'], 4 | rules: { 5 | 'prettier/prettier': 'error', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ========================= 2 | # Node.js-Specific Ignores 3 | # ========================= 4 | 5 | # Build directory 6 | /build 7 | /public/build 8 | 9 | # Remix cache 10 | /.cache 11 | 12 | # npm lockfile (we use yarn) 13 | /package-lock.json 14 | 15 | # lockfiles inside workspaces 16 | packages/*/yarn.lock 17 | 18 | # Logs 19 | logs 20 | *.log 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # Runtime data 26 | pids 27 | *.pid 28 | *.seed 29 | *.pid.lock 30 | 31 | # Directory for instrumented libs generated by jscoverage/JSCover 32 | lib-cov 33 | 34 | # Coverage directory used by tools like istanbul 35 | coverage 36 | 37 | # nyc test coverage 38 | .nyc_output 39 | 40 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 41 | .grunt 42 | 43 | # Bower dependency directory (https://bower.io/) 44 | bower_components 45 | 46 | # node-waf configuration 47 | .lock-wscript 48 | 49 | # Compiled binary addons (http://nodejs.org/api/addons.html) 50 | build/Release 51 | 52 | # Dependency directories 53 | node_modules/ 54 | jspm_packages/ 55 | 56 | # Typescript v1 declaration files 57 | typings/ 58 | 59 | # Optional npm cache directory 60 | .npm 61 | 62 | # Optional eslint cache 63 | .eslintcache 64 | 65 | # Optional REPL history 66 | .node_repl_history 67 | 68 | # Output of 'npm pack' 69 | *.tgz 70 | 71 | # Yarn Integrity file 72 | .yarn-integrity 73 | 74 | # dotenv environment variables file 75 | .env 76 | 77 | # Yarn v2 78 | .yarn/* 79 | !.yarn/releases 80 | !.yarn/plugins 81 | !.yarn/sdks 82 | !.yarn/versions 83 | .pnp.* 84 | 85 | # ========================= 86 | # Operating System Files 87 | # ========================= 88 | 89 | # OSX 90 | # ========================= 91 | 92 | .DS_Store 93 | .AppleDouble 94 | .LSOverride 95 | 96 | # Thumbnails 97 | ._* 98 | 99 | # Files that might appear on external disk 100 | .Spotlight-V100 101 | .Trashes 102 | 103 | # Directories potentially created on remote AFP share 104 | .AppleDB 105 | .AppleDesktop 106 | Network Trash Folder 107 | Temporary Items 108 | .apdisk 109 | 110 | # Windows 111 | # ========================= 112 | 113 | # Windows image file caches 114 | Thumbs.db 115 | ehthumbs.db 116 | 117 | # Folder config file 118 | Desktop.ini 119 | 120 | # Recycle Bin used on file shares 121 | $RECYCLE.BIN/ 122 | 123 | # Windows Installer files 124 | *.cab 125 | *.msi 126 | *.msm 127 | *.msp 128 | 129 | # Windows shortcuts 130 | *.lnk 131 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "tabWidth": 2, 4 | "printWidth": 120, 5 | "singleQuote": true, 6 | "trailingComma": "all", 7 | "arrowParens": "avoid", 8 | "endOfLine": "auto" 9 | } 10 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-recommended", 3 | "rules": { 4 | "at-rule-no-unknown": [ 5 | true, 6 | { 7 | "ignoreAtRules": ["extends", "tailwind", "layer"] 8 | } 9 | ], 10 | "block-no-empty": null, 11 | "unit-allowed-list": ["em", "rem", "s", "vw", "vh", "%"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.insertSpaces": true, 4 | "editor.renderWhitespace": "boundary", 5 | "editor.rulers": [120], 6 | "editor.formatOnSave": true, 7 | "files.encoding": "utf8", 8 | "files.trimTrailingWhitespace": true, 9 | "files.insertFinalNewline": true, 10 | "typescript.tsdk": "./node_modules/typescript/lib", 11 | "search.exclude": { 12 | "build/**": true, 13 | "public/**": true, 14 | "node_modules/**": true 15 | }, 16 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], 17 | "css.validate": false, 18 | "stylelint.enable": true 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # remix-typescript-tailwind-quickstart 2 | 3 | > Get started on Remix with TypeScript and Tailwind CSS in seconds. 4 | 5 | This is an example setup of [Remix](https://remix.run/) building on top of the official TypeScript template and the [Remix example for Twind](https://github.com/tw-in-js/example-remix). It adds the following: 6 | 7 | - Styling with [Tailwind CSS](https://tailwindcss.com/) and [twind](https://github.com/tw-in-js/twind) 8 | - Basic ESLint and Prettier setup 9 | - CSS linting with [Stylelint](https://stylelint.io/) 10 | - ESLint and TypeScript typechecking as part of `validate` script (ready to plug into any CI) 11 | 12 | ## Important Links 13 | 14 | - [15m Quickstart Blog Tutorial](https://remix.run/tutorials/blog) 15 | - [Deep Dive Jokes App Tutorial](https://remix.run/tutorials/jokes) 16 | - [Remix Docs](https://remix.run/docs) 17 | - [twind Docs](https://twind.dev/) 18 | 19 | --- 20 | 21 | ## Development 22 | 23 | From your terminal: 24 | 25 | ```sh 26 | npm run dev 27 | ``` 28 | 29 | This starts your app in development mode, rebuilding assets on file changes. 30 | 31 | ## Deployment 32 | 33 | First, build your app for production: 34 | 35 | ```sh 36 | npm run build 37 | ``` 38 | 39 | Then run the app in production mode: 40 | 41 | ```sh 42 | npm start 43 | ``` 44 | 45 | Now you'll need to pick a host to deploy it to. 46 | 47 | ### DIY 48 | 49 | If you're familiar with deploying node applications, the built-in Remix app server is production-ready. 50 | 51 | Make sure to deploy the output of `remix build` 52 | 53 | - `build/` 54 | - `public/build/` 55 | 56 | ### Using a Template 57 | 58 | When you ran `npx create-remix@latest` 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. 59 | 60 | ```sh 61 | cd .. 62 | # create a new project, and pick a pre-configured host 63 | npx create-remix@latest 64 | cd my-new-remix-app 65 | # remove the new project's app (not the old one!) 66 | rm -rf app 67 | # copy your app over 68 | cp -R ../my-old-remix-app/app app 69 | ``` 70 | -------------------------------------------------------------------------------- /app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import { hydrate } from 'react-dom'; 2 | import { RemixBrowser } from 'remix'; 3 | 4 | hydrate(, document); 5 | -------------------------------------------------------------------------------- /app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | import { renderToString } from 'react-dom/server'; 2 | import { RemixServer } from 'remix'; 3 | import type { EntryContext } from 'remix'; 4 | import inline from '@twind/with-remix/server'; 5 | 6 | export default function handleRequest( 7 | request: Request, 8 | responseStatusCode: number, 9 | responseHeaders: Headers, 10 | remixContext: EntryContext, 11 | ) { 12 | let markup = renderToString(); 13 | 14 | // Add twind styles to the markup 15 | markup = inline(markup); 16 | 17 | responseHeaders.set('Content-Type', 'text/html'); 18 | 19 | return new Response('' + markup, { 20 | status: responseStatusCode, 21 | headers: responseHeaders, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /app/root.tsx: -------------------------------------------------------------------------------- 1 | import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from 'remix'; 2 | import type { MetaFunction } from 'remix'; 3 | import install from '@twind/with-remix'; 4 | import config from '../twind.config.js'; 5 | 6 | install(config); 7 | 8 | export const meta: MetaFunction = () => { 9 | return { title: 'New Remix App' }; 10 | }; 11 | 12 | export default function App() { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | const anchorClass = 'transition opacity-75 hover:(opacity-100 text-emerald-500)'; 2 | 3 | export default function Index() { 4 | const links = [ 5 | { 6 | href: 'https://remix.run/tutorials/blog', 7 | text: '15m Quickstart Blog Tutorial', 8 | }, 9 | { 10 | href: 'https://remix.run/tutorials/jokes', 11 | text: 'Deep Dive Jokes App Tutorial', 12 | }, 13 | { 14 | href: 'https://remix.run/docs', 15 | text: 'Remix Docs', 16 | }, 17 | { 18 | href: 'https://twind.dev/', 19 | text: 'Twind Docs', 20 | }, 21 | ]; 22 | 23 | return ( 24 |
25 |
26 |

Welcome to Remix

27 | 36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remix-typescript-tailwind-quickstart", 3 | "private": true, 4 | "description": "Get started on Remix with TypeScript and Tailwind CSS in seconds.", 5 | "license": "Unlicense", 6 | "sideEffects": false, 7 | "scripts": { 8 | "build": "cross-env NODE_ENV=production remix build", 9 | "dev": "cross-env NODE_ENV=development remix dev", 10 | "format": "prettier --write \"./app/**/*.{js,jsx,ts,tsx}\" \"./*.{js,ts}\"", 11 | "lint": "eslint \"app/**/*.{ts,tsx}\"", 12 | "postinstall": "remix setup", 13 | "start": "cross-env NODE_ENV=production remix-serve build", 14 | "type-check": "tsc", 15 | "validate": "yarn lint && yarn type-check" 16 | }, 17 | "dependencies": { 18 | "@remix-run/react": "^1.2.3", 19 | "@remix-run/serve": "^1.2.3", 20 | "@twind/preset-autoprefix": "^1.0.0-next.37", 21 | "@twind/preset-tailwind": "^1.0.0-next.37", 22 | "@twind/with-remix": "^1.0.0-next.37", 23 | "cross-env": "^7.0.3", 24 | "react": "^17.0.2", 25 | "react-dom": "^17.0.2", 26 | "remix": "^1.2.3", 27 | "twind": "^1.0.0-next.37" 28 | }, 29 | "devDependencies": { 30 | "@remix-run/dev": "^1.2.3", 31 | "@remix-run/eslint-config": "^1.2.3", 32 | "@types/react": "^17.0.24", 33 | "@types/react-dom": "^17.0.9", 34 | "eslint": "^8.9.0", 35 | "eslint-config-prettier": "^8.5.0", 36 | "eslint-plugin-prettier": "^4.0.0", 37 | "prettier": "^2.5.1", 38 | "stylelint": "^14.5.3", 39 | "stylelint-config-recommended": "^7.0.0", 40 | "typescript": "^4.5.5" 41 | }, 42 | "engines": { 43 | "node": ">=14" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resir014/remix-typescript-tailwind-quickstart/ef769a725b604516628da0f7fb0fdd447418a333/public/favicon.ico -------------------------------------------------------------------------------- /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 | // devServerPort: 8002 11 | }; 12 | -------------------------------------------------------------------------------- /remix.env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["**/node_modules/**", "**/build/**", "**/.cache/**"], 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 6 | "isolatedModules": true, 7 | "esModuleInterop": true, 8 | "jsx": "react-jsx", 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "target": "ES2019", 12 | "strict": true, 13 | "baseUrl": ".", 14 | "paths": { 15 | "~/*": ["./app/*"] 16 | }, 17 | "noEmit": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /twind.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'twind'; 2 | import presetAutoprefix from '@twind/preset-autoprefix'; 3 | import presetTailwind from '@twind/preset-tailwind'; 4 | import tailwindConfig from './tailwind.config.js'; 5 | 6 | export default defineConfig({ 7 | ...tailwindConfig, 8 | presets: [presetAutoprefix(), presetTailwind()], 9 | }); 10 | --------------------------------------------------------------------------------