Renderizado en el servidor

El renderizado en el lado del servidor (SSR) se refiere a la generación de páginas HTML en el servidor bajo demanda y enviarlas al cliente.

SSR te permite:

  • Implementar sesiones para iniciar sesión en tu aplicación.
  • Renderizar datos desde una llamada de API dinámicamente con fetch.
  • Desplegar tu sitio en un servidor usando un adaptador.

Considera habilitar el renderizado en el lado del servidor (SSR) en tu proyecto de Astro si necesitas lo siguiente:

  • Endpoints de API: SSR te permite crear páginas específicas que funcionan como endpoints de API para tareas como el acceso a bases de datos, la autenticación y la autorización, al tiempo que mantienen oculta la información confidencial del cliente.

  • Páginas protegidas: Si necesitas restringir el acceso a una página según los privilegios del usuario, puedes habilitar SSR para manejar el acceso del usuario en el servidor.

  • Contenido que cambia con frecuencia: Habilitar SSR te permite generar páginas individuales sin necesidad de reconstruir estáticamente todo tu sitio. Esto es útil cuando el contenido de una página se actualiza con frecuencia.

Para hablitar las características SSR para despliegues en producción, actualiza tu configuración output a 'server' o 'hybrid' (introducido en v2.6.0). Ambos modos controlan cuales páginas o endpoints del servidor deben ser renderizados en el servidor. Cada opción de configuración tiene un comportamiento por defecto diferente, y permite que las rutas individuales opten por no utilizar el valor por defecto:

  • output: 'server': Renderizado en el servidor por defecto. Utiliza esto cuando la mayoría o todo tu sitio debe ser renderizado en el servidor. Cualquier página o endpoint individual puede optar por pre-renderizar.
  • output: 'hybrid': Pre-renderizado a HTML por defecto. Utiliza esto cuando la mayoría de tu sitio debe ser estático. Cualquier página o endpoint individual puede optar por no pre-renderizar.
astro.config.mjs
import { defineConfig } from 'astro/config';
import nodejs from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: nodejs(),
});

Puedes optar por no utilizar el comportamiento de renderizado por defecto con una declaración de exportación en cualquier página o ruta:

src/pages/mypage.astro
---
export const prerender = true;
// ...
---
<html>
<!-- Estático, página pre-renderizada aquí... -->
</html>

Ver más ejemplos de uso de configuración de rutas individuales

Convirtiendo un sitio estático a renderizado híbrido

Sección titulada Convirtiendo un sitio estático a renderizado híbrido

Para convertir un sitio Astro estático existente para permitir el renderizado híbrido, cambia la opción output a 'hybrid' y añade un adaptador:

astro.config.mjs
import { defineConfig } from 'astro/config';
import nodejs from '@astrojs/node';
export default defineConfig({
adapter: nodejs({
mode: 'middleware' // or 'standalone'
}),
output: 'hybrid',
});

Cuando sea el momento de desplegar un proyecto SSR, vas a necesitar añadir un adaptador. Esto es porque SSR requiere un servidor en tiempo de ejecución: el ambiente que ejecuta tu código en el lado del servidor. Cada adaptador le permite a Astro entregar un script que ejecuta tu proyecto en un ambiente específico.

Puedes encontrar tanto adaptadores SSR oficiales como de la comunidad en nuestro directorio de integraciones.

Puedes añadir cualquiera de los integraciones oficiales del adaptador de Astro con el comando astro add. Esto instalará el adaptador y hará los cambios apropiados a tu archivo astro.config.mjs en un solo paso. Por ejemplo, para instalar el adaptador de Vercel, ejecuta:

Ventana de terminal
npx astro add vercel

También puedes añadir un adaptador manualmente instalando el paquete y actualizando astro.config.mjs tú mismo. (Mira los enlaces debajo para instrucciones específicas de cada adaptador y completar los pasos para habilitar SSR.) Usando mi-adaptador como ejemplo, las instrucciones serían:

  1. Instala el adaptador a las dependencias de tu proyecto usando tu gestor de paquetes preferido:

    Ventana de terminal
    npm install @astrojs/mi-adaptador
  2. Añade el adaptador a tu archivo de configuración astro.config.mjs de la siguiente forma.

    astro.config.mjs
    import { defineConfig } from 'astro/config';
    import miAdaptador from '@astrojs/mi-adaptador';
    export default defineConfig({
    output: 'server',
    adapter: miAdaptador(),
    });

