vitral

Micro-frontends de qualquer framework numa página só, sem configuração de build no host. React, Next, Vue, Nuxt, Angular, Svelte, Solid, Preact, Lit e JS puro.
131
Install
npm install vitral
Latest Version:0.7.2
Vue:3
License:MIT
Last Updated:Oct 27, 2026
Links: GitHub  ·  npm
Maintainer: pacheco_code

vitral. Feito de partes. Visto como um todo.

CI npm tamanho licença MIT

Documentação · Começar · Por que o vitral · Benchmark · English

Junte micro-frontends de qualquer framework numa página só, sem configuração de build no host.

Cada equipe publica o próprio pedaço da página (um remote) com vite build. O host recebe a URL de cada remote e monta tudo no navegador.

Por que usar:

  • Zero configuração no host. Sem plugin de build: só configure({ remotes }) com a URL de cada remote.
  • Qualquer framework. React, Next, Vue, Nuxt, Angular, Svelte, Solid, Preact, Lit e JS puro na mesma página. Sem combinar versão de React entre equipes.
  • Deploy independente. Cada equipe publica quando quiser. O host vê a versão nova sem novo build.
  • CSS e erros isolados. O CSS de um remote não muda o resto da página. Um remote fora do ar mostra o próprio erro, e o resto continua funcionando.
  • Seguro. O JS só roda se vier de uma origem permitida e se o hash SHA-384 bater.
  • Leve e rápido. 3,4 kB de host. Primeira visita 57% mais rápida que o Module Federation e troca de área 4,6 vezes mais rápida que o Next.js Multi-Zones.
npm install vitral

Três equipes (React, Vue, Angular) publicam um vitral.json cada uma num CDN. O host lê as URLs com configure e monta os três remotes na mesma página.

Comparação

Module Federation single-spa Next.js Multi-Zones vitral
Configuração no host Plugin no build, combinado com cada remote Root config e import map Um app Next por zona e rewrites Nenhuma. Só configure()
Frameworks Combinar versões em shared Um adaptador e um ciclo de vida por framework Só Next Qualquer um, sem combinar versão
Divide a página em Pedaços Apps por rota Rotas (cada zona é uma página) Pedaços ou rotas
Trocar de área Sem recarregar Sem recarregar Recarrega a página e baixa Next e React de novo Sem recarregar
Isolamento de CSS Nenhum Nenhum Por página Escopo automático ou Shadow DOM
Remote fora do ar Erro sem tratamento derruba a página A tela de erro é com você A zona inteira fica fora Só aquele remote mostra erro, com retry
Código de host (brotli) 19,4 kB 5,8 kB Runtime do Next em cada zona 3,4 kB

Medido no benchmark público (Chromium, rede Slow 4G, mediana de 7 visitas):

  • 57% menos tempo na primeira visita que o Module Federation com React compartilhado (1157 ms contra 2673 ms, 1 remote).
  • 4,6 vezes mais rápido para trocar de área que o Next.js Multi-Zones (184 ms contra 850 ms), com 63% menos bytes para abrir duas áreas.
  • 10 frameworks cobertos por testes de ponta a ponta no CI, com host e remote de frameworks diferentes, em Chromium, Firefox, WebKit e mobile.

O método e as versões usadas estão na página do benchmark.

Começo rápido

Para gerar host e remote prontos: npm create vitral@latest.

1. No remote, exporte o componente e use o plugin:

// src/App.jsx (ou .vue, .svelte, .tsx…)
export default function Carrinho({ userId }) { … }
// vite.config.js
import react from '@vitejs/plugin-react'
import vitral from 'vitral/vite'

export default { plugins: [react(), vitral()] }

O plugin descobre o framework e embrulha o export default com o adaptador certo. A entrada padrão é src/remote.* ou src/App.*.

vite build gera o JS, o CSS e um vitral.json. Publique a pasta dist em qualquer CDN com CORS.

2. No host, registre a URL e use o componente:

import { configure } from 'vitral'
import { Remote } from 'vitral/react'

configure({
  remotes: { carrinho: 'https://cdn.empresa.com/carrinho/vitral.json' },
})

export function App() {
  return (
    <Remote
      name="carrinho"
      props={{ userId: 42 }}
      fallback={<p>Carregando…</p>}
      error={(erro, tentarDeNovo) => <button onClick={tentarDeNovo}>Tentar de novo</button>}
    />
  )
}

Pronto. Mudar props atualiza o remote sem remontar.

Sem build no host, numa página HTML qualquer:

