Renderizado en el servidor (SSR)
PreffX renderiza sus componentes en el servidor a una cadena HTML y luego hidrata el mismo árbol en el cliente. El servidor emite el marcado más un script de precarga por raíz que contiene los datos asíncronos resueltos: el cliente lee ese script y alimenta el árbol con los valores en lugar de volver a obtenerlos.
Las utilidades SSR se importan desde preffx/server.
createRoot en el servidor
El servidor exporta su propio createRoot, que espeja el del cliente pero devuelve un método renderToString en lugar de mount.
import { createRoot } from 'preffx/server';
import { App } from './App';
const server = createRoot();
const { preload, serialize } = server.renderToString(App, {
props: { name: 'PreffX' }
});
await preload(); // resolve every async resource
const html = serialize(); // markup + preload data scriptNormalmente se crea una raíz nueva por solicitud, porque la aplicación puede depender de la URL de la solicitud (enrutamiento, idioma) o del contexto con ámbito de solicitud:
import { createRoot } from 'preffx/server';
export async function render(url: URL) {
const root = createRoot({
defaultURL: url,
defaultLang: 'en',
context: { tenant: 'acme' }
});
const { preload, serialize } = root.renderToString(App, {});
await preload();
return { appHtml: serialize() };
}API
createRoot(config?)
Acepta las mismas opciones de raíz que la raíz del cliente, por lo que la configuración permanece sincronizada:
| Opción | Tipo | Descripción |
|---|---|---|
defaultURL | string | URL | URL inicial para el enrutamiento |
defaultLang | string | Idioma inicial para i18n |
prefix | string | Prefijo por raíz (debe coincidir con la del cliente) |
context | object | Contexto con ámbito de solicitud compartido con el árbol |
Devuelve un objeto con un único método:
renderToString(component, config?)
component— el componente raíz (PCoAPC).config— opcional, con:props— props del componente raíz.serializer— función personalizada para serializar los datos precargados (por defectoJSON.stringify).
Devuelve { preload, serialize }:
| Método | Firma | Descripción |
|---|---|---|
preload | preload(timeout?) | Resuelve todos los recursos asíncronos registrados. Un timeout opcional convierte un recurso lento en "best-effort": la página se renderiza igualmente y el cliente re-obtiene. |
serialize | serialize(): string | Emite la cadena HTML (marcado + script de datos de precarga por raíz). |
Recursos en el servidor
Dentro de un componente, resource se comporta de forma transparente en el servidor: en lugar de ejecutar un efecto, registra su fetcher para preload(). Reutilice el mismo componente tanto en el servidor como en el cliente: no se necesita ninguna ramificación.
import type { PC } from 'preffx';
export const UserCard: PC = (_props, { resource, computed }) => {
const [user] = resource(async () => {
const res = await fetch('/api/user');
return res.json();
});
const display = computed(() => user.state.value?.name ?? 'loading');
return (
<article className="user">
<h1>{display}</h1>
</article>
);
};- Antes de que se ejecute
preload(),user.state.valueesnullypendingestrue, por lo que se renderiza el valor alternativo ('loading'). - Después de
preload(), el valor resuelto se incrusta en el marcado yserialize()también lo escribe en el script de datos de precarga. - En el cliente durante la hidratación, el recurso lee el valor precargado y omite el fetch, manteniendo
pendingenfalse.
Hidratación en el cliente
El createRoot().mount() del cliente detecta el script de datos de precarga emitido por el servidor, lo alimenta en el árbol e hidrata el DOM existente en lugar de recrearlo: no se necesita ninguna llamada adicional.
import { createRoot } from 'preffx';
import { App } from './App';
createRoot().mount(App, {
node: document.getElementById('app')!
});Requisitos para una hidratación correcta:
- Las opciones del
createRootdel cliente (prefix,defaultLang,defaultURL,context) deben coincidir con las del servidor. - El marcado renderizado debe insertarse exactamente donde el cliente lo espera (el contenedor referido por
mount).
Ejemplo: flujo completo de una solicitud
// entry-server.tsx
import { createRoot } from 'preffx/server';
import { App } from './App';
export async function render(url: URL) {
const server = createRoot({ defaultURL: url });
const { preload, serialize } = server.renderToString(App, {});
await preload();
return { appHtml: serialize() };
}// entry-client.tsx
import { createRoot } from 'preffx';
import { App } from './App';
createRoot().mount(App, { node: document.getElementById('app')! });El servidor Node renderiza appHtml en la carcasa HTML (p. ej. index.html) y lo envía al navegador; luego el script del cliente hidrata el mismo árbol.