TypeScript

Fix Rollup failed to resolve import "/src/main.tsx" in Vite

Vite build fails with 'Rollup failed to resolve '/src/main.tsx'' when index.html is placed inside src and the script tag uses an absolute path. Fix it by correcting the file structure.

Mahdi Ben RhoumaMahdi Ben Rhouma
4 min read

If you're migrating a React app from Webpack to Vite and the build fails with [vite]: Rollup failed to resolve import "/src/main.tsx" from your index.html, the problem is almost always the placement of the index.html file relative to the root configured in vite.config.ts. Fix it by moving index.html to the project root and removing the custom root option. This exact error was reported in a popular Stack Overflow question where a Webpack‑to‑Vite migration produced the same diagnostic.

The error, decoded#

The full terminal output you see looks like this:

text
> tsc && vite build
 
vite v4.3.5 building for production...
✓ 2 modules transformed.
✓ built in 32ms
[vite]: Rollup failed to resolve import "/src/main.tsx" from "/Users/John/source/reddwarf/frontend/snap-web/src/index.html".
This is most likely unintended because it can break your application at runtime.
If you do want to externalize this module explicitly add it to
`build.rollupOptions.external`
error during build:
Error: [vite]: Rollup failed to resolve import "/src/main.tsx" from "/Users/John/source/reddwarf/frontend/snap-web/src/index.html".
    at viteWarn (...)
    at onwarn (...)
    at onRollupWarning (...)
    ...

It happens when you run vite build. The dev server (vite dev) often does not reproduce this error because its module resolution is more lenient — it can resolve some paths that the production Rollup build rejects. This is a common gotcha for developers who test everything with vite dev and only hit resolution failures during a build.

The error suggests adding the import to build.rollupOptions.external. Don’t do that. Externalising your own entry module will strip it from the bundle and cause runtime failures — the suggestion is a generic fallback, not the actual fix for this structural issue.

Why Vite can’t resolve the import#

Vite uses the root property in vite.config.ts to determine where the project begins. The default root is the directory where you run vite, usually the project root (my-app/). In a default Vite scaffold, the index.html lives there and contains:

html
<script type="module" src="/src/main.tsx"></script>

The path /src/main.tsx is an absolute path from the project root — not relative to the HTML file, and not relative to the configured root. It says: “starting from the root of the filesystem as Vite sees it, go to src/main.tsx.”

In the failing setup, the developer moved index.html inside src/ and then set root: path.join(__dirname, 'src') in vite.config.ts. The intent was to mimic the Webpack structure where the entry HTML sits inside src. But now Vite’s root is src, and the script tag still says /src/main.tsx. To Vite, this means “start at the root (which is src/), then go to src/main.tsx” — so it looks for a file at <project>/src/src/main.tsx. That file doesn’t exist, and Rollup throws the resolution error.

The relevant problematic config:

ts
// vite.config.ts — the broken version
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
 
export default defineConfig({
  root: path.join(__dirname, 'src'),   // <— root becomes <project>/src
  plugins: [react()],
  build: {
    outDir: "build"
  }
});

With that root, the script tag’s /src/main.tsx tries to resolve to <project>/src/src/main.tsx and the build fails.

Fix: restore the default file structure#

The most straightforward fix — and what the official Vite documentation expects — is to move index.html back to the project root and remove the custom root property. If index.html exists at that root locally but the Docker build reports it as the unresolved entry, check Docker's filtered build context before changing the JavaScript import.

Before you move files, check your current layout. Run tree -L 2 (install it via brew install tree on macOS if not present) from the project directory. The broken structure looks like this:

bash
.
├── node_modules
├── package.json
├── src
│   ├── App.tsx
│   ├── main.tsx
│   ├── index.html           # <— index.html inside src
│   └── vite-env.d.ts
├── tsconfig.json
└── vite.config.ts           # contains root: 'src'

Your index.html inside src might reference the entry point with an absolute path like <script type="module" src="/src/main.tsx"></script>. That path is relative to the project root (the directory containing vite.config.ts), but with root set to src, it becomes src\src\main.tsx and causes the failure.

Move the index.html to the project root:

bash
mv src/index.html index.html

Now your tree should show:

bash
.
├── index.html               # <— now at project root
├── node_modules
├── package.json
├── src
│   ├── App.tsx
│   ├── main.tsx
│   └── vite-env.d.ts
├── tsconfig.json
└── vite.config.ts           # no root property

