Remix seamlessly supports both JavaScript and TypeScript. If you name a file with a .ts or .tsx extension, it will treat it as TypeScript (.tsx is for TypeScript files with JSX in them). But it isn't required. You can write all your files as .js files if you don't want TypeScript.

The Remix CLI will not perform any type checking. Instead, you'll want to use TypeScript's tsc CLI yourself. A common solution is to add a typecheck script to your package.json:

  "name": "remix-app",
  "private": true,
  "sideEffects": false,
  "scripts": {
    "build": "remix vite:build",
    "dev": "remix vite:dev",
    "lint": "eslint --ignore-path .gitignore .",
    "start": "remix-serve ./build/index.js",
    "typecheck": "tsc"
  "dependencies": {
    "@remix-run/node": "latest",
    "@remix-run/react": "latest",
    "@remix-run/serve": "latest",
    "isbot": "^4.1.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  "devDependencies": {
    "@remix-run/dev": "latest",
    "@types/react": "^18.2.20",
    "@types/react-dom": "^18.2.7",
    "eslint": "^8.23.1",
    "typescript": "^5.1.6",
    "vite": "^5.1.4"
  "engines": {
    "node": ">=18.0.0"

Then you can run that script as part of continuous integration, alongside your tests.

Remix has TypeScript type definitions built-in as well. For example, the starter templates create a tsconfig.json file that includes the necessary types for Remix and Vite:

  "include": [
  "compilerOptions": {
    "lib": ["DOM", "DOM.Iterable", "ES2022"],
    "types": ["@remix-run/node", "vite/client"],
    "isolatedModules": true,
    "esModuleInterop": true,
    "jsx": "react-jsx",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "resolveJsonModule": true,
    "target": "ES2022",
    "strict": true,
    "allowJs": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "baseUrl": ".",
    "paths": {
      "~/*": ["./app/*"]

    // Vite takes care of building everything, not tsc.
    "noEmit": true

Note that the types referenced in the types array will depend on which environment you're running your app in. For example, there are different globals available in Cloudflare.

Docs and examples licensed under MIT