Skip to content
Latchkey

Remix "Could not resolve" module in server/browser build in CI

Remix uses esbuild to bundle each route. "Could not resolve X" means the importer references a specifier that esbuild cannot map to a file or package on the runner. The message names the importing file and the missing target.

What this error means

The build fails with ERROR: Could not resolve "..." and a caret pointing at an import in one of your route or app files.

remix
x [ERROR] Could not resolve "~/utils/session"

    app/routes/login.tsx:2:28:
      2 | import { getSession } from "~/utils/session";
        |                             ^

Common causes

A wrong path or unconfigured alias

The ~/ alias or a relative path points at a file that does not exist (a rename, a case-sensitivity mismatch on Linux, or a missing extension).

A dependency not installed on the runner

A bare specifier resolves locally because the package is present, but CI ran without installing it (a devDependency skipped, or a dirty local node_modules).

How to fix it

Verify the path and case exactly

  1. Confirm the file exists at the exact path and casing the import uses (Linux runners are case-sensitive).
  2. Ensure the ~/ alias maps to app/ in tsconfig paths and any bundler config.
  3. For a bare package, add it to dependencies and reinstall.
tsconfig.json
// tsconfig.json
{ "compilerOptions": { "paths": { "~/*": ["./app/*"] } } }

Reinstall from the lockfile

Rule out a dirty local install by running a clean, lockfile-driven install on the runner.

Terminal
npm ci

How to prevent it

  • Match import casing to filenames since CI runs on case-sensitive Linux.
  • Keep tsconfig paths aliases in sync with the bundler.
  • Declare every imported package in dependencies, not just devDependencies.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →