La prop page y la navegación
Cada página paginada recibe un objeto page con todo lo que necesita para pintarse y para orientar al lector: la rebanada de datos en page.data, el número actual y el último en currentPage y lastPage, y un conjunto de URLs listas para navegar hacia la anterior, la siguiente, la primera y la última. Cómo leerlas y cómo construir con ellas una barra de paginación honesta.
El objeto page: la rebanada y su contexto
Lo primero que ofrece page es lo que la página debe mostrar y en qué punto del conjunto se encuentra. page.data es el array de elementos de esta página —no la colección entera, solo su tramo—, y a su alrededor hay un puñado de números que sitúan ese tramo dentro del todo.
---// src/pages/blog/[page].astroconst { page } = Astro.props;---<h1>Artículos</h1><p>Página {page.currentPage} de {page.lastPage}</p><p>Mostrando {page.start + 1} a {page.end + 1} de {page.total}</p><ul> {page.data.map((post) => ( <li><a href={`/blog/${post.id}`}>{post.data.title}</a></li> ))}</ul>Cada propiedad tiene un papel claro. page.data es la rebanada que recorres para pintar la lista. page.currentPage es el número de esta página, empezando en uno; page.lastPage es el número de la última, es decir, cuántas páginas hay en total. Y para el clásico rótulo de mostrando tantos de tantos, page.start y page.end son los índices —de base cero— del primer y último elemento de esta página dentro del conjunto, y page.total es el número de elementos de toda la colección. Con esos cinco datos tienes resuelto todo el texto informativo de la paginación sin un solo cálculo propio.
Conviene mirar dos casos límite. Cuando la colección cabe entera en una sola página, page.lastPage vale uno y no hay vecinas: toda la navegación se apagará sola, como veremos enseguida. Y cuando page.total es cero, paginate genera de todos modos una página vacía, así que tu plantilla debe contemplar el caso de una lista sin elementos y no dar por hecho que page.data siempre trae algo que recorrer.
Las URLs de navegación: next, prev, first y last
La joya de page es su propiedad url, un objeto con las direcciones de las páginas relacionadas ya construidas. No compones cadenas ni sumas números a la URL actual: las lees. Y hay una convención de diseño que lo hace especialmente cómodo: cuando una vecina no existe, su URL vale indefinido en lugar de apuntar a una página inválida.
page.url.currentes la dirección de esta misma página, útil para la etiqueta canónica delhead.page.url.preves la de la página anterior, o indefinido si estás en la primera.page.url.nextes la de la siguiente, o indefinido si estás en la última.page.url.firstes la de la primera página, indefinida si ya estás en ella.page.url.lastes la de la última, indefinida si ya estás en ella.
Esa semántica del indefinido no es un descuido: es lo que te permite construir una barra de navegación correcta sin escribir tú la lógica de los bordes. En la primera página no hay anterior ni primera a la que saltar, y Astro te lo comunica dejando esas URLs vacías; en la última ocurre lo simétrico con siguiente y última. Tu plantilla solo tiene que pintar el enlace cuando la URL existe.
Construir la UI: enlaces que se ocultan solos
Con esas piezas, la barra de paginación se escribe casi sola. La regla es uniforme: pinta cada enlace únicamente si su URL está definida. Como los extremos vienen indefinidos justo cuando no aplican, la condición page.url.prev es a la vez la pregunta hay página anterior y la fuente de su dirección.
---const { page } = Astro.props;---<nav aria-label="Paginación"> {page.url.first && <a href={page.url.first}>« Primera</a>} {page.url.prev && <a href={page.url.prev}>‹ Anterior</a>} <span>Página {page.currentPage} de {page.lastPage}</span> {page.url.next && <a href={page.url.next}>Siguiente ›</a>} {page.url.last && <a href={page.url.last}>Última »</a>}</nav>Ese patrón del y lógico —page.url.prev && ...— es el gesto idiomático: si la URL es indefinida, la expresión se corta y no se pinta nada; si existe, se renderiza el enlace con ella. El resultado es una navegación que se adapta a su posición sin un solo if explícito sobre currentPage: en la primera página desaparecen primera y anterior, en la última desaparecen siguiente y última, y en las intermedias aparecen las cuatro. Dejaste que la ausencia de datos, y no una comparación de números, gobierne la interfaz.
Para una paginación numerada —los botones con cada número de página— sí necesitas los enteros: recorres de uno a page.lastPage y compones cada URL, resaltando el que coincide con page.currentPage.
---const { page } = Astro.props;const numeros = Array.from({ length: page.lastPage }, (_, i) => i + 1);---<nav aria-label="Números de página"> {numeros.map((n) => ( <a href={n === 1 ? '/blog' : `/blog/${n}`} aria-current={n === page.currentPage ? 'page' : undefined} >{n}</a> ))}</nav>Ese recorrido de uno a page.lastPage materializa la tira de números, y aria-current marca el actual comparándolo con page.currentPage para que lectores de pantalla y estilos lo distingan sin un atributo extra. Con las flechas de los extremos y esta tira numerada, la barra de paginación queda completa.
Y para el SEO, page.url.current alimenta la etiqueta canónica, mientras que page.url.prev y page.url.next pueden volcarse en las relaciones rel prev y rel next del head para que los buscadores entiendan la serie.