wandres.dev
ROUTING POR ARCHIVOS · src/pages

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.

⏱ 15 min

Enrutar por ficheros no obliga a un único formato. Bajo src/pages, Astro reconoce varias extensiones y cada una encaja con un tipo de contenido: componentes ricos, prosa en Markdown, prosa con islas, o respuestas de API que no son HTML. Todas comparten el mismo destino —una URL— pero difieren en qué te dejan escribir dentro y en qué devuelven al visitante.

🎯 Al terminar esta lección sabrás
  • Distinguir los formatos de página .astro, .md y .mdx y cuándo usar cada uno.
  • Conectar un Markdown con una plantilla mediante la clave layout del frontmatter.
  • Escribir endpoints en .js o .ts que devuelven un objeto Response.
  • Entender cómo un endpoint cambia de comportamiento 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.

🚀

.astro

El formato nativo: HTML con frontmatter de servidor, expresiones, componentes e islas. La página de máxima potencia.

📝

.md

Markdown puro con frontmatter. Ideal para prosa: un aviso legal, una nota. Sin componentes ni expresiones.

🧬

.mdx

Markdown que además admite componentes y expresiones. Prosa con islas incrustadas. Requiere la integración MDX.

🌐

.html

HTML estático literal, sin expresiones ni componentes. Un caso de nicho para marcado que no debe procesarse.

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.

---
layout: ../../layouts/Base.astro
title: Sobre nosotros
---

# Sobre nosotros

Texto 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.

📝
Páginas sueltas frente a colecciones

Colocar Markdown en src/pages es cómodo para páginas únicas —un aviso legal, una portada—. Para cuerpos grandes y homogéneos —un blog, una documentación— casi siempre querrás colecciones en src/content, con esquema tipado y una plantilla dinámica que las renderice. La regla práctica: una página aislada puede vivir en src/pages; un conjunto que comparte forma pide una colección.

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.

// src/pages/api/estado.ts  ->  /api/estado
import 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.

// 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.

flowchart TD
E[endpoint en src pages] --> M{modo de renderizado}
M -->|estatico| S[se ejecuta en el build]
M -->|bajo demanda| D[se ejecuta por peticion]
S --> SF[genera un fichero fijo]
S --> SG[solo tiene sentido GET]
D --> DR[responde en tiempo real]
D --> DV[admite POST PUT DELETE]
style E fill:#89b4fa,color:#11111b
style SF fill:#a6e3a1,color:#11111b
style DR fill:#a6e3a1,color:#11111b

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.

📝
La extensión no decide el modo, solo la salida

Es tentador asociar los endpoints con lo dinámico y las páginas con lo estático, pero son ejes independientes. Un .astro puede renderizarse bajo demanda y un .ts puede prerenderizarse a un fichero fijo. La extensión elige el tipo de salida —HTML o cualquier otra cosa—; output y prerender eligen cuándo se calcula. Mantener separadas ambas decisiones te ahorra reglas falsas del estilo los endpoints son siempre dinámicos, que tarde o temprano te llevan a una sorpresa.

Páginas y endpoints son dialectos de una misma lengua

Astro trata páginas y endpoints como dos dialectos de un mismo idioma, y esa unidad esconde una elección de diseño elegante. Un .astro, un .md y un .ts parecen cosas distintas —una plantilla, un texto, un manejador— pero en el fondo todos hacen lo mismo: reciben un contexto de petición y devuelven una respuesta. La página es un endpoint cuya respuesta resulta ser HTML; el endpoint es una página cuya plantilla decides tú byte a byte. Al fundir ambos en el mismo mecanismo de enrutado por ficheros, Astro evita el error de tantos frameworks: inventar un sistema para las vistas y otro paralelo para la API, con reglas, ficheros y mentalidades separadas. Aquí hay un solo modelo. Y ese modelo se apoya en un estándar de la plataforma web —el objeto Response, el mismo que usan los Service Workers, Deno o los runtimes de edge— en lugar de una abstracción propietaria. La consecuencia es doble. Primero, lo que aprendes es transferible: dominar Response te sirve dentro y fuera de Astro. Segundo, el framework se vuelve más pequeño y más honesto, porque delega en la plataforma en vez de reimplementarla. Un buen framework no multiplica los conceptos: encuentra el único que ya lo explica todo y lo lleva hasta sus últimas consecuencias.

⚔️ Mezcla formatos en tus rutas
  1. Crea src/pages/nota.md con un frontmatter layout y comprueba que la plantilla envuelve el Markdown.
  2. Convierte una página a .mdx, importa un componente y úsalo en mitad del texto; observa que necesitas la integración MDX.
  3. Crea src/pages/datos.json.ts con una función GET que devuelva un Response con JSON y visita /datos.json.
  4. Marca ese endpoint con prerender y razona cómo cambiaría su comportamiento entre un sitio estático y uno bajo demanda.