|
| 1 | +Hemos generado una lista de posts estática, pero ¿qué pasa si queremos mostrar una lista paginada de posts? ¿cómo podría hacerlo? |
| 2 | + |
| 3 | +¿Y si quisiera hacerlo paginado? Aquí podría crearme una pagina con los 10 primeros posts y después generar paginas dinámicas, del tipo `blog/[page].astro` para ir renderizando los posts correspondientes a cada página. |
| 4 | + |
| 5 | +Para ver como funcional las páginas dinámica, vamos a ver un ejemplo más sencillo con los tags de un blog. |
| 6 | + |
| 7 | +¿Qué vamos a hacer? |
| 8 | + |
| 9 | +- Suponemos que tenemos un listado conocido de tags (Esto podría sacarse de una API REST, o de un JSON). |
| 10 | + |
| 11 | +- Vamos a crear un componente que muestra el listado de esos tags, y que al pinchar navege a un tag en concreto (lo utilizaremos en las páginas). |
| 12 | + |
| 13 | +- Cuando el usuario pinche en un tag, le llevamos a una página que tendrá el nombre del tag (será una página dinámica, habrán tantas como tags tenga el array). |
| 14 | + |
| 15 | +- Ahí filtramos de la lista de posts, en el frontmatter los que tengan el tag en concreto y lo mostramos (esto en un proyecto real se podría sacar de una API Rest). |
| 16 | + |
| 17 | +Nos arrancamos por crear la página dinámica. |
| 18 | + |
| 19 | +¿De que va esto? |
| 20 | + |
| 21 | +- Vamos a crear una carpeta `tags` dentro de `pages` |
| 22 | + |
| 23 | +- El nombre del parametro va a tener entre corchete el nombre `[tag]`, con eso le indicamos que ese campo va a ser dinamico. |
| 24 | + |
| 25 | +- Lo siguiente es decirle que valores puede aceptar `[tag]`, con eso Astro ya sabe cuantas páginas estaticas tiene que generar y que plantilla usar (una por cada tag en el array). |
| 26 | + |
| 27 | +- Y ahora nos centrams en el parametro, leemos lo que viene del parametro `tag` (es decir `[tag]`) e indicamos que estams mostrando los posts que tengan ese tag. |
| 28 | + |
| 29 | +_./src/pages/tags/[tag].astro_ |
| 30 | + |
| 31 | +```astro |
| 32 | +--- |
| 33 | +import BaseLayout from '../../layouts/base.astro'; |
| 34 | +
|
| 35 | +// Define los tipos para las rutas estáticas |
| 36 | +interface StaticPath { |
| 37 | + params: { |
| 38 | + tag: string; |
| 39 | + }; |
| 40 | +} |
| 41 | +
|
| 42 | +export async function getStaticPaths(): Promise<StaticPath[]> { |
| 43 | + return [ |
| 44 | + { params: { tag: "astro" } }, |
| 45 | + { params: { tag: "blogging" } }, |
| 46 | + { params: { tag: "hola mundo" } }, |
| 47 | + { params: { tag: "learning in public" } }, |
| 48 | + { params: { tag: "successes" } }, |
| 49 | + ]; |
| 50 | +} |
| 51 | +
|
| 52 | +// Tipar los parámetros que recibe la página |
| 53 | +interface Params { |
| 54 | + tag: string; |
| 55 | +} |
| 56 | +
|
| 57 | +const { tag } = Astro.params as Params; |
| 58 | +--- |
| 59 | +<BaseLayout pageTitle={tag}> |
| 60 | + <p>Posts tagged with {tag}</p> |
| 61 | +</BaseLayout> |
| 62 | +``` |
| 63 | + |
| 64 | +Si ahora visitamos, por ejemplo: |
| 65 | + |
| 66 | +- `http://localhost:3000/tags/astro` |
| 67 | +- `http://localhost:3000/tags/blogging` |
| 68 | +- `http://localhost:4321/tags/hola%20mundo` |
| 69 | +- `http://localhost:3000/tags/learning%20in%20public` |
| 70 | +- `http://localhost:3000/tags/successes` |
| 71 | + |
| 72 | +Podemos ver la página correspondiente a cada tag. |
| 73 | + |
| 74 | +Bueno, esto funciona, pero puede ser un poco rollo tener que ir añadiendo tags a mano cada vez que metas uno en un post, ya que esto se ejecuta una sóla vez y ya se genera todo estático, ¿No sería más fácil leer de los posts los tags que tenemos y generar una lista? |
| 75 | + |
| 76 | +Vamos a hacer eso: |
| 77 | + |
| 78 | +- Leemos todos los posts en `.md`. |
| 79 | +- Eso `md` tiene definido un frontmatter con los tags. |
| 80 | +- Iteramos y leemos las listas. |
| 81 | +- Las añadimos a un conjunto de tags. |
| 82 | + |
| 83 | +Para optimizar vamos a leer la lista de posts una sola vez, aprovechando **getStaticPaths**, y s se lo pasamos como prop a cada tag para que despues haga el filtrado. |
| 84 | + |
| 85 | +_./src/pages/tags/[tag].astro_ |
| 86 | + |
| 87 | +```diff |
| 88 | +--- |
| 89 | +import BaseLayout from '../../layouts/base.astro'; |
| 90 | + |
| 91 | ++ // Deberíamos mover esto a un fichero de modelo común |
| 92 | ++ // Ya lo estamos usando en tres sitios |
| 93 | ++ interface Frontmatter { |
| 94 | ++ layout: string; |
| 95 | ++ title: string; |
| 96 | ++ pubDate: string; |
| 97 | ++ description: string; |
| 98 | ++ author: string; |
| 99 | ++ image: { |
| 100 | ++ url: string; |
| 101 | ++ alt: string; |
| 102 | ++ }; |
| 103 | ++ tags: string[]; |
| 104 | ++ } |
| 105 | ++ |
| 106 | ++ // Tipar las props |
| 107 | ++ interface Props { |
| 108 | ++ posts: MarkdownInstance<Record<string, Frontmatter>>[]; |
| 109 | ++ } |
| 110 | + |
| 111 | +// Define los tipos para las rutas estáticas |
| 112 | +interface StaticPath { |
| 113 | + params: { |
| 114 | + tag: string; |
| 115 | ++ props: Props; |
| 116 | + }; |
| 117 | +} |
| 118 | + |
| 119 | +export async function getStaticPaths(): Promise<StaticPath[]> { |
| 120 | ++ const allPosts = await Astro.glob<Frontmatter>('../posts/*.md'); |
| 121 | + |
| 122 | + return [ |
| 123 | +- { params: { tag: "astro" } }, |
| 124 | ++ { params: { tag: "astro"}, props: {posts: allPosts} }, |
| 125 | +- { params: { tag: "blogging" } }, |
| 126 | ++ { params: { tag: "blogging"}, props: {posts: allPosts} }, |
| 127 | +- { params: { tag: "hola mundo" } }, |
| 128 | ++ { params: { tag: "hola mundo"}, props: {posts: allPosts} }, |
| 129 | +- { params: { tag: "learning in public" } }, |
| 130 | ++ { params: { tag: "learning in public"}, props: {posts: allPosts} }, |
| 131 | +- { params: { tag: "successes" }, props: {posts: allPosts} }, |
| 132 | + ]; |
| 133 | +} |
| 134 | + |
| 135 | +// Tipar los parámetros que recibe la página |
| 136 | +interface Params { |
| 137 | + tag: string; |
| 138 | +} |
| 139 | + |
| 140 | + |
| 141 | + |
| 142 | +const { tag } = Astro.params as Params; |
| 143 | ++ const { posts } = Astro.props; |
| 144 | ++ |
| 145 | ++ // Ahora sacamos la lista de posts que tengan el tag |
| 146 | ++ const filteredPosts = posts.filter(post => post.frontmatter.tags?.includes(tag)); |
| 147 | +--- |
| 148 | +``` |
| 149 | + |
| 150 | +Y ahora que tenemos esa lista la podemos mostrar en la misma página de tags |
| 151 | + |
| 152 | +_./src/pages/tags/[tag].astro_ |
| 153 | + |
| 154 | +```diff |
| 155 | +<BaseLayout pageTitle={tag}> |
| 156 | + <p>Posts tagged with {tag}</p> |
| 157 | ++ <ul> |
| 158 | ++ {filteredPosts.map((post) => <li><a href={post.url}>{post.frontmatter.title}</a></li>)} |
| 159 | ++ </ul> |
| 160 | +</BaseLayout> |
| 161 | +``` |
| 162 | + |
| 163 | +Y ya que estamos, podemos incluso usar el componente Blog Post que creamos antes |
| 164 | + |
| 165 | +- Lo importamos. |
| 166 | +- Lo usamos |
| 167 | + |
| 168 | +_./src/pages/tags/[tag].astro_ |
| 169 | + |
| 170 | +```diff |
| 171 | +--- |
| 172 | +import type { MarkdownInstance } from "astro"; |
| 173 | +import BaseLayout from "../../layouts/base.astro"; |
| 174 | ++ import BlogPost from "../../components/blog-post.astro"; |
| 175 | +``` |
| 176 | + |
| 177 | +```diff |
| 178 | +<BaseLayout pageTitle={tag}> |
| 179 | + <p>Posts tagged with {tag}</p> |
| 180 | + <ul> |
| 181 | + { |
| 182 | + filteredPosts.map((post) => ( |
| 183 | +- <li> |
| 184 | +- <a href={post.url}>{post.frontmatter.title}</a> |
| 185 | +- </li> |
| 186 | ++ <BlogPost url={post.url} title={post.frontmatter.title}/> |
| 187 | + )) |
| 188 | + } |
| 189 | + </ul> |
| 190 | +</BaseLayout> |
| 191 | +``` |
| 192 | + |
| 193 | +Vamos ahora a crear la página _index_ de las tags que va a tener la colección completa. |
| 194 | + |
| 195 | +Antes que eso vamos a sacar el modelo de _Frontmatter_ a un fichero común. |
| 196 | + |
| 197 | +_./src/models/blog-post-frontmatter.ts_ |
| 198 | + |
| 199 | +```typescript |
| 200 | +// Deberíamos de llamarlo BlogFrontMatter |
| 201 | +export interface Frontmatter { |
| 202 | + layout: string; |
| 203 | + title: string; |
| 204 | + pubDate: string; |
| 205 | + description: string; |
| 206 | + author: string; |
| 207 | + image: { |
| 208 | + url: string; |
| 209 | + alt: string; |
| 210 | + }; |
| 211 | + tags: string[]; |
| 212 | +} |
| 213 | +``` |
| 214 | + |
| 215 | +Y usarlo en `[tag].astro`` |
| 216 | + |
| 217 | +_./src/pages/tags/[tag].astro_ |
| 218 | + |
| 219 | +```diff |
| 220 | +--- |
| 221 | +import type { MarkdownInstance } from "astro"; |
| 222 | +import BaseLayout from "../../layouts/base.astro"; |
| 223 | ++ import type { Frontmatter } from "../../models/blog-post-frontmatter"; |
| 224 | + |
| 225 | +- // Deberíamos mover esto a un fichero de modelo común |
| 226 | +- // Ya lo estamos usando en tres sitios |
| 227 | +- interface Frontmatter { |
| 228 | +- layout: string; |
| 229 | +- title: string; |
| 230 | +- pubDate: string; |
| 231 | +- description: string; |
| 232 | +- author: string; |
| 233 | +- image: { |
| 234 | +- url: string; |
| 235 | +- alt: string; |
| 236 | +- }; |
| 237 | +- tags: string[]; |
| 238 | +- } |
| 239 | +``` |
| 240 | + |
| 241 | +Y ahora vamos a por el index: |
| 242 | + |
| 243 | +_./src/pages/tags/index.astro_ |
| 244 | + |
| 245 | +```astro |
| 246 | +--- |
| 247 | +import BaseLayout from "../../layouts/base.astro"; |
| 248 | +import type { Frontmatter } from "../../models/blog-post-frontmatter"; |
| 249 | +
|
| 250 | +const allPosts = await Astro.glob<Frontmatter>('../posts/*.md'); |
| 251 | +// Aquí usamos una aproximación más moderna para sacar los tags |
| 252 | +const tags = [...new Set(allPosts.map((post) => post.frontmatter.tags).flat())]; |
| 253 | +const pageTitle = "Tag Index"; |
| 254 | +--- |
| 255 | +``` |
| 256 | + |
| 257 | +Y ahora mostramos la lista: |
| 258 | + |
| 259 | +_./src/pages/tags/index.astro_ |
| 260 | + |
| 261 | +```diff |
| 262 | +--- |
| 263 | ++ <BaseLayout pageTitle={pageTitle}> |
| 264 | ++ <div>{tags.map((tag) => <p>{tag}</p>)}</div> |
| 265 | ++ </BaseLayout> |
| 266 | +``` |
| 267 | + |
| 268 | +Y ya que estamos vamos a convertir esto en enlaces: |
| 269 | + |
| 270 | +_./src/pages/tags/index.astro_ |
| 271 | + |
| 272 | +```diff |
| 273 | + <BaseLayout pageTitle={pageTitle}> |
| 274 | +- <div>{tags.map((tag) => <p>{tag}</p>)}</div> |
| 275 | ++ {tags.map((tag) => <p><a href={`/tags/${tag}`}>{tag}</a></p>)} |
| 276 | + </BaseLayout> |
| 277 | +``` |
| 278 | + |
| 279 | +Y le añadimos unos estilos: |
| 280 | + |
| 281 | +```diff |
| 282 | +</BaseLayout> |
| 283 | + |
| 284 | ++ <style> |
| 285 | ++ a { |
| 286 | ++ color: #00539F; |
| 287 | ++ } |
| 288 | ++ |
| 289 | ++ .tags { |
| 290 | ++ display: flex; |
| 291 | ++ flex-wrap: wrap; |
| 292 | ++ } |
| 293 | ++ |
| 294 | ++ .tag { |
| 295 | ++ margin: 0.25em; |
| 296 | ++ border: dotted 1px #a1a1a1; |
| 297 | ++ border-radius: .5em; |
| 298 | ++ padding: .5em 1em; |
| 299 | ++ font-size: 1.15em; |
| 300 | ++ background-color: #F8FCFD; |
| 301 | ++ } |
| 302 | ++ </style> |
| 303 | +``` |
| 304 | + |
| 305 | +Hey, y si navegamos a `http://localhost:4321/tags`, podemos ver la lista de tags. |
| 306 | + |
| 307 | +Podríamos añadir esta ruta a nuestro menú principal. |
| 308 | + |
| 309 | +_./src/components/navigation.astro_ |
| 310 | + |
| 311 | +```diff |
| 312 | +<div class="nav-links"> |
| 313 | + <a href="/">Home</a> |
| 314 | + <a href="/about/">About</a> |
| 315 | + <a href="/blog/">Blog</a> |
| 316 | ++ <a href="/tags/">Tags</a> |
| 317 | +</div> |
| 318 | +``` |
0 commit comments