Astro seguirá siendo un generador de sitios estáticos por defecto. Pero una vez que habilites el renderizado en el servidor y agregues un adaptador, algunas características nuevas estarán disponibles para ti.

Configuración de rutas individuales

Sección titulada Configuración de rutas individuales

Ambos modos server e hybrid permiten páginas y endpoints renderizados en el servidor y renderizarán todas las páginas de acuerdo a su comportamiento por defecto. Sin embargo, ambos modos permiten marcar una página individual para optar por no utilizar este comportamiento por defecto.

Optar por no renderizar en el servidor

Sección titulada Optar por no renderizar en el servidor

Para una aplicación principalmente renderizada en el servidor configurada como output: server, añade export const prerender = true a cualquier página o ruta para pre-renderizar una página o endpoint estático:

src/pages/index.astro
---
export const prerender = true;
// ...
---
<html>
<!-- Estático, página pre-renderizada aquí... -->
</html>
src/pages/mypage.mdx
---
layout: '../layouts/markdown.astro'
title: 'Mi página'
---
export const prerender = true;
# Esta es mi página estática, pre-renderizada

Y para un endpoint:

src/pages/myendpoint.js
export const prerender = true;
export async function GET() {
return {
body: JSON.stringify({ message: `Este es mi endpoint estático` }),
};
}

Para un sitio principalmente estático configurado como output: hybrid, añade export const prerender = false a cualquier archivo que debería ser renderizado en el servidor:

src/pages/randomnumber.js
export const prerender = false;
export async function GET() {
let number = Math.random();
return {
body: JSON.stringify({ number, message: `Aquí hay un número al azar: ${number}` }),
};
}

Los encabezados de la solicitud están disponibles en Astro.request.headers. Esto funciona de manera similar a Request.headers en el navegador. Es un objeto Headers, es un objeto similar a un Map donde puedes recuperar encabezados como la cookie.

src/pages/index.astro
---
const cookie = Astro.request.headers.get('cookie');
// ...
---
<html>
<!-- Maquetado aquí... -->
</html>

El método HTTP utilizado en la solicitud está disponible como Astro.request.method. Esto funciona de manera similar a Request.method. en el navegador. Devuelve la representación en cadena del método HTTP utilizado en la solicitud.

src/pages/index.astro
---
console.log(Astro.request.method) // GET (cuando se navega en el navegador)
---

Esta es una utilidad para leer y modificar una sola cookie. Te permite comprobar, establecer, obtener y eliminar una cookie.

Ve más detalles sobre Astro.cookies y el tipo AstroCookie en la referencia de la API.

El ejemplo a continuación actualiza el valor de una cookie para un contador de vistas de página.

src/pages/index.astro
---
let counter = 0
if (Astro.cookies.has("counter")) {
const cookie = Astro.cookies.get("counter")
counter = cookie.number() + 1
}
Astro.cookies.set("counter",counter)
---
<html>
<h1>Contador = {counter}</h1>
</html>

En el objeto global Astro, este método te permite redirigir a otra página. Puedes hacer esto después de verificar si el usuario ha iniciado sesión obteniendo la sesión desde una cookie.

src/pages/account.astro
---
import { isLoggedIn } from '../utils';
const cookie = Astro.request.headers.get('cookie');
// Si el usuario no ha iniciado sesión, redirígelo a la página de inicio de sesión.
if (!isLoggedIn(cookie)) {
return Astro.redirect('/login');
}
---
<html>
<!-- Maquetado aquí... -->
</html>

También puedes devolver una Response desde cualquier página. Puedes hacer esto para devolver un 404 en una página dinámica luego de buscar y no encontrar la id en la base de datos.

