Saltar al contenido

Tipos de página: .astro, .md, .mdx y endpoints

Las extensiones que se convierten en rutas: .astro como formato nativo, .md y .mdx para contenido, y los endpoints .js/.ts que devuelven algo distinto de HTML. Cómo el layout envuelve el Markdown y cómo un endpoint cambia entre modo estático y bajo demanda.

Cuatro extensiones que se vuelven páginas

Astro no impone un único lenguaje para las páginas. Cada extensión reconocida ofrece un equilibrio distinto entre potencia y sencillez, y elegir bien es media batalla ganada en legibilidad.

Un .astro es una página programable; un .md es un documento; un .mdx es un documento que puede volverse interactivo donde haga falta. La regla mental: usa el formato más sencillo que cubra el contenido, y sube de nivel solo cuando lo necesites. El .html es el peldaño más bajo —marcado literal que Astro sirve casi sin tocar— y su nicho es exactamente ese: fragmentos que no deben pasar por ninguna transformación.

Lo importante es que, sea cual sea la extensión, el destino es común. Los cuatro formatos alimentan el mismo manifiesto de rutas y producen una URL siguiendo la misma regla de proyección que ya conoces. No hay un router para el HTML y otro para el Markdown: hay un solo enrutado por ficheros que acepta varios lenguajes de página. Elegir la extensión es elegir cómo escribes esa ruta, no cómo se enruta.

Markdown y MDX: contenido que se convierte en página

Un .md en src/pages es una página completa: Astro toma su frontmatter y su cuerpo, transforma el Markdown a HTML y lo sirve en la URL correspondiente. Pero por sí solo un Markdown renderizado es HTML desnudo, sin cabecera ni pie. Ahí entra la clave layout del frontmatter: apunta a un componente .astro que envolverá el contenido.

MD
---layout: ../../layouts/Base.astrotitle: Sobre nosotros---# Sobre nosotrosTexto en **Markdown** que se convierte en una pagina completa.

El layout recibe el contenido en su <slot /> y, además, todo el frontmatter bajo Astro.props.frontmatter, junto a metadatos útiles como la lista de encabezados. Así un mismo layout puede pintar el título, la fecha o la descripción declarados arriba. Es el puente entre el contenido plano y la plantilla de tu sitio.

.mdx sube la apuesta: es Markdown que además entiende componentes. Puedes importar un .astro o un componente de framework y usarlo en mitad de la prosa, pasar props e intercalar expresiones. Por eso esta misma guía se escribe en MDX: el texto fluye como Markdown, pero un <Callout> o un <Mermaid> aparecen donde hagan falta. A cambio, .mdx exige registrar la integración @astrojs/mdx en la configuración; .md funciona sin nada extra.

Endpoints: rutas que no devuelven HTML

No toda ruta sirve una página. Los ficheros .js y .ts en src/pages generan endpoints: rutas que devuelven cualquier cosa —JSON, un XML de sitemap, un CSV, una imagen— en lugar de HTML. En vez de marcado, exportas funciones nombradas según el método HTTP que atienden, y cada una devuelve un objeto Response estándar de la plataforma web.

TypeScript
// src/pages/api/estado.ts  ->  /api/estadoimport type { APIRoute } from 'astro';export const GET: APIRoute = ({ site }) => {  return new Response(    JSON.stringify({ ok: true, site: site?.href }),    { headers: { 'Content-Type': 'application/json' } }  );};

La función recibe un contexto —el APIContext— con las mismas piezas que usarías en una página: params con los segmentos dinámicos, request con la petición entrante, url con la dirección, redirect para desviar y cookies para leerlas o escribirlas. El endpoint es, en esencia, una página sin plantilla: mismo contexto, distinta salida. Además de un método concreto puedes exportar ALL para atender cualquier verbo con una sola función, útil cuando el enrutado importa más que el método.

Un patrón habitual es incrustar el tipo de contenido en el nombre del fichero. src/pages/datos.json.ts sirve /datos.json; src/pages/feed.xml.ts sirve /feed.xml. La primera extensión forma parte de la URL; la segunda —.ts— solo le dice a Astro que es un endpoint. Fija la cabecera Content-Type correcta en el Response: es lo que le dice al navegador si está recibiendo JSON, XML o texto plano, y omitirla es la causa habitual de que un feed o una API se muestren mal.

Estático o bajo demanda: los dos modos del endpoint

Un endpoint se comporta distinto según el modo de renderizado, y este es el matiz que más confunde. En un sitio estático, el endpoint se ejecuta una vez, en el build, y su respuesta se congela en un fichero. Solo tiene sentido GET, porque no hay servidor que atienda peticiones después: /datos.json es un archivo generado, no un manejador vivo.

En renderizado bajo demanda, el mismo endpoint se ejecuta en cada petición. Ahí cobran sentido POST, PUT, DELETE o PATCH: puedes recibir datos de un formulario, mutar estado o responder distinto según la cabecera. El endpoint pasa de ser un fichero prehorneado a ser una función viva del servidor.

TypeScript
// src/pages/api/contacto.ts  (solo bajo demanda)import type { APIRoute } from 'astro';export const POST: APIRoute = async ({ request }) => {  const datos = await request.formData();  const correo = datos.get('correo');  return new Response(JSON.stringify({ recibido: correo }), {    status: 201,    headers: { 'Content-Type': 'application/json' },  });};

Aquí se ve la asimetría con claridad: un POST que lee el cuerpo de la petición no tiene ningún sentido en el build, porque en el build no hay petición ni cuerpo que leer. Los verbos de mutación son, por naturaleza, criaturas del servidor; el modo estático solo puede hornear respuestas de lectura.

Recuerda que puedes fijar el modo ruta a ruta con export const prerender. Un endpoint de datos que no cambia puede prerenderizarse aunque el resto del sitio sea dinámico, y un endpoint de formulario puede quedar bajo demanda en un sitio por lo demás estático. La extensión decide qué devuelve la ruta; prerender decide cuándo se calcula.