The index.html now sits next to vite.config.ts. Vite automatically treats the directory containing vite.config.ts as the root, so the absolute path /src/main.tsx correctly resolves to <project>/src/main.tsx.

Some developers attempt a workaround that does not involve moving index.html: they keep root: 'src' and change the script tag to "./main.tsx" (relative to the HTML file). This lets Vite resolve the file because root is src, and ./main.tsx correctly points to src/main.tsx. While this clears the resolution error, it introduces fragility. The Vite documentation strongly advises keeping index.html at the project root. Moving it to a subdirectory and changing to relative paths can break imports of other assets (like favicon or static files) that still rely on absolute paths from the root. Stick to the recommended layout: index.html at the project root, no custom root in the config.

The config file after the fix:

ts
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
 
export default defineConfig({
  plugins: [react()],
  build: {
    outDir: "build"   // keep your desired output dir
  }
});

Notice that the root property is completely removed. With this setup, vite build will resolve /src/main.tsx relative to the project directory, just as it should.

Verify the build#

After moving index.html and removing the root property, run the build again:

bash
npm run build

A successful build will output something like:

text
vite v4.3.5 building for production...
✓ 32 modules transformed.
build/index.html               0.46 kB
build/assets/index-abc123.js   145.32 kB │ gzip: 48.76 kB
build/assets/index-abc123.css   0.87 kB │ gzip:  0.44 kB
✓ built in 1.23s

No more Rollup resolution errors. The HTML, JavaScript, and CSS are written to the build/ directory (or whatever you set as build.outDir). Open build/index.html and verify that the <script> tag points to the hashed asset file. That file is your bundled application entry.

If you still see the same resolution error, double‑check the file placement with ls:

bash
ls -l src/main.tsx   # should exist
ls -l index.html     # must be at the project root

Also inspect your vite.config.ts to make sure no root property remains. A leftover root: 'src' will cause the error to reappear even after moving the file, because Vite will once again resolve the absolute /src/main.tsx path starting from src/.

If the build fails for a different reason — for instance, a TypeScript type error or a missing dependency — address that separately. The resolution error should be gone. If it persists, check that your package.json build script doesn't override the config or pass an explicit root via a CLI flag like vite build --root src. That would reintroduce the problem.

IDE configuration#

If you’re using WebStorm or IntelliJ IDEA, the Node.js interpreter configured in the IDE can mask or multiply build problems. In the Stack Overflow case, the developer had an outdated Node 14 interpreter set for the project while nvm was using Node 18. This mismatch caused ESLint and the TypeScript service to fail with extraneous errors like:

plaintext
Error: Cannot find module 'eslint-plugin-react'
Error: Cannot find module 'typescript'

These errors appeared in the IDE’s terminal and tool window alongside the Rollup failure, making it harder to isolate the real issue.

Verify your active Node version outside the IDE:

bash
node -v          # e.g., v18.17.1
which node       # e.g., /Users/yourname/.nvm/versions/node/v18.17.1/bin/node

In WebStorm, open Settings → Languages & Frameworks → Node.js. In the Node interpreter field, click the browse button and select the path reported by which node. Alternatively, if you use nvm, you can choose the .nvm directory’s interpreter. Apply the change and restart the ESLint/TypeScript services (usually automatically after changing the interpreter). The spurious module‑not‑found errors should disappear.

Once the IDE interpreter matches the CLI version, the only error you’ll see when running vite build is the Rollup resolution failure. Fixing that (moving index.html) then lets the build succeed cleanly inside the IDE as well.

FAQ#

Why does the error only appear during vite build and not during vite dev?#

Vite’s development server uses esbuild’s resolution logic, which is more forgiving than Rollup’s. It can sometimes resolve paths that are technically incorrect. The production build, however, uses Rollup for bundling and enforces strict resolution — that’s when the mismatch becomes a hard error. A fourth build-only failure is Rollup refusing a top-level await: dev runs it fine through esbuild, and the production build fails until build.target is raised past the default, old-browser list.

Should I add the module to build.rollupOptions.external as the error suggests?#

No. External elements are not bundled; they are expected to be provided at runtime (e.g., a global variable). Adding "/src/main.tsx" to external would cause the build to complete, but your bundle would be missing the application entry point and break in the browser.

Further reading: Fix "Missing specifier" & npx tailwindcss init Errors, Fix: import.meta.env Undefined on Production Build (Vite). Also see Fix Vite: Preprocessor dependency "sass" not found.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.