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

  1. Open the live site, press F12, and open the Network tab.
  2. Reload, then filter by JS.
  3. 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:

HostBuild commandOutput directory
Vercelnpm run builddist (framework preset Vite)
Netlifynpm run builddist
Cloudflare Pagesnpm run builddist
GitHub Pagesa build step in your workflowupload 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.html is not cached for long. Hashed files in /assets/ can be cached forever, but index.html should be revalidated on every visit. On Netlify or Cloudflare Pages, a public/_headers file 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

  1. Run npm run build and npx vite preview locally. If it works there, the problem is the host settings, not your code.
  2. Deploy, then load the home page and a deep link like /dashboard in a private window.
  3. In the Network tab, every .js file should return status 200 with type javascript.
Still stuck? No shame in it: these bugs hide in settings your AI builder cannot see. Send us the error and your repo and we will fix it as a pull request within 24 hours, explained in plain English, for $49. Not fixed means full refund.
Request a fix

Last updated 2026-10-19.