💭 where are you taking me?

webdev

⚡♦️ Fixing module preload paths within a Rails/Vite setup

Hello and happy new year! Today I'm writing about an asset related issue in a Rails application. Enjoy!

Several months ago, I migrated my Rails app from Webpack(er) to Vite, and I’ve been quite happy with the switch. Vite is blazingly fast, well-documented, and works seamlessly once properly configured.

This morning, while resolving CSP violations (all fixed now, by the way 🎉), I encountered the following error in the browser’s developer console:

Failed to load module script: Expected a JavaScript-or-Wasm 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.

(This message comes from Chromium; it may appear slightly differently in other browsers.)

TL;DR

Stick around for the full story:

Nothing on my site appeared broken, but I wanted to resolve the issue anyway so I put on my debugging glasses.

My first findings in order:

  1. The problem doesn’t occur on every page load
  2. In the network tab, I noticed that certain requests were failing (highlighted in red)
  3. The trigger was the import of chart library modules
  4. The response content type of the failing requests was text/html, even though a JS file was requested
  5. The issue occurred only on specific servers (visible as i have a header showing the server name)

Given these observations, it became clear that the issue was tied to my asset host configuration. In short, I have one server that serves assets via a subdomain and also functions as a regular app server.

But why did the site still work despite failing imports?

A bit of research revealed that Vite preloads modules by default (see MDN on modulepreload). In a nutshell, JavaScript modules are preloaded for performance gains (they are not actually used until explicitly imported in your JS).

Since this behavior is controlled via an HTML attribute, I checked my page source and found several modulepreload links. These used relative paths, meaning they pointed to myapp.com/assets/foo.js instead of assets.myapp.com/assets/foo.js. As a result, requests hit the application servers, which responded with HTML instead of the JS file.

So why was Vite generating links with relative paths?

After some digging, I found a related issue. The fix is to set Rails.application.config.action_controller.asset_host (see the Vite Ruby docs).

Once configured, Vite generates absolute paths in the preload tags, resolving the issue.

However, I noticed URLs with double leading slashes (e.g., //assets.myapp.com/...). To fix this, I adjusted the Vite config:

import { defineConfig } from "vite"
import RubyPlugin from "vite-plugin-ruby"

export default defineConfig({
  base: "./",
  // other build configuration omitted
})

TL;DR

Adapt the following two things (first one is mandatory) to fix the issue:

# config/application.rb
# Can be set via environment variable
config.action_controller.asset_host = "https://yourassethost.yourapp.com" 
// vite.config.mts
export default defineConfig({
  base: "./",
  // other build configuration omitted
})

I needed to fix some specs and could deploy to staging for acceptance 😌

I hope this helps you if you run into a similar issue. Enjoy your day, and stay curious!

#rails #vite #viteruby #webdev