src/pages/[id].astro
---
import { getProduct } from '../api';
const product = await getProduct(Astro.params.id);
// Producto no encontrado
if (!product) {
return new Response(null, {
status: 404,
statusText: 'No encontrado'
});
}
---
<html>
<!-- Maquetado aquí... -->
</html>

Un server endpoint, también conocido como una ruta API, es una función exportada desde un archivo .js o .ts dentro de la carpeta src/pages/. La función recibe un contexto del endpoint y devuelve una Response. Una característica poderosa de SSR, las rutas API son capaces de ejecutar código de forma segura en el servidor. Para aprender más, consulta nuestra guía de endpoints.

Los navegadores admiten de forma nativa la transmisión HTTP, donde un documento se divide en fragmentos, se envía por la red en orden y se representa en la página en ese mismo orden.

Durante este proceso, los navegadores consumen HTML de forma incremental: analizando, representando en el DOM y pintando en la pantalla. Esto ocurre tanto si se transmite intencionalmente el HTML como si no. Las condiciones de la red pueden hacer que los documentos grandes se descarguen lentamente, y esperar a que se recuperen los datos puede bloquear la representación de la página.

Utilizando streaming para mejorar el rendimiento de la página

Sección titulada Utilizando streaming para mejorar el rendimiento de la página

La siguiente página utiliza await para obtener algunos datos en su frontmatter. Astro esperará a que todas las llamadas fetch se resuelvan antes de enviar cualquier HTML al navegador.

src/pages/index.astro
---
const personResponse = await fetch('https://randomuser.me/api/');
const personData = await personResponse.json();
const randomPerson = personData.results[0];
const factResponse = await fetch('https://catfact.ninja/fact');
const factData = await factResponse.json();
---
<html>
<head>
<title>Un nombre y un hecho</title>
</head>
<body>
<h2>Un nombre</h2>
<p>{randomPerson.name.first}</p>
<h2>Un hecho</h2>
<p>{factData.fact}</p>
</body>
</html>

Mover las llamadas await a componentes más pequeños te permite aprovechar el streaming de Astro. Utilizando los siguientes componentes para realizar las recuperaciones de datos, Astro puede representar primero algo de HTML, como el título, y luego los párrafos cuando los datos estén listos.

src/components/RandomName.astro
---
const personResponse = await fetch('https://randomuser.me/api/');
const personData = await personResponse.json();
const randomPerson = personData.results[0];
---
<p>{randomPerson.name.first}</p>
src/components/RandomFact.astro
---
const factResponse = await fetch('https://catfact.ninja/fact');
const factData = await factResponse.json();
---
<p>{factData.fact}</p>

La página Astro a continuación, utilizando estos componentes, puede renderizar partes de la página más rápido. Las etiquetas <head>, <body> y <h1> ya no se bloquean por las solicitudes de datos. El servidor luego obtendrá los datos RandomName y RandomFact en paralelo y transmitirá el HTML resultante al navegador.

src/pages/index.astro
---
import RandomName from '../components/RandomName.astro'
import RandomFact from '../components/RandomFact.astro'
---
<html>
<head>
<title>Un nombre y un hecho</title>
</head>
<body>
<h2>Un nombre</h2>
<RandomName />
<h2>Un hecho</h2>
<RandomFact />
</body>
</html>

También puedes incluir promesas directamente en la plantilla. En lugar de bloquear todo el componente, se resolverá la promesa en paralelo y solo se bloqueará el marcado que viene después de ella.

src/pages/index.astro
---
const personPromise = fetch('https://randomuser.me/api/')
.then(response => response.json())
.then(arr => arr[0].name.first);
const factPromise = fetch('https://catfact.ninja/fact')
.then(response => response.json())
.then(factData => factData.fact);
---
<html>
<head>
<title>Un nombre y un hecho</title>
</head>
<body>
<h2>Un nombre</h2>
<p>{personPromise}</p>
<h2>Un hecho</h2>
<p>{factPromise}</p>
</body>
</html>

En este ejemplo, Un nombre se renderizará mientras se cargan personPromise y factPromise.

Una vez que personPromise se haya resuelto, aparecerá Un hecho y factPromise se mostrará cuando haya terminado de cargarse.