Rutas dinámicas SSG
- SSG. Static Site Generation
- No necesita configuración extra, como en el SSR donde se debe habilitar en las configuraciones
- Al hacer el build, esto genera una página o ruta por cada valor disponible
- Útil cuando los servicios, productos, recursos, etc. son limitados y/o se conocen antes de construir el proyecto
- No es útil cuando los recursos son dinámicos, como los comentarios de un producto, los posts en Instagram, etc., estos son recursos que se crean y cambian constantemente
Página destino
- El nombre del archivo debe estar entre llaves
[], ejemplo,[id].astro - El archivo debe estar dentro de una carpeta que servirá para la ruta, ej.
src/pages/productos/[slug].astro
- En VSC utilizar el snippet
getStaticPaths getStaticPathses necesario para saber que rutas se deben construir. No es necesario si se utilizaserverohybrid, pero si es estático (SSG) entonces sí- Las rutas se pueden escribir una a una o llamando una API externa, como
GET api/products - En tiempo de construcción se crean solo las páginas definidas en la función, todo lo demás será 404
src\pages\pokemon[name].astro
---
import type { GetStaticPaths } from "astro";
export const getStaticPaths = (() => {
return [
{
// `params.name` es lo que se definió en la ruta
// `src/pages/pokemon/[name].astro`
params: { name: "charmander" },
props: {
name: "charmander",
url: "https://pokeapi.co/api/v2/pokemon/4/",
},
},
{
params: { name: "bulbasaur", },
props: {
name: "bulbasaur",
url: "https://pokeapi.co/api/v2/pokemon/1/",
},
},
];
}) satisfies GetStaticPaths;
const { name } = Astro.params; // Extraer params
const { url } = Astro.props; // Extraer props
const id = url.split("/").at(-2);
const audioSrc = `https://page/${id}.ogg`;
---
<MainLayout title={name}>
<a href="/">Regresar</a>
<h1>{name}</h1>
<PokemonCard name={name} url={url} />
<audio controls>
<source src={audioSrc} type="audio/ogg" />
Your browser does not support the audio element.
</audio>
</MainLayout>
Crear múltiples páginas estáticas
Ejemplo 1
- Devolviendo el response (tipado) sin modificaciones
src\pages\pokemon[name].astro
---
import type { GetStaticPaths } from "astro";
import type { PokemonListResponse } from "../../interfaces/pokemon-list.response";
// =====================================================================
export const getStaticPaths = (async () => {
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151");
const { results } = (await res.json()) as PokemonListResponse;
return results.map(({ name, url }) => ({
// `params.name` es lo que se definió en la ruta
// `src/pages/pokemon/[name].astro`
params: { name: name },
props: { name: name, url: url },
}));
}) satisfies GetStaticPaths;
const { name } = Astro.params;
const { url } = Astro.props;
// =====================================================================
const id = url.split("/").at(-2);
const audioSrc = `https://page/${id}.ogg`;
---
<MainLayout title={name}>
<a href="/">Regresar</a>
<h1>{name}</h1>
<PokemonCard name={name} url={url} />
<audio controls class="mt-5">
<source src={audioSrc} type="audio/ogg" />
Your browser does not support the audio element.
</audio>
</MainLayout>
Ejemplo 2
- Manipulando o extrayendo datos del response y devolviéndolos
---
import type { PokemonListResponse } from "@interfaces/pokemon-list.response";
import type { GetStaticPaths } from "astro";
import PokemonCard from "@components/PokemonCard.astro";
import MainLayout from "../../layouts/MainLayout.astro";
// =====================================================================
export const getStaticPaths = (async () => {
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151");
const { results } = (await res.json()) as PokemonListResponse;
return results.map(({ name, url }) => {
// Ej. Extraer el id de la URL del response
const id = url.split("/").at(-2);
// `params.name` es lo que se definió en la ruta
// `src/pages/pokemon/[name].astro`
return {
params: { id: id },
props: { name: name, url: url },
};
});
}) satisfies GetStaticPaths;
// =====================================================================
const { id } = Astro.params;
const { url, name } = Astro.props;
const audioSrc = `https://page/${id}.ogg`;
---
<MainLayout title={name}>
<h1>{name}</h1>
<PokemonCard name={name} url={url} isBig />
<audio controls>
<source src={audioSrc} type="audio/ogg" />
Your browser does not support the audio element.
</audio>
</MainLayout>
Navegación
src\pages\index.astro
<a href={`/pokemon/${name}`} >
<img src={imageUrl} alt={name} />
</a>
Paginación estática
- Es una característica que se desestructura de
getStaticPaths - El archivo debe llamarse
[page].astro - Si en el proyecto están dos rutas dinámicas en el mismo path, ej.
src/users/[name].astroysrc/users/[page].astrono habrá ambigüedad porque cada página implementa su lógica, entonces al hacer la compilación (ya que es SSG) habrán rutas comousers/primero/index.html,users/segundo/index.htmly la paginación se verá comousers/1,users/2 - Si podría haber ambigüedad si, por ejemplo, se ingresa por nombres o parámetros similares, por ejemplo página 1 y ID 1,
src/user/1(paginación) ysrc/user/1(ID)
src\pages\pokemon[page].astro
---
import type { GetStaticPaths } from "astro";
import type { PokemonListResponse } from "../../interfaces/pokemon-list.response";
export const getStaticPaths = (async ({ paginate }) => {
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151");
const { results } = (await res.json()) as PokemonListResponse;
// 1. Devolver los resultados
// 2. Opcional. Elementos por página
return paginate(results, { pageSize: 12 });
}) satisfies GetStaticPaths;
// 3. Extraer el objeto "page" de los props
// (proporcionado por paginate)
const { page } = Astro.props;
---
<MainLayout title="Home | Pokémon Static">
<section>
{
page.data.map((pokemon) => (
<PokemonCard name={pokemon.name} url={pokemon.url} />
))
}
</section>
<section>
<a
href={page.url.prev}
class:list={[
"btn",
{ disabled: !page.url.prev },
]}
>Anteriores</a>
<a
href={page.url.next}
class:list={[
"btn",
{ disabled: !page.url.next },
]}
>Siguientes</a>
<span>Página {page.currentPage}</span>
</section>
</MainLayout>
<style>
.btn {
@apply bg-blue-500 p-2 text-white rounded-md select-none;
}
.disabled {
@apply bg-gray-600 text-gray-400;
}
</style>