Contenido
Renderizado en el servidor
Carats ofrece renderizado automático en el servidor (SSR) mediante su paquete @carats/ssr.
Punto de entrada del servidor
El punto de entrada del servidor se define en src/server/entrypoint.ts usando defineServerEntry de @carats/ssr. Enlaza las facetas compartidas, registra los culets y expone las funciones render y getServerProps que usa el plugin del servidor.
import { defineServerEntry } from '@carats/ssr';
import facets from '../client/facets.cara';
export default defineServerEntry(facets);
Lo que expone la entrada
El objeto que devuelve `defineServerEntry` es lo que gobierna el plugin del servidor:
- `render(req)` resuelve la faceta de la URL solicitada, ejecuta el componente de página y devuelve el `html` renderizado junto con las etiquetas `head` recogidas.
- `getServerProps(req)` resuelve el culet de la URL solicitada y devuelve su valor, o `undefined` cuando la ruta no tiene culet.
- `facets` es el objeto de facetas compartido, y `culets` es el registro que construyen `culet` y `seat`.
Por eso la exportación por defecto debe ser la propia entrada: el plugin de Express llama a estas funciones en cada petición.
Definir culets
Los culets se definen con la función culet de @carats/ssr. Define uno solo cuando una ruta necesite realmente una prop del servidor, y solo cuando los datos puedan obtenerse con una única petición GET. El manejador recibe la petición, incluidos los parámetros de ruta, las cookies, las cabeceras, la consulta y el método.
import { culet } from '@carats/ssr';
culet<User>('/profile', (req) => {
return {
id: '1',
name: 'Alexander Whitmore',
username: 'awhitmore',
email: 'a.whitmore@vault.io',
phone: '+1 (212) 555-0193',
website: 'whitmore.capital',
};
});
El parámetro genérico es opcional pero merece conservarse: es lo que hace que el valor devuelto coincida con las props de la página que lo consume.
Sentar culets
Un culet creado en su propio archivo debe registrarse con la función seat en el punto de entrada del servidor. Sentar no envuelve ni recrea el culet; devuelve la misma función para poder usarla en línea.
import { culet, seat } from '@carats/ssr';
import getTradeData from './culets/trade';
seat(getTradeData);
Un culet declarado en línea en el punto de entrada se registra por sí mismo, así que no necesita ninguna llamada a seat. En cambio, un culet importado desde src/server/culets fue creado con culet() en su propio módulo y debe sentarse una vez. No envuelvas un culet sentado en otra llamada a culet(), porque eso registraría el mismo manejador bajo una segunda ruta.
Integración con Express
Carats se integra con Express mediante @carats/express. La función carats exportada devuelve un Router que renderiza páginas, resuelve las props del servidor y sirve los recursos del cliente compilados, por lo que se monta con app.use. Este paquete requiere Express 5.
Punto de entrada del servidor
import { culet, defineServerEntry, seat } from '@carats/ssr';
import facets from '../client/facets.cara';
import getTradeData from './culets/trade';
seat(getTradeData);
culet<User>('/profile', () => {
return {
id: '1',
name: 'Alexander Whitmore',
username: 'awhitmore',
email: 'a.whitmore@vault.io',
phone: '+1 (212) 555-0193',
website: 'whitmore.capital',
};
});
export default defineServerEntry(facets);
Aplicación Express
import { carats } from '@carats/express'
import express from 'express'
const app = express()
const port = process.env.PORT || 5173
app.use(carats())
const server = app.listen(port, () => {
console.log(`Server started at http://localhost:${port}`)
})
export default server;
Ordena el middleware de modo que carats() se ejecute después de las rutas y los manejadores estáticos que no debe tragarse. El plugin devuelve un Router de Express, y por eso se monta con app.use en lugar de registrarse como una ruta.
Configuración de Vite para el servidor
El paquete del servidor se compila desde src/server/entrypoint.ts hacia dist/server. publicDir queda desactivado porque los recursos estáticos pertenecen a la compilación del cliente.
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
publicDir: false,
build: {
ssr: path.resolve(__dirname, 'src/server/entrypoint.ts'),
outDir: path.resolve(__dirname, 'dist/server'),
emptyOutDir: true,
minify: true,
rollupOptions: {
treeshake: true
}
}
})
Ejemplo completo del punto de entrada del servidor
// src/server/culets/trade.ts
import { culet } from '@carats/ssr';
export default culet<TradeData>('/trade/:symbol', async (req) => {
const symbol = req.params.symbol;
if (!symbol) {
throw new Error('Symbol is required');
}
return {
symbol: symbol.toUpperCase(),
price: '$67,234.18',
change24h: '+2.34%',
volume: '$28.4B',
high24h: '$68,102.50',
low24h: '$65,890.00',
orderBook: { bids: [], asks: [] },
};
});
// src/server/entrypoint.ts
import { culet, defineServerEntry, seat } from '@carats/ssr';
import facets from '../client/facets.cara';
import getTradeData from './culets/trade';
seat(getTradeData);
culet<User>('/profile', () => {
return {
id: '1',
name: 'Alexander Whitmore',
username: 'awhitmore',
email: 'a.whitmore@vault.io',
phone: '+1 (212) 555-0193',
website: 'whitmore.capital',
};
});
export default defineServerEntry(facets);
Fíjate en que la ruta de la faceta y la del culet son cadenas idénticas, incluido el parámetro :symbol. Una discrepancia es el motivo más habitual de que una página bruñida se renderice sin datos.
Definiciones de tipos
import { CaratsRequest } from '@carats/core';
import { Facets } from '@carats/render';
interface CaratsServerEntry {
render: (req: CaratsRequest<never>) => Promise<{
html?: string;
head?: string;
}>;
getServerProps: <T = any>(req: CaratsRequest<never>) => Promise<T> | T;
facets: Facets;
culets: Record<string, Culet>;
}
type CuletArgs = Omit<CaratsRequest, 'data'> & {
params: Record<string, string>;
};
type Culet<T = any> = (request: CuletArgs) => T;
declare const seat: <T extends Culet>(f: T) => T;
declare function culet<T = any>(route: string, culet: Culet<T>): Culet<T>;
declare function defineServerEntry(facets: Facets): CaratsServerEntry;