Express error: Strict MIME type checking is enforced for module scripts per HTML spec

I’m trying to build a simple single page application via express.

Everything works fine until I start creating the classes, then the browser throws the following error:

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.

I was hoping that express.static() would make that go away but that’s not the case. I’m not very skilled yet with JS, Node and all that, so I apologize for anything dumb here.

My code is quite simple at the moment:

file architecture:

-server.js
-[frontend]
+-[static]
++-index.html
++-[js]
+++-index.js
+++-[views]
++++-AbstractView.js
++++-Home.js
-[node_modules]
-package.json

server.js

const express = require('express');
const path = require('path');

const App = express()

App.use('/static', express.static(path.resolve(__dirname, 'frontend', 'static')))
App.get('/*', (req, res) => {
    res.sendFile(path.resolve(__dirname, "frontend", "index.html"))
})
App.listen(3000, () => console.log("server running on port 3000"))

index.js:

import Home from "./views/Home"

const navigateTo = url => {
    history.pushState(null, null, url)
    router()
}

const router = async() => {
    const routes = [{
                path: '/',
                view: new Home()
            },
            // {
            //     path: '/projects',
            //     view: () => console.log('projects')
            // },
            // {
            //     path: '/about',
            //     view: () => console.log('about')
            // },
            // {
            //     path: '/studies',
            //     view: () => console.log('studies')
            // },
        ]
        // test routes for matches
    const potentialMatches = routes.map(route => {
        return {
            route: route,
            isMatch: location.pathname === route.path
        }
    })

    /**
     * NEED TO SET 404 PAGE!
     */
    let match = potentialMatches.find(potentialMatch => potentialMatch.isMatch)
    if (!match) {
        match = {
            route: routes[0],
            isMatch: true
        }
    }

    const view = new match.route.view()
    const content = document.querySelector("#app")
    content.innerHTML = await view.getHtml()
    console.log(content);
}

window.addEventListener("popstate", router);

document.addEventListener("DOMContentLoaded", () => {
    document.body.addEventListener("click", e => {
        if (e.target.matches("[data-link")) {
            e.preventDefault()
            navigateTo(e.target.href)
        }
    })
    router()
})

the Home class (extending and AbstractView general class):

import AbstractView from "./AbstractView";

export default class Home extends AbstractView {
    constructor() {
        super()
        this.setTitle("Home")
    }
    setTitle(title) {
        document.title = title
    }

    async getHtml() {
        return `<h1>Home</h1>`;
    }
}

The generic class:

export default class AbstractView {
    constructor() {

    }
    setTitle(title) {
        document.title = title
    }

    async getHtml() {
        return "";
    }
}

of course, the index.html file:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Paolo Iocca</title>
</head>

<body>
    <div class="menu" id="menu">
        <span class="menu__line"></span>
        <span class="menu__line"></span>
        <span class="menu__line"></span>
    </div>
    <aside class="aside">
        <nav class="nav">
            <ul class="nav__list">
                <li class="nav__list__item">
                    <a href="/" class="nav__list__item__link" data-link>home </a>
                </li>
                <li class="nav__list__item">
                    <a href="/projects" class="nav__list__item__link" data-link>projects</a
            >
          </li>
          <li class="nav__list__item">
            <a href="/about" class="nav__list__item__link" data-link>about </a>
                </li>
                <li class="nav__list__item">
                    <a href="/studies" class="nav__list__item__link" data-link>studies
            </a>
                </li>
            </ul>
        </nav>
    </aside>
    <div id="app"></div>
    <script type="module" src="/static/js/index.js"></script>
</body>

</html>

Hope you can help me with this. Thanks a lot.

_____ UPDATE

here’s the package.json file, just in case:

{
    "dependencies": {
        "express": "^4.17.1"
    },
    "name": "boilerplate-server",
    "version": "1.0.0",
    "main": "server.js",
    "devDependencies": {
        "nodemon": "^2.0.14",
        "serve-static": "^1.14.1"
    },
    "scripts": {
        "dev": "nodemon server",
        "start": "node server.js"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": ""
}

Answer

The statement

import Home from "./views/Home"

leads to the request GET /static/js/views/Home (without .js suffix), which the static middleware cannot serve. But now the next middleware takes over and responds with the index.html file (with Content-Type: text/html). That’s why you see a 200 OK status, but the browser complains about the wrong content type.

With

express.static(path.resolve(__dirname, 'frontend', 'static'),
  {extensions: ["js"]});

you would instruct the static middleware to silently add the .js suffix.