Fix guides / Blank page: "Failed to load module script ... MIME type of text/html"
How to fix "Failed to load module script" with MIME type "text/html"
The app works in the builder preview, you deploy it, and the live site is a white page. The console says Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec. Your JavaScript is fine. The server simply answered a request for a .js file with an HTML page.
What the error really means
Vite builds your app into an index.html plus hashed files such as /assets/index-4f9a1c.js. When the browser asks for that file and it is not where the browser looked, most hosts with a single-page app fallback return index.html instead of a 404. The browser expected JavaScript, received HTML, and refuses to run it. So the real question is always: which URL did the browser request, and why is there no file there?
Step 1: find the failing URL
- Open the live site, press F12, and open the Network tab.
- Reload, then filter by JS.
- Click the script that failed. Look at the full Request URL and open the Response tab. If you see
<!doctype html>, that confirms the fallback served your page instead of the script.
The shape of that URL tells you which cause below is yours.
Cause 1: the app lives in a subfolder (GitHub Pages and similar)
If your site is at https://you.github.io/my-app/, the build still points to /assets/... at the domain root, where nothing exists. Set Vite's base to the subfolder and rebuild:
// vite.config.ts
export default defineConfig({
base: '/my-app/', // the repo or folder name, with both slashes
plugins: [react()],
});
If you use React Router, also tell the router about the subfolder so links and refreshes stay inside it:
<BrowserRouter basename={import.meta.env.BASE_URL}>
On a root domain such as myapp.com or myapp.vercel.app, leave base at the default /.
Cause 2: relative asset paths break on nested routes
Some AI builders set base: './' to make previews work. On the home page that is fine, but open /dashboard/settings directly and the browser looks for /dashboard/assets/index-4f9a1c.js. That file does not exist, the fallback returns HTML, and you get this error only on deep links or after a refresh. The fix is an absolute base:
base: '/',
Then check that dist/index.html after a build references /assets/... and not ./assets/....
Cause 3: the host is serving your source folder, not the build
If the failing URL is /src/main.tsx, the host is serving the raw project instead of the built output. Browsers cannot run that file directly. Fix the deploy settings:
| Host | Build command | Output directory |
|---|---|---|
| Vercel | npm run build | dist (framework preset Vite) |
| Netlify | npm run build | dist |
| Cloudflare Pages | npm run build | dist |
| GitHub Pages | a build step in your workflow | upload dist, not the repo root |
Create React App projects use build instead of dist.
Cause 4: it broke right after a redeploy (old tabs, stale cache)
Each build creates new hashed file names and the old ones disappear. Someone who still has the old page open, or a CDN that cached the old index.html, asks for a file that no longer exists. You often see a related message too: Failed to fetch dynamically imported module. Fixes:
- Make sure
index.htmlis not cached for long. Hashed files in/assets/can be cached forever, butindex.htmlshould be revalidated on every visit. On Netlify or Cloudflare Pages, apublic/_headersfile does this:/index.html Cache-Control: no-cache /assets/* Cache-Control: public, max-age=31536000, immutable
- Recover gracefully when a lazy chunk is gone. Vite fires an event you can catch to reload once:
window.addEventListener('vite:preloadError', () => { window.location.reload(); });
Cause 5: a rewrite rule that catches everything
A single-page app needs a fallback so /dashboard loads index.html. A rule written too broadly can also capture real files. On Vercel, the filesystem is checked before rewrites, so this safe pattern works:
{ "rewrites": [ { "source": "/(.*)", "destination": "/index.html" } ] }
If you used routes instead (the older format), put { "handle": "filesystem" } first. On Netlify, a _redirects line of /* /index.html 200 is correct because existing files win, but a line marked with ! (forced) overrides them and causes exactly this error.
Check the fix
- Run
npm run buildandnpx vite previewlocally. If it works there, the problem is the host settings, not your code. - Deploy, then load the home page and a deep link like
/dashboardin a private window. - In the Network tab, every
.jsfile should return status 200 with typejavascript.
Request a fix
Last updated 2026-10-19.