Если у вас есть два приложения на React + Vite — хост и микрофронт (remote) — и при общем старте через одну команду всё магически ок, а при раздельном запуске хост падает с 404 на remoteEntry.js, вы не одиноки. Разбираемся, почему так, и показываю рабочие рецепты.
Remote (react-vite/remote) публикует модуль:
// vite.config.js (remote)
import federation from '@originjs/vite-plugin-federation'
export default defineConfig({
plugins: [
federation({
name: "remote_app",
filename: "remoteEntry.js",
exposes: { './Button': './src/components/Button' },
shared: ['react','react-dom']
})
]
})
Host (react-vite/host) тянет remote:
// vite.config.js (host)
import federation from '@originjs/vite-plugin-federation'
export default defineConfig({
plugins: [
federation({
name: 'app',
remotes: {
remoteApp: 'http://localhost:5001/assets/remoteEntry.js',
},
shared: ['react','react-dom']
})
]
})
Импорт в хосте:
// src/App.jsx (host)
import Button from 'remoteApp/Button';
Проблема
При раздельном запуске: хост запрашивает http://localhost:5001/assets/remoteEntry.js, а remote ещё не готов или отдает другой путь/файл для текущего режима. Результат — 404 и:
GET …/remoteEntry.js net::ERR_ABORTED 404
Failed to fetch dynamically imported module…
При общем старте командой вида
pnpm --parallel --filter "./**" preview
оба сервиса поднимаются вместе, remote успевает начать отдавать remoteEntry.js, и хост не падает.
Почему это происходит на самом деле
Не “синхронный импорт”, а гонка запуска и/или несоответствие режимов.
В Vite:
В dev-режиме файл remote может быть “виртуальным” и сервиться на лету плагином.
В preview-фазе он отдается из dist после vite build.
Если хост открылся раньше, remote ещё не слушает порт 5001 или не собран — будет 404.
Ещё один частый кейс — запуск в разных режимах (например, host в preview, а remote в dev): тогда пути и поведение могут различаться.
Как воспроизвести и увидеть разницу
# Терминал 1
pnpm --filter "react-vite/remote" preview # или dev
# Терминал 2 (сразу или чуть раньше)
pnpm --filter "react-vite/host" preview
Если хост открыли раньше, получите 404 на remoteEntry.js.
pnpm --parallel --filter "./**" preview
Обычно remote успевает подняться к первому заходу в хост, и всё ок.
Рабочие рецепты
Запускайте в одном режиме на обоих концах.
dev+dev или build+preview для обоих. Не мешайте режимы.
Гарантируйте порядок (ждите remote).
Делайте URL remote динамическим через env
// 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 // например: http://localhost:5001/assets/remoteEntry.js
},
shared: ['react','react-dom']
})
]
}
})
Теперь можно явно указывать корректный URL для dev/preview через .env:
# .env.development
VITE_REMOTE_URL=http://localhost:5001/assets/remoteEntry.js
Добавьте простой retry/“обновите страницу”.
Иногда банально достаточно дождаться старта remote и перезагрузить хост: Vite подхватит remoteEntry.js.
Монорепо-скрипт “одним махом”.
Уже работающий подход — общая команда, которая стартует оба сервиса. Это снижает вероятность гонки.
Быстрый чеклист
Порты совпадают с конфигом? Host реально указывает на 5001, а remote слушает 5001?
Режимы согласованы? dev↔dev, preview↔preview.
Remote действительно отдает assets/remoteEntry.js? Откройте URL в браузере.
Порядок старта учтен? Ждите remote перед открытием host.
Итоги
Симптом: 404 на remoteEntry.js при раздельном запуске.
Причина: гонка старта и/или разные режимы сборки/предпросмотра.
Решение: стартовать синхронно, ждать готовность remote, унифицировать режимы, вынести URL remote в переменные окружения, при необходимости — добавить ожидание/ретраи.
Следуя этим практикам, вы избавитесь от «мистических» 404 и сделаете запуск предсказуемым как локально, так и в CI/CD.