javascriptroom blog

Webpack Dev Server Error: Refused to Execute Script Due to MIME Type ('text/html') – Fix for React/Redux with Node.js, Koa, and Passport.js Authentication

If you’re building a modern web application with React/Redux (frontend), Node.js/Koa (backend), and Passport.js (authentication), you’ve likely encountered the frustrating error: "Refused to execute script from 'http://localhost:3000/static/js/main.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled."

This error occurs when the browser expects a JavaScript (or CSS) file but receives an HTML response (e.g., a 404 page, login page, or index.html) instead. The root cause? A misconfiguration in how your frontend, backend, or authentication layer handles routing, static assets, or request/response headers.

In this blog, we’ll demystify this error, explore its common causes in your tech stack, and walk through step-by-step fixes to get your app running smoothly.

2026-02

Table of Contents#

  1. Understanding the Error
  2. Common Causes in React/Redux + Node.js/Koa + Passport.js Stacks
  3. Step-by-Step Fixes
  4. Verification
  5. Conclusion
  6. References

Understanding the Error#

What’s a MIME Type?#

MIME (Multipurpose Internet Mail Extensions) types tell browsers how to interpret files. For example:

  • text/html for HTML files,
  • application/javascript for JavaScript,
  • text/css for CSS.

Browsers enforce strict MIME type checking for security: if a script tag references a file with a MIME type like text/html, the browser refuses to execute it (since HTML isn’t executable code).

Why Does This Happen in Your Stack?#

Your React/Redux app (a Single-Page Application, SPA) relies on client-side routing (e.g., React Router). When the browser requests a JavaScript bundle (e.g., main.js), it expects a application/javascript MIME type. But if your Koa backend or Passport.js authentication redirects the request (e.g., to a login page) or serves an HTML error page (e.g., 404), the response MIME type becomes text/html—triggering the error.

Common Causes in the Stack#

Let’s break down the most likely culprits in your specific tech stack:

1. Koa Backend Routing Conflicts#

Koa may misroute requests for static assets (JS/CSS) to HTML pages. For example:

  • If you haven’t configured a catch-all route for SPA client-side routing, Koa might return a 404 HTML page instead of your React bundle.
  • API routes defined after static file middleware could override asset requests.

2. Misconfigured Webpack Dev Server#

Webpack Dev Server (WDS) serves bundled assets in development. Issues here include:

  • Incorrect publicPath or contentBase settings, causing WDS to serve assets from the wrong path.
  • Missing historyApiFallback (critical for SPAs), leading to 404 errors on client-side routes.

3. Passport.js Authentication Redirects#

Passport.js redirects unauthenticated users to a login page (e.g., /login), which returns HTML. If:

  • Your auth middleware is applied to static asset routes (e.g., /static/js/main.js),
  • An unauthenticated request for main.js gets redirected to /login (HTML),

the browser receives HTML instead of JavaScript—hence the MIME type error.

4. CORS or MIME Type Header Issues#

  • CORS misconfiguration can block browsers from reading MIME type headers.
  • Middleware (e.g., custom error handlers) might override MIME type headers for static assets.

Step-by-Step Fixes#

Let’s resolve each cause with actionable fixes.

Fix 1: Correct Koa Backend Routing for SPAs#

Koa must prioritize API routes, serve static assets, and then fall back to index.html for client-side routes. Here’s how:

Step 1. Serve Static Files with koa-static#

First, use koa-static to serve your React build assets (JS, CSS, images). Install it:

npm install koa-static --save  

Then, configure Koa to serve static files before defining routes:

// server/src/app.js  
const Koa = require('koa');  
const static = require('koa-static');  
const path = require('path');  
 
const app = new Koa();  
 
// Serve static assets (React build files)  
const staticPath = path.join(__dirname, '../../client/build'); // Path to your React build folder  
app.use(static(staticPath));  
 
// Define API routes FIRST (before catch-all)  
app.use(require('./routes/api')); // Your API routes (e.g., /api/users)  
 
// Catch-all route: Serve index.html for client-side SPA routes  
app.use(async (ctx) => {  
  // Only handle GET requests for SPA routes  
  if (ctx.method === 'GET' && !ctx.path.startsWith('/api')) {  
    ctx.type = 'text/html';  
    ctx.body = fs.readFileSync(path.join(staticPath, 'index.html'), 'utf8');  
  }  
});  

