Fixing Vite Module Federation Errors When Starting Apps Separately

August, 22nd 2025 3 min read

Vite Module Federation 404 Errors

When using Vite Module Federation with multiple React (or Vue) apps — typically a host and a remote (microfrontend) — everything may work fine when launched together with a single command. But if you start them separately, the host can fail with a dreaded 404 on remoteEntry.js:

plaintext
12
      GET http://localhost:5001/assets/remoteEntry.js net::ERR_ABORTED 404
Failed to fetch dynamically imported module…
    

This guide explains why this happens and provides practical recipes to fix it.


The Setup

Remote (Microfrontend)

js
1234567891011121314
      // vite.config.js (remote)
import federation from '@originjs/vite-plugin-federation'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    federation({
      name: "remote_app",
      filename: "remoteEntry.js",
      exposes: { './Button': './src/components/Button' },
      shared: ['react','react-dom']
    })
  ]
})
    

Host (Main App)

js
123456789101112131415
      // vite.config.js (host)
import federation from '@originjs/vite-plugin-federation'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    federation({
      name: 'app',
      remotes: {
        remoteApp: 'http://localhost:5001/assets/remoteEntry.js',
      },
      shared: ['react','react-dom']
    })
  ]
})
    

Usage in Host

jsx
123456
      // src/App.jsx (host)
import Button from 'remoteApp/Button';

export default function App() {
  return <Button />
}
    

The Problem

  • When running separately:
    The host immediately requests http://localhost:5001/assets/remoteEntry.js. If the remote isn’t ready yet or is serving a different path depending on mode (dev vs preview), you get a 404.

  • When running together:
    If started with something like:

    bash
    1
          pnpm --parallel --filter "./**" preview
        

    both services launch in sync, the remote is ready, and everything works.

Root cause:

  • Race condition — host tries to fetch remote before it’s serving.
  • Mode mismatch — dev builds remoteEntry virtually, preview serves it from dist.

Visual Diagram: Race Condition

text
123456789
      Timeline (separate start)

[Host starts] ----------------------> tries http://localhost:5001/assets/remoteEntry.js (404)
                    [Remote not ready yet] -----> builds/serves remoteEntry.js

Timeline (parallel start)

[Host starts] ----------------------> request remoteEntry.js
[Remote ready in time] --------------> serves remoteEntry.js ✔
    

How to Reproduce

bash
12345
      # Terminal 1
pnpm --filter "react-vite/remote" preview

# Terminal 2 (right after or even earlier)
pnpm --filter "react-vite/host" preview
    

If the host starts too soon → 404 on remoteEntry.js.


How to Fix It

Here are working recipes to eliminate the error:

1. Use the Same Mode for Both

  • Run both apps in dev, or both in preview.
  • Avoid mixing host preview with remote dev.
bash
1234567
      # ✅ good
pnpm --filter "react-vite/remote" dev
pnpm --filter "react-vite/host" dev

# ✅ good
pnpm --filter "react-vite/remote" preview
pnpm --filter "react-vite/host" preview
    

2. Ensure Startup Order

Wait for remote to start before opening the host.


3. Use Environment Variables for Dynamic URLs

js
1234567891011121314151617
      // vite.config.js (host)
import { defineConfig, loadEnv } from 'vite'
import federation from '@originjs/vite-plugin-federation'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), '')
  return {
    plugins: [
      federation({
        remotes: {
          remoteApp: env.VITE_REMOTE_URL
        },
        shared: ['react','react-dom']
      })
    ]
  }
})
    

.env.development

plaintext
1
      VITE_REMOTE_URL=http://localhost:5001/assets/remoteEntry.js
    

Now dev/preview builds can each load the correct remote entry.


4. Add a Retry or Manual Refresh

Sometimes the simplest fix is:

  • Start host,
  • Wait for remote,
  • Refresh the page — Vite will pick up the remote entry.

5. Use a Monorepo Script

Keep both host and remote in one workspace and run them in parallel:

bash
1
      pnpm --parallel --filter "./**" preview
    

This reduces the chance of race conditions.


Quick Checklist

  • ✅ Ports match (host expects 5001, remote serves 5001).
  • ✅ Same mode: dev ↔ dev, preview ↔ preview.
  • ✅ Remote really serves remoteEntry.js (open in browser).
  • ✅ Startup order respected (remote first, host second).

Conclusion

If you see 404 on remoteEntry.js when running Vite Module Federation apps separately:

  • The cause is usually startup race conditions or mismatched modes.
  • Solutions:
    • Run both in the same mode,
    • Wait for the remote before starting host,
    • Externalize the remote URL into env variables,
    • Add retries or launch with a monorepo script.

Following these practices makes your setup predictable and stable, both locally and in CI/CD pipelines.