Saltar al contenido principal

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
  • getStaticPaths es necesario para saber que rutas se deben construir. No es necesario si se utiliza server o hybrid, 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>
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].astro y src/users/[page].astro no habrá ambigüedad porque cada página implementa su lógica, entonces al hacer la compilación (ya que es SSG) habrán rutas como users/primero/index.html, users/segundo/index.html y la paginación se verá como users/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) y src/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>