Key Notes:#

  • Order Matters: API routes (/api/*) must be defined before the catch-all route. Otherwise, Koa will serve index.html for API requests!
  • Use fs.readFileSync to read index.html (ensure fs is required: const fs = require('fs')).

Fix 2: Configure Webpack Dev Server Properly#

In development, Webpack Dev Server (WDS) bundles and serves assets. Fix WDS with these settings:

Step 1. Update webpack.config.js#

Ensure your Webpack config includes:

  • publicPath: '/': Serves assets from the root path.
  • historyApiFallback: true: Redirects 404s to index.html (critical for SPAs).
  • proxy: Forwards API requests to your Koa backend (avoids CORS issues).

Example webpack.config.js:

// client/webpack.config.js  
module.exports = {  
  // ... other config (entry, output, loaders)  
  devServer: {  
    port: 3000, // Frontend dev port  
    hot: true, // Enable HMR  
    open: true,  
    historyApiFallback: true, // Fix 404s on client-side routes  
    publicPath: '/', // Serve assets from root  
    proxy: {  
      // Proxy API requests to Koa backend (e.g., /api/* → http://localhost:4000/api/*)  
      '/api': 'http://localhost:4000',  
      // Proxy auth routes (e.g., /login) if needed  
      '/login': 'http://localhost:4000'  
    }  
  }  
};  

Step 2. Verify package.json Script#

Ensure your start script uses WDS:

// client/package.json  
"scripts": {  
  "start": "webpack serve --config webpack.config.js"  
}  

Fix 3: Adjust Passport.js Authentication Flow#

Prevent Passport from redirecting static asset requests. Apply auth middleware only to API routes.

Step 1. Scope Auth Middleware to API Routes#

Modify your Koa app to apply Passport auth middleware only to /api/* routes:

// server/src/app.js  
const passport = require('koa-passport');  
const authMiddleware = require('./middleware/auth'); // Your Passport auth middleware  
 
// Apply auth ONLY to API routes  
app.use(async (ctx, next) => {  
  if (ctx.path.startsWith('/api')) {  
    await authMiddleware(ctx, next); // e.g., passport.authenticate('jwt')  
  } else {  
    await next(); // Skip auth for static assets/login  
  }  
});  

Step 2. Ensure Login/Static Routes Are Unprotected#

Your login page (/login) and static assets (/static/*) should be accessible without authentication. Confirm:

  • koa-static serves /static/js/main.js unprotected.
  • The /login route returns HTML (no auth required).

Fix 4: Verify MIME Types and CORS Headers#

Ensure Koa serves assets with correct MIME types and CORS headers.

Step 1. Validate MIME Types with koa-static#

koa-static automatically sets MIME types for common files (e.g., .jsapplication/javascript). To confirm, check the Content-Type header in your browser’s DevTools (Network tab) for main.js:
MIME Type Check in DevTools

Step 2. Fix CORS with koa-cors#

If your frontend (port 3000) and backend (port 4000) run on different ports, enable CORS:

Install koa-cors:

npm install koa-cors --save  

Configure CORS in Koa:

// server/src/app.js  
const cors = require('koa-cors');  
 
app.use(cors({  
  origin: 'http://localhost:3000', // Allow frontend origin  
  allowHeaders: ['Content-Type', 'Authorization']  
}));  

Verification#

Test if the error is fixed:

  1. Clear Browser Cache: Old cached assets can cause false positives.
  2. Restart Servers: Stop and restart both Koa backend and Webpack Dev Server.
  3. Check DevTools Network Tab:
    • Request http://localhost:3000/static/js/main.js (or your bundle path).
    • Ensure the Status is 200 OK.
    • Check Content-Type header: Should be application/javascript.
    • Verify the response is JavaScript, not HTML.

Conclusion#

The "Refused to execute script due to MIME type" error is almost always a routing or configuration issue. By:

  • Prioritizing API routes and using a catch-all for SPAs in Koa,
  • Configuring Webpack Dev Server with historyApiFallback and proxy,
  • Scoping Passport auth to API routes,
  • Ensuring proper CORS and MIME type headers,

you’ll resolve the error and keep your React/Redux app running smoothly.

References#