Start with the exact build error
[vite]: Rollup failed to resolve import "./components/Header" from "src/App.tsx"
Record the importer and specifier exactly. Run the production command locally from the same workspace root:
npm ci npm run build
If the failure only appears on Vercel, Netlify or another Linux builder, filename case and omitted build-context files move to the top of the list.
Classify the specifier
| Specifier | Resolution starts from | First check |
|---|---|---|
./Header or ../lib/api | The importing file’s directory | Exact path, extension candidates and filename case. |
/src/main.ts | Vite project root | Correct root and file inclusion. |
@/components/Header | resolve.alias | Alias exists in Vite and maps to an absolute filesystem path. |
react or @scope/pkg/subpath | Package resolution | Dependency is installed in this workspace and the subpath is exported. |
import(`./pages/${name}.tsx`) | Vite dynamic-import transform | The expression follows Vite’s supported variable limits or uses import.meta.glob. |
1. Fix filename case for Linux
Vite’s troubleshooting guide explicitly calls out projects developed on case-insensitive Windows/macOS filesystems and built on case-sensitive Linux. Compare Git’s recorded path—not only Finder or Explorer—with the import:
git ls-files | sort git status --short git diff --cached --name-status
If the only change is case, make sure Git records it. A two-step rename is often clearer across filesystems:
git mv src/components/header.tsx src/components/header.tmp git mv src/components/header.tmp src/components/Header.tsx
Then use exactly ./components/Header everywhere and run the build again.
2. Fix aliases in Vite, not only the editor
A TypeScript paths entry can make editor navigation work while the bundler still lacks an equivalent resolver rule. Vite documents that filesystem alias replacements should be absolute paths:
// vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
export default defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})Keep TypeScript, test runner and Vite aliases aligned. Do not add a plugin until you know which configuration source is intended to be authoritative.
3. Fix a missing dependency or blocked package subpath
For a bare specifier, inspect the correct workspace manifest and installed tree:
npm ls PACKAGE_NAME npm explain PACKAGE_NAME npm view PACKAGE_NAME exports
Install a package only if the import is intentional and the package is truly absent. Commit the manifest and lockfile. If the package exists but its exports field does not expose the requested subpath, import a documented public entry instead of reaching into internal files.
4. Confirm the file reached the build context
AI-generated changes and local-only files can work in a dev session but never reach CI. Check:
- The file is tracked or deliberately generated before
vite build. .gitignore, Docker ignore rules, workspace filters and platform include rules do not omit it.- The deployment root points at the workspace containing the importer and dependency manifest.
- Code generation runs before—not after—the production bundle.
5. Handle dynamic imports deliberately
Vite documents constraints for variable dynamic imports: the import must be statically analyzable, variables normally represent a filename only one level deep, and a file extension is required. For a broader known set, use a literal import.meta.glob pattern.
const pages = import.meta.glob('./pages/*.tsx')
const load = pages[`./pages/${name}.tsx`]Validate that load exists before calling it. Do not construct arbitrary filesystem paths from user input.
Why “externalize it” is usually wrong for app source
Marking an unresolved application module as external suppresses bundling without creating the file in the browser. Externalization is appropriate only when the runtime truly provides that dependency and the deployment architecture documents it. It is not a generic fix for a misspelled component or missing npm package.
Verification gate
- A clean
npm cifollowed bynpm run buildsucceeds. - The test runs on Linux or the same container/platform image as production.
- Git records the exact filename case.
- No unresolved import warning was converted into a runtime 404.
- The deployed workspace root and lockfile match local testing.