<script type="module" src="https://cdn.jsdelivr.net/npm/vitral/dist/auto.js"></script>
<vitral-remote src="https://cdn.empresa.com/carrinho/vitral.json"></vitral-remote>

Todos os componentes de host aceitam src no lugar de name.

Rodar local

Cada remote roda no próprio vite dev, numa porta. O host aponta para essas portas:

cd carrinho && npm run dev   # vite --port 5174
cd busca && npm run dev      # vite --port 5175
cd loja && npm run dev       # vite --port 3000
// loja/public/remotes.json
{
  "carrinho": "http://localhost:5174/vitral.json",
  "busca": "http://localhost:5175/vitral.json"
}
// loja/src/main.jsx
configure({ remotes: '/remotes.json' })

Editar o remote atualiza o host sem recarregar a página (HMR). Para rodar só um remote local e pegar os outros de homologação, abra o host com ?vitral=carrinho@http://localhost:5174.

Docker e Kubernetes

Cada remote vira uma imagem nginx com o dist. O host lê a lista de remotes de uma variável de ambiente quando o contêiner sobe, então a mesma imagem serve em qualquer ambiente.

# compose.yaml
services:
  loja:                # host: http://localhost:8080
    build: ./loja
    ports: ["8080:80"]
    environment:
      VITRAL_REMOTES: '{"carrinho":"/mf/carrinho/vitral.json","busca":"/mf/busca/vitral.json"}'
  carrinho:            # remote React
    build: ./carrinho
  busca:               # remote Vue
    build: ./busca

O nginx do host repassa /mf/carrinho/ para o contêiner carrinho. Tudo fica na mesma origem: sem CORS e com uma porta só. Parar o contêiner carrinho mostra o erro só no lugar do carrinho.

No Kubernetes, as mesmas imagens funcionam com um Service por remote; o Ingress pode fazer o papel do nginx do host. Se os remotes ficam num CDN, troque VITRAL_REMOTES pelas URLs do CDN.

Exemplo completo, com Dockerfiles e configuração do nginx: examples/docker.

Frameworks

Framework Remote Host Import
React 18/19 reactRemote(App) <Remote> vitral/react
Next.js (App e Pages Router) reactRemote(App) <Remote> vitral/react
Vue 3 vueRemote(App, { setup }) <VitralRemote> vitral/vue
Nuxt 3/4 vueRemote(App, { setup }) <VitralRemote> vitral/vue
Angular 17+ angularRemote(Component, { providers }) <vitral-remote> + provideVitral() vitral/angular
Svelte 5 svelteRemote(App) use:remote vitral/svelte
Solid solidRemote(App) <Remote> vitral/solid
Preact preactRemote(App) <Remote> vitral/preact
Lit e Web Components elementRemote('meu-widget') <vitral-remote> vitral/element
JS e TypeScript puro component({ render }) <vitral-remote> vitral

No Next e no Nuxt o componente renderiza o fallback no servidor e monta o remote no cliente.

Host em Vue

<script setup>
import { VitralRemote } from 'vitral/vue'
</script>

<template>
  <VitralRemote name="carrinho" :props="{ userId: 42 }">
    <template #fallback>Carregando…</template>
    <template #error="{ retry }"><button @click="retry">Tentar de novo</button></template>
  </VitralRemote>
</template>

Host em qualquer outro lugar

<vitral-remote> funciona em HTML puro, Angular, Svelte, Astro ou HTML gerado no servidor:

<script type="module">
  import { configure } from 'vitral'
  import { defineVitralElement } from 'vitral/element'

  configure({ remotes: { carrinho: 'https://cdn.empresa.com/carrinho/vitral.json' } })
  defineVitralElement()
</script>

<vitral-remote name="carrinho" props='{"userId": 42}'>
  <p slot="fallback">Carregando…</p>
  <p slot="error">Indisponível</p>
</vitral-remote>

Remote sem framework

import { component, html } from 'vitral'

export default component<{ nome: string }>({
  state: { cliques: 0 },
  render: ({ nome }, state) => html`
    <p>Olá, ${nome}</p>
    <button onclick=${() => state.cliques++}>cliques: ${state.cliques}</button>
  `,
})

O que entra em ${} é escapado. Mudar state ou as props atualiza a tela.

Desenvolvimento

  • HMR: com o remote em vite dev, aponte o host para http://localhost:5174/vitral.json. Editar o remote atualiza o host sem recarregar a página.
  • Overrides: http://localhost:3000/?vitral=carrinho@http://localhost:5174 troca a URL de um remote sem mexer no host. Só vale em localhost ou com configure({ devOverrides: true }). Para guardar no navegador, use devtools() de vitral/devtools.
  • Tipos das props: o build publica vitral.d.ts. No host, npx vitral types gera vitral-env.d.ts, e uma prop errada passa a não compilar.

Comunicação entre remotes

import { events } from 'vitral'

events.emit('carrinho:total', 120)
const parar = events.on('carrinho:total', (total) => console.log(total))
events.last('carrinho:total') // 120

Todos os remotes usam o mesmo barramento. O que um remote assina é cancelado quando ele desmonta.

Para estado comum (usuário, tema, carrinho), use o contexto compartilhado:

import { provide, shared } from 'vitral'

provide('usuario', { id: 42 })                    // host: cria e é o dono
provide('tema', 'claro', { writable: false })     // só o dono altera
shared('tema').subscribe((tema) => console.log(tema))
const nome = useShared('usuario', (u) => u?.nome) // React, Preact, Vue e Solid

Angular usa injectShared, Svelte usa $tema e Lit usa SharedController. Detalhes em Contexto compartilhado.

Para mudar de página de dentro de um remote, use navigate('/pedidos/42'). Todos os roteadores da página recebem a mudança.

CSS e Tailwind

Use Tailwind ou qualquer CSS no remote. O plugin prefixa o CSS para valer só dentro do remote, então o CSS de um remote não muda o host nem os outros remotes.

Para isolar por completo, use Shadow DOM:

<Remote name="carrinho" isolate="shadow" />

Com Shadow DOM, as regras @property e @font-face sobem para o documento, então sombras, rings, gradientes e fontes do Tailwind v4 funcionam. O plugin já faz essa separação no build (campo shadow do vitral.json): a página só adota o CSS pronto. Manifestos antigos continuam funcionando, com a separação feita no navegador.

O remote também pode usar o tema do host:

vitral({ theme: 'host' })                                  // var(--x) usa o valor do host, se existir
configure({ styles: { inherit: ['/assets/base.css'] } })   // CSS base do host dentro do Shadow DOM

Tema com Tailwind v4, dark mode, CSS legado e Tailwind v3 no host: Estilos.

Configuração

configure({
  remotes: '/remotes.json',                    // ou { nome: url }
  allowedOrigins: ['https://cdn.empresa.com'], // padrão: a página e as origens registradas
  timeout: 15000,                              // ms por download
  retries: 1,                                  // novas tentativas em erro de rede
  isolate: 'none',                             // ou 'shadow'
})

Cada remote também aceita um objeto:

configure({
  remotes: {
    carrinho: { url: 'https://cdn.empresa.com/carrinho/vitral.json', integrity: 'sha384-…' },
    menu: { module: () => import('./menu') }, // remote local, sem rede
  },
})

Erros

Todo erro é um RemoteError com code, o nome do remote e docs, o link da página do erro com causas e correção. Um remote que falha mostra o próprio erro e não derruba a página. retry monta só aquele remote de novo.

code Quando
NOT_REGISTERED Nome não registrado em configure
ORIGIN_BLOCKED URL fora de allowedOrigins
TIMEOUT Não respondeu dentro de timeout
INTEGRITY O hash do JS não confere
INVALID_MODULE O arquivo não exporta mount()
LOAD_FAILED Erro de rede ou HTTP
MOUNT_FAILED O remote lançou erro ao montar
RENDER_FAILED O remote quebrou depois de montado

Performance

Remote com import() também gera dist/split/: o navegador baixa só o que o remote usa ao abrir, e o resto chega no import(), conferido pelo mesmo hash. Hosts antigos continuam usando o arquivo único.

No CDN, sirva vitral.json com Cache-Control: no-cache e o resto (nome com hash) com public, max-age=31536000, immutable.

Assim que o manifesto chega, o JS e o CSS do remote baixam juntos. A montagem espera os dois, para o remote não aparecer sem estilo. configure() abre a conexão com a origem de cada remote (<link rel="preconnect">).

import { preload } from 'vitral'
// Baixa JS e CSS sem montar, por exemplo no hover de um link
link.addEventListener('pointerenter', () => preload('checkout'), { once: true })

Nos componentes de host e no <vitral-remote>:

<!-- monta perto de aparecer na tela; reserve o espaço para a página não pular -->
<vitral-remote name="comentarios" loading="lazy" style="display:block;min-height:400px"></vitral-remote>
<!-- monta com o navegador ocioso, mas já baixa no hover -->
<vitral-remote name="checkout" loading="idle" prefetch="hover"></vitral-remote>
<!-- remote que aparece logo ao abrir a página -->
<vitral-remote name="cabecalho" priority="high"></vitral-remote>
<Remote name="checkout" loading="lazy" prefetch="visible" keepAlive />
Opção Valores O que faz
loading lazy, idle Monta a 200px de aparecer na tela, ou com o navegador ocioso
prefetch hover, visible, idle Chama preload() antes de montar
priority high fetchpriority alto no manifesto, no JS e no CSS
keepAlive (keep-alive) true ou o máximo Ao sair da página, o remote vai para um contêiner oculto (LRU, padrão 5); na volta só recebe as props novas

Com keep-alive, o remote pode pausar timers e assinaturas:

mount(el, props, ctx) {
  ctx.onDeactivate?.(() => clearInterval(timer))
  ctx.onActivate?.(() => (timer = setInterval(tick, 1000)))
}

Métricas

Cada etapa do carregamento vira um performance.measure com o nome vitral:<remote>:<etapa>. As etapas são manifest, js, css e mount. Elas aparecem na aba Performance do DevTools. Para mandar ao seu RUM:

configure({
  onMetric: ({ name, stage, duration }) => rum.send(`vitral.${stage}`, duration, { remote: name }),
})

O evento vitral-ready (e o ready do componente Vue) traz os tempos em detail.timings, por exemplo { manifest: 12.3, js: 40.1, css: 8, mount: 2.4 }. Um remote local (module) só mede css e mount. Erros dentro do onMetric são ignorados.

Dependências compartilhadas

Para não baixar React ou Vue mais de uma vez, liste-os em dedupe no remote:

vitral({ dedupe: ['react'] })

O remote usa a cópia que já está na página quando a versão serve (padrão: ^ da versão instalada no remote) e baixa a própria só quando não serve. Os componentes de host em React oferecem o React do host sem configuração.

Mais formas de usar

Vários componentes por remote (<Remote name="carrinho/MiniCarrinho" />), módulos sem UI (loadModule('carrinho/api')), deploy novo sem recarregar a página (watchUpdates() e refresh('carrinho')) e app shell por rota (routes() e <vitral-outlet>, em vitral/routes). Veja Exposes, Atualização sem reload e Rotas.

Segurança

  • Só importa JS de origens permitidas, mesmo que o vitral.json aponte para outra. O src de um componente de host entra na allowlist; o atributo src do <vitral-remote> não entra quando há allowedOrigins.
  • Com integrity, nada roda sem o hash conferir. A lib usa o primeiro caminho que o navegador garante: import map com integrity (Chrome 133+, Safari 18.4+), <link rel="modulepreload" integrity> (Firefox 115+) ou, nos outros, baixa, confere e executa exatamente os bytes conferidos via blob:. Aceita sha256, sha384 e sha512, e vários hashes separados por espaço (vale o mais forte). O CSS do manifesto também é conferido.
  • Com CSP, a lib copia o nonce de um <script nonce> da página para o import map. Sem nonce, cai para o modulepreload.
  • O hash só protege contra CDN comprometido se vier de outro lugar: fixe integrity no host ou embuta o manifesto ({ url, manifest }).
  • Sem eval e sem iframes.
  • Shadow DOM isola o CSS, não o JS. Para código de terceiros em que você não confia, use iframe.

Testes do host

import { mockRemote, failingRemote } from 'vitral/testing'

const carrinho = mockRemote('carrinho', (el, props) => (el.textContent = `itens: ${props.itens}`))
// renderize o host
expect(carrinho.lastProps).toEqual({ itens: 2 })

failingRemote('recomendacoes') // testa a tela de erro

Related Packages

wu-framework

Universal Microfrontends Framework - 13 frameworks, zero config, Shadow DOM isol...

12,532
ziko-wrapper

integrate zikojs elements within other ui framework like vue react solidjs svelt...

5,604 49
vite-plugin-pwa

Zero-config PWA for Vite

140,031,941 4,301
astrobook

The minimal UI component playground

134,651 279

Version History

Version Released Vue License
0.7.2 >=3.3 MIT
0.7.1 >=3.3 MIT
0.7.0 >=3.3 MIT
0.6.0 >=3.3 MIT
0.5.0 >=3.3 MIT
Pre-releases (1)
Version Released License
0.0.0-stage