Tutorial: Primeros pasos con Astro — 7. Montando la página de About
- 1. Creando el proyecto en Content Island
- 2. Definiendo el modelo de datos
- 3. Añadiendo datos
- 4. Creando el proyecto en Astro
- 5. Creando la estructura de carpetas
- 6. Conectando Content Island con Astro
- 7. Montando la página de About
- 8. Página con lista de posts
- 9. Montando un post
- 10. Despliegue automático con Astro y Content Island usando GitHub Actions
- 11. Desplegando en Netlify
- 12. Automatizar el despliegue con GitHub Webhooks + Content Island
7. Montando la página de About
Vamos a montar la página de About, aquí podemos tomar dos aproximaciones:
Una sería montar el HTML de forma estática en Astro y después enlazar los datos de Content Island.
La otra es traernos los datos de Content Island, montar un HTML sin estilo, conectarlo todo y, cuando esté todo listo, añadir los estilos.
Ambas aproximaciones son igual de válidas, depende de tus preferencias y de cómo te sientas más cómodo. Quizás, si eres desarrollador, prefieras asegurarte que los datos cargan y, luego, pasarle este trabajo a un diseñador y que haga su magia. :)
Vamos a por la segunda opción, nos ponemos nuestras gafas de desarrollador y empezamos a trabajar.
Lo primero, estamos trabajando con TypeScript, vamos a definir el modelo que nos traemos de la entidad About de Content Island.
Si nos vamos a Content Island, en la pestaña de modelo, podemos ver que tenemos un modelo de About, con los siguientes campos:
- picture: mi foto de perfil.
- fullname: mi nombre y apellidos.
- minibio: Mini bio.
- bio: Bio extendida.
Toca pasar esto a código, pero espera... Content Island no generar este módelo por nosotros, sólo tenemos que pinchar en el icono TS que hay en la barra del nombre de la entidad y copiar y pegar el interfaz generado.
El código resultante:
./src/pages/about/about.model.ts
import type { Media } from '@content-island/api-client';
export interface About {
id: string;
language: 'en';
picture: Media;
fullname: string;
shortBio: string;
extendedBio: string;
}En este ejemplo hemos puesto el fichero model.ts debajo de pages/about, esto puede ser valido mientras pongamos ficheros con extensión .ts, pero si ponemos ficheros .astro para componentes que no sen páginas, podemos tener problemas ya Astro puede pensar que ese componete es una página, otra aproximación valida sería crear una carpeta de api debajo de src para ponerte estas entradas.
Y ahora vamos a definir una API para extraer estos datos.
¿Qué vamos a hacer aquí?
Definir una funcíon asíncrona "getAbout" que nos traiga los datos de Content Island.
Aquí tiramos de la librería de Content Island y nos traemos los datos de About. Para ello, usamos la función "getContent", a la que le pasamos el id de nuestro contenido y el "contentType" de About, para mapear el resultado de la consulta a nuestro modelo le pasamos como genérico el interfaz que acabamos de crear.
./src/pages/about/about.api.ts
import client from "../../lib/client";
import type { About } from "./about.model";
export async function getAbout(): Promise<About> {
const aboutContent = await client.getContent<About>(
{
// TODO: IMPORTANTE PON EL ID QUE TENGAS EN TU INSTANCIA
// DE About en Content Island
id: "685eb36e45767b0f312fce64",
contentType: "About",
}
);
return aboutContent;
}Ahora que lo tenemos, vamos a la página de About a montar un layout muy básico para ver que se están mostrando los datos.
Lo primero, vamos a por el código que se ejecuta en el servidor en tiempo de generación del sitio (el que está entre rejas :))
./src/pages/about/index.astro
---
import Layout from '../../layouts/Layout.astro';
+ import { getAbout } from './about.api.ts';
+ const about = await getAbout();
---Y vamos a mostrar la información:
./src/pages/about/index.astro
<Layout>
<div class="container about">
<h1>About</h1>
<img src={about.picture.url} alt={about.picture.name} />
<h2>{about.fullname}</h2>
<p><strong>Short Bio:</strong> {about.shortBio}</p>
<p><strong>Extended Bio:</strong> {about.extendedBio}</p>
</Layout>¡Ey! Ya se ve algo. Mmm falta una cosa: el campo de bio extendida. Este campo tiene algo especial y es que está en MarkDown. Como lo estamos generando de forma dinámica vamos a instalar una librería para procesarlo.
npm install markedY ya que estamos (nos preparamos para los blogs), vamos a instalar otra librería que haga syntax highlighting para cuando peguemos código en el Markdown.
npm install highlight.jsPara esta librería, necesitamos añadir el CSS del tema que queramos en el layout. Aquí podemos directamente importarlo desde la carpeta de node_modules o referenciar a una CDN, Vamos a hacer lo segundo:
./src/layouts/Layout.astro
Y añadimos:
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github-dark.min.css"
/>Vamos a darle un poco de estilo y vamos a procesar el markdown:
Primero en el código entre rejas
---
+ import { marked } from 'marked';
import Layout from '../../layouts/Layout.astro';
import { getAbout } from './about.api.ts';
const about = await getAbout();
+ const aboutExtendedBio = marked(about.extendedBio || '');
---Al final del componente, añadimos la parte de estilado:
<style>
.about {
display: flex;
flex-direction: column;
gap: var(--space-lg);
}
.about__header {
display: flex;
flex-direction: column;
gap: var(--space-md);
align-items: flex-end;
}
@media screen and (min-width: 768px) {
.about__header {
flex-direction: row;
}
}
.about__intro {
display: flex;
flex-direction: column;
gap: var(--space-xs);
h1 {
font-size: var(--fs-4xl);
font-family: var(--ff-title);
width: 10ch;
line-height: 1.1;
}
p {
max-width: 30ch;
font-size: var(--fs-md);
}
}
@media screen and (min-width: 768px) {
.about__intro {
padding-bottom: var(--space-md);
}
}
.about__image {
width: 100%;
object-fit: cover;
aspect-ratio: 16/9;
border-radius: var(--border-radius);
}
@media screen and (min-width: 768px) {
.about__image {
width: 50%;
aspect-ratio: 10/16;
}
}
@media screen and (min-width: 1024px) {
.about__image {
width: 30%;
}
}
.about__bio {
max-width: 800px;
margin-right: auto;
margin-left: auto;
display: flex;
flex-direction: column;
gap: var(--space-lg);
img {
width: 100%;
object-fit: cover;
aspect-ratio: 21/9;
border-radius: var(--border-radius);
}
}
</style>Y vamos a cambiar el layout:
+ <div class="container about">
- <h1>About</h1>
- <img src={about.picture.link} alt={about.fullname} />
-
- <h2>{about.fullname}</h2>
- <p><strong>Short Bio:</strong> {about.shortBio}</p>
- <p><strong>Extended Bio:</strong> {about.extendedBio}</p>
+
+ <div class="about__header">
+ <img src={about.picture.url} alt={about.picture.name} class="about__image" />
+ <div class="about__intro">
+ <h1>I'm, <br /> {about.fullname}</h1>
+ <p>
+ {about.shortBio}
+ </p>
+ </div>
+ </div>
+ <div set:html={aboutExtendedBio} class="about__bio" />
+ </div>
+ </div>En este código estamos utilizando la directiva de Astro set:html que permite inyectar HTML directamente en el DOM, es algo parecido a dangerouslySetInnerHTML de React, o a usar en JavaScript plano innerHTML, si no tenemos control sobre el markdown que vamos a leer es buena idea pasarle una librería como sanitize para evitar que nos puedan inyectar código malicioso y evitar ataques XSS.
Vaya, esto tiene buena pinta. :)
¿Vamos a por la lista de posts?