Astro y Next.js
Wu incluye componentes de primera parte para Astro y Next, para que no tengas que escribir a mano la plomería de init y mount.
npm install @wu-framework/astro # peer: astro >= 4npm install @wu-framework/next # peers: next >= 13, react >= 18También son accesibles desde el paquete principal como
wu-framework/integrations/astro y wu-framework/integrations/next.
Dos modos, y la diferencia importa
Sección titulada «Dos modos, y la diferencia importa»WuApp |
WuAppSSR |
|
|---|---|---|
| Renderizado en el servidor | ❌ <div> vacío |
✅ marcado completo, en su shadow root |
| Contenido en el HTML inicial | ninguno | sí — visible con curl |
| Bueno para | islas interactivas bajo el pliegue | SEO, primer pintado, contenido sobre el pliegue |
| Necesita un módulo de servidor | no | sí |
| Desde | 2.0 | 2.7 |
WuApp renderiza un marcador de posición y monta después de la hidratación.
WuAppSSR ejecuta renderApp() durante el render de servidor, así que el HTML
ya contiene el marcado de la micro-app —encapsulado en un Declarative Shadow DOM
con sus estilos dentro— y luego lo hidrata en el sitio.
El framework del shell y el de la micro-app no tienen por qué coincidir.
WuAppSSR es un componente React porque Next es React, pero la micro-app que
renderiza puede ser React, Vue, Preact, Svelte, Solid o HTML plano: lo que sea
con lo que se haya construido su módulo de servidor.
Inicializa una sola vez
Sección titulada «Inicializa una sola vez»---import WuShell from '@wu-framework/astro/WuShell.astro';---<html> <body> <WuShell apps={[ { name: 'header', url: '/apps/header' }, { name: 'cart', url: '/apps/cart' }, ]}> <slot /> </WuShell> </body></html>| Prop | Tipo | Significado |
|---|---|---|
apps |
Array<{ name, url, strategy? }> |
Se pasa a wu.init() |
sandbox |
'module' | 'strict' | 'eval' |
Modo de sandbox global |
debug |
boolean |
Registro detallado |
La configuración viaja al navegador dentro de una etiqueta <script> con JSON;
el script de montaje lo empaqueta Vite. Esa indirección existe porque
define:vars obligaría a usar is:inline, y un especificador wu-framework
pelado no se resuelve en el navegador.
Islas solo de cliente
Sección titulada «Islas solo de cliente»---import WuApp from '@wu-framework/astro/WuApp.astro';---<WuApp name="header" /><WuApp name="dashboard" lazy />| Prop | Por defecto | Significado |
|---|---|---|
name |
— | Nombre de la micro-app registrada |
lazy |
false |
Retrasa el montaje hasta que sea visible (IntersectionObserver, margen de 200 px) |
class / style |
— | Se aplican al <div> contenedor |
Renderizado en el servidor
Sección titulada «Renderizado en el servidor»---import WuAppSSR from '@wu-framework/astro/WuAppSSR.astro';import cartServer from '../apps/cart/server.js';---<WuAppSSR name="cart" module={cartServer} props={{ userId: 42 }} />| Prop | Tipo | Por defecto | Significado |
|---|---|---|---|
name |
string |
— | Debe coincidir con lo que registra el cliente |
module |
módulo o () => import(...) |
— | El módulo de servidor de la app |
props |
Record<string, any> |
{} |
Props de render; se publican para la hidratación |
styles |
string | string[] |
— | CSS extra encapsulado dentro del shadow root |
hydrate |
boolean |
true |
Monta/hidrata en el cliente tras la carga |
fallbackToClient |
boolean |
true |
Si falla el SSR, deja el hueco y monta en el cliente |
class / style |
string |
— | Se aplican al contenedor |
Funciona tanto en la salida SSR de Astro como en la estática. En un build estático el marcado se cocina en tiempo de build, que es exactamente lo que quieres para páginas de contenido.
El componente emite <script type="application/json" data-wu-ssr-state="...">
junto al contenedor, y su script de cliente lo publica en
window.__WU_SSR_STATE__ antes de llamar a wu.mount(), que es donde
mount() lee las props para entregárselas al slot hydrate de la app.
Pon hydrate={false} para contenido inerte: se envía el marcado del servidor y
ningún JavaScript monta encima.
Auto-init mediante la integración
Sección titulada «Auto-init mediante la integración»import wu from '@wu-framework/astro';
export default defineConfig({ integrations: [ wu({ apps: [ { name: 'header', url: '/apps/header', strategy: 'eager' }, { name: 'dashboard', url: '/apps/dashboard' }, ], sandbox: 'module', }), ],});Opciones: apps, sandbox, debug y cdn (inyectar el runtime desde una URL
en vez de empaquetarlo). La integración también añade wu-framework a
optimizeDeps.include de Vite.
Usa la integración o WuShell, no ambas: dos llamadas a init() son
inofensivas (es idempotente) pero la duplicación confunde.
Next.js
Sección titulada «Next.js»Inicializa una sola vez, en un layout
Sección titulada «Inicializa una sola vez, en un layout»import { WuShell } from '@wu-framework/next';
export default function RootLayout({ children }) { return ( <html><body> <WuShell apps={[{ name: 'header', url: '/apps/header' }]}> {children} </WuShell> </body></html> );}WuShell es un componente de cliente. Importa el runtime dinámicamente dentro
de un efecto, así que nada corre durante el SSR ni en un render de RSC.
wu.init() es idempotente, así que un doble montaje de StrictMode es inofensivo.
Islas solo de cliente
Sección titulada «Islas solo de cliente»'use client';import { WuApp } from '@wu-framework/next';
<WuApp name="header" /><WuApp name="dashboard" lazy />| Prop | Por defecto | Significado |
|---|---|---|
name / appName |
— | Nombre de la micro-app (appName gana si se pasan ambos) |
lazy |
false |
Retrasa el montaje hasta que sea visible |
className / style |
— | Se aplican al contenedor |
onMount / onUnmount |
null |
Callbacks ({ name }) => void |
Solo de cliente por construcción: el runtime se importa dentro de useEffect,
así que el servidor renderiza un <div id> vacío y el cliente lo rellena. Sin
desajustes de hidratación, y sin necesidad de dynamic(..., { ssr: false }).
Renderizado en el servidor — un Server Component
Sección titulada «Renderizado en el servidor — un Server Component»// app/page.jsx — sin 'use client'import { WuAppSSR } from '@wu-framework/next';
export default function Page() { return ( <main> <WuAppSSR name="cart" module={() => import('../apps/cart/server.js')} props={{ userId: 42 }} /> </main> );}| Prop | Tipo | Por defecto | Significado |
|---|---|---|---|
name / appName |
string |
— | Nombre de la micro-app (appName gana) |
module |
() => import(...) o módulo |
— | El módulo de servidor de la app |
props |
object |
{} |
Props de render; se publican para la hidratación |
styles |
string | string[] |
— | CSS extra dentro del shadow root |
shadowMode |
'open' | 'closed' |
'open' |
Modo del shadow root declarativo |
hydrate |
boolean |
true |
Monta/hidrata en el cliente; false = marcado inerte |
fallbackToClient |
boolean |
true |
Si falla el SSR, degrada a un montaje de cliente en vez de tirar la página |
className / style |
— | — | Se aplican al contenedor |
Es un Server Component async: espera a renderApp(), coloca el HTML
renderizado con dangerouslySetInnerHTML (React no debe entrar en un subárbol
del que wu es dueño), y renderiza un <WuMount> hermano que hace la mitad del
cliente.
WuMount se exporta por separado para configuraciones a medida:
'use client';import { WuMount } from '@wu-framework/next';
<WuMount name="cart" containerId="wu-app-cart" props={serverProps} ssr />No renderiza nada. Publica props en window.__WU_SSR_STATE__ y luego llama a
wu.mount(), que detecta el shadow root del servidor y llama a hydrate en vez
de a mount.
La micro-app no tiene que ser React
Sección titulada «La micro-app no tiene que ser React»Este es el punto que más fácil se pasa por alto. Un shell Next puede alojar una micro-app Vue:
// apps/catalog/server.js — la mitad del servidorimport { renderVue } from 'wu-framework/server';import App from './App.vue';export default renderVue(App, { styles: css });// apps/catalog/main.js — la mitad del navegadorimport { wuVue } from 'wu-framework/adapters/vue';import App from './App.vue';wuVue.register('catalog', App);// app/page.jsx — en el shell Next<WuAppSSR name="catalog" module={() => import('../apps/catalog/server.js')} />Next renderiza el HTML de la app Vue en el servidor, lo envía dentro de un
shadow root, y el createSSRApp de Vue lo hidrata en el navegador. React nunca
toca ese subárbol.
Cómo elegir entre ambos
Sección titulada «Cómo elegir entre ambos»¿Este contenido importa para el SEO o el primer pintado?│├─ Sí → WuAppSSR│ necesita un módulo de servidor (renderReact/renderVue/… o renderHtml)│ envía HTML real, hidrata en el sitio, sin parpadeo de caja vacía│└─ No → WuApp no necesita módulo de servidor añade `lazy` para todo lo que esté bajo el plieguePuedes mezclar ambos en una misma página. Un hero renderizado en el servidor y tres islas perezosas es una composición perfectamente normal.
Detalles propios de estas integraciones
Sección titulada «Detalles propios de estas integraciones»El script de WuApp en Astro consulta todos los contenedores. Su script de
cliente selecciona cada div[data-wu-app] de la página, así que monta todas las
islas de una pasada. Está bien, pero significa que el script corre una vez por
página, no una vez por instancia de componente.
El WuAppSSR de Astro solo hidrata contenedores con
data-wu-hydrate="true". Poner hydrate={false} deja el marcado inerte a
propósito.
El WuApp de Next desmonta al limpiar el efecto. En una transición de ruta
la app se desmonta y se vuelve a montar. Si eso es caro, marca la app con
keepAlive: true en wu.init() para que el teardown se convierta en un ocultar:
mira Pestañas con keep-alive.
Los fallos de SSR no son fatales por defecto. Ambos componentes WuAppSSR
capturan un error de render, lo registran y dejan el contenedor vacío para que
lo rellene el cliente. Pasa fallbackToClient={false} cuando un render de
servidor ausente deba tirar la página de forma ruidosa.
El Declarative Shadow DOM necesita un navegador moderno: Chrome 111+, Safari
16.4+, Firefox 123+. Wu incluye declarativeShadowDomPolyfill(), y mount()
también materializa por su cuenta cualquier <template shadowrootmode> que
quede. Mira SSR.
