Newsletter para devsEntra

Tutorial de Astro: aprende creando tu portfolio web paso a paso

ℹ️ ¿Llegas desde /bootcamp/astro? Este tutorial es la versión consolidada — y ahora actualizada y mantenida — del antiguo bootcamp de Astro de Web Reactiva. Aquí tienes todos los pasos seguidos, en un solo sitio.

¿Has oído hablar de Astro y quieres ponerle las manos encima sin perderte en la documentación? Este tutorial es exactamente eso: una guía pegada al teclado para que construyas un portfolio web real con Astro, paso a paso, sin saltos mágicos.

Vamos a recorrer las piezas que de verdad importan del framework: páginas, layouts, componentes, props, slots, estilos con alcance, fetch a una API externa, sintaxis JSX, markdown y despliegue en la nube. Al final tendrás una web funcional desplegada y, lo más importante, entenderás por qué Astro hace las cosas como las hace.

¿Qué vas a aprender en este tutorial de Astro?

  • Trabajar con uno de los frameworks de creación de páginas con mayores expectativas del sector
  • Crear páginas, layouts y componentes e integrarlos entre ellos
  • Hacer llamadas a una API externa para cargar datos a través de un fetch
  • Desplegar el fruto de tu aprendizaje en la nube

Cómo usar este tutorial

  1. Cada paso está pensado para que puedas hacerlo a tu ritmo, con todo el código necesario.
  2. Avanza secuencialmente: cada bloque se apoya en lo anterior.
  3. Hay varios emojis a lo largo del tutorial que significan algo concreto:
  • 👀 / ⚡️ Ojo, esto es importante
  • 🔍 Definición de algún concepto
  • 💦 Te propongo que hagas algo por tu cuenta
  • 💣 Algo peligroso puede pasar
  • 😱 Da miedo, pero tendrá solución
  • 🌎 Recursos en internet o sabiduría popular
  • ⭐️ / 🎧 Recomendaciones de contenido podcasts o contenido premium de Web Reactiva
  • 🤖 La IA al rescate
  • 💪 ¡Otro avance conseguido!

Esto es lo que vas a lograr al final del tutorial

👀 El diseño no es espectacular, pero el cómo está construida, sí ;)

Puedes ver el resultado final en este StackBlitz.

¿Qué es Astro?

Astro es un framework moderno para crear webs.

En dos palabras: ¡Astro mola!

🌎 Astro.build, la web oficial

Estas son algunas de sus principales características:

Desarrollo basado en componentes

Astro utiliza un enfoque de desarrollo basado en componentes web estándar para permitir la creación de sitios web de una manera fácil y rápida.

Generación de sitios web estáticos

Astro genera sitios web estáticos, lo que significa que los sitios se cargan rápidamente y son fáciles de escalar.

Integración de tecnologías modernas

Astro se relaciona con otros frameworks como React, Vue o Svelte, lo que lo hace más versátil y adaptable.

Utiliza la Islands Architecture para integrar componentes de JavaScript creados con otras librerías en un mismo proyecto.

⭐️ Podcast Premium: Qué es SPA, MPA, SSG, SSR, ISR e Islands Architecture explicado como si fuera un restaurante

Enfoque en el rendimiento

Astro está diseñado para ser rápido y eficiente, lo que lo hace ideal para sitios web de alto rendimiento.

Intenta que el navegador cargue la menor cantidad de JavaScript para que el usuario tenga una mejor experiencia.

💪 ¡Vamos a por ello!

Arrancando el proyecto

Instala todo lo necesario

💡 Puedes crear un proyecto de Astro directamente en la nube con un editor online desde Astro.new. Nosotros seguiremos los pasos en tu ordenador local.

Crea el proyecto

Utilizaremos el asistente de configuración:

npm create astro@latest

Esto arrancará la creación de un nuevo proyecto.

Aunque las preguntas se van modificando en las nuevas versiones, este es el resumen de lo que necesitamos responder:

  • Elegir el nombre del proyecto. Te propone uno pero nosotros escribimos astro-portfolio.
  • Instalar una plantilla: No, queremos la “Empty” porque empezaremos desde cero.
  • Soporte para TypeScript: Sí.
  • Instalar dependencias: Sí (un paso que te ahorras).
  • Inicializar el repositorio: recomiendo un “Sí” siempre que hayas trabajado ya con git antes.

🌎 La documentación de Astro está disponible en castellano. Aquí tienes todas las instrucciones de instalación.

Arranca el servidor de desarrollo

En la terminal solo te quedan dos pasos para ver funcionando tu proyecto:

cd astro-portfolio
npm run dev

Este último paso abrirá tu navegador en la dirección http://localhost:3000.

👀 No te preocupes si ves poca cosa, pronto lo llenaremos.

A partir de aquí el servidor de desarrollo está basado en Vite y se encargará solito de recargar el navegador CADA VEZ que hagas un cambio.

💪 ¡Maravilloso! ¡Tienes todo listo!

🌎 Podcast: Buenas razones para usar Vite

Estructura del proyecto

Las principales carpetas y ficheros que verás en la instalación son estos:

  • 📂 public — Ficheros públicos que no necesitan ser procesados. Por ejemplo, el favicon.
  • 📂 src — Donde vivirá todo tu código.
  • 📂 src/pages — Aquí crearemos las páginas. Tendrás creado ya index.astro que es el contenido que estás viendo en el navegador.
  • 📄 astro.config.mjs — El fichero de configuración de Astro.
  • 📄 package.json — Las dependencias del proyecto y los scripts que hacen funcionar todo el desarrollo.

Creando la primera página

Realmente ya tienes la primera página creada en src/pages/index.astro.

👀 Si no tienes instalada la extensión de Astro probablemente no reconozca bien el contenido de este fichero. Es buen momento para hacerlo.

Ahora sustituye todo el fichero por estas líneas de código:

---
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content="{Astro.generator}" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
      crossorigin="anonymous"
    />
    <title>Soy Astro</title>
  </head>
  <body>
    <div class="container col-xxl-8 px-4 py-5">
      <div class="row flex-lg-row-reverse align-items-center g-5 py-5">
        <div class="col-10 col-sm-8 col-lg-6">
          <img
            src="https://placekitten.com/g/500/400"
            class="d-block mx-lg-auto img-fluid"
            alt="Flamante Astro web"
            width="700"
            height="500"
            loading="lazy"
          />
        </div>
        <div class="col-lg-6">
          <h1 class="display-5 fw-bold lh-1 mb-3">Mi primera Astro web</h1>
          <p class="lead">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
            eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
            ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
            aliquip ex ea commodo consequat.
          </p>
          <div class="d-grid gap-2 d-md-flex justify-content-md-start">
            <a href="/about" class="btn btn-primary btn-lg px-4 me-md-2">Leer más</a>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

👀 Las dos primeras líneas con --- también. Más tarde entenderás qué significan ;)

Verás que se recarga el navegador y te aparece un gatito con un texto y todo bien pintado.

🌎 Estamos usando la librería Bootstrap CSS para centrarnos en Astro y no en toda la creación de los estilos de la web.

💪 Primera página conseguida ;)

Creando la página de “Sobre mí”

🔍 Empecemos a sacar partido a la potencia de Astro creando nuevas páginas.

Ahora duplica src/pages/index.astro en src/pages/about.astro y cambia el título, el texto y el botón:

---
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content="{Astro.generator}" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
      crossorigin="anonymous"
    />
    <title>Sobre mi</title>
  </head>
  <body>
    <div class="container col-xxl-8 px-4 py-5">
      <div class="row flex-lg-row-reverse align-items-center g-5 py-5">
        <div class="col-10 col-sm-8 col-lg-6">
          <img
            src="https://placekitten.com/g/500/400"
            class="d-block mx-lg-auto img-fluid"
            alt="Flamante Astro web"
            width="700"
            height="500"
            loading="lazy"
          />
        </div>
        <div class="col-lg-6">
          <h1 class="display-5 fw-bold lh-1 mb-3">Sobre mi</h1>
          <p class="lead">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
            eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
            ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
            aliquip ex ea commodo consequat.
          </p>
          <div class="d-grid gap-2 d-md-flex justify-content-md-start">
            <a href="/" class="btn btn-primary btn-lg px-4 me-md-2">⟸ Volver</a>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

Si cargas localhost:3000/about verás que aparece una nueva página con los contenidos actualizados.

👀 Prueba a cargar cualquier otra dirección y verás cómo aparece un error 404. Por ejemplo localhost:3000/noexisto.

Layouts y Props

Usando los layouts o “plantillas de página”

👀 Alerta, código duplicado

Si te has dado cuenta index.astro y about.astro son casi iguales. Una de las malas prácticas en programación es duplicar código.

🌎 Dicen los que saben que “El mejor código es el que no se escribe”.

Como queremos hacer las cosas bien desde el principio, nos vamos a meter en otro de los brillantes conceptos de Astro: los layouts.

🔍 Un layout (o “plantilla de página”) es como una plantilla contenedor que nos permite meter dentro páginas de Astro.

Crea la carpeta src/layouts y, dentro, el fichero SiteLayout.astro. Copia y pega este código:

---
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content={Astro.generator} />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
      crossorigin="anonymous"
    />
    <title>Soy Astro</title>
  </head>
  <body>
    <slot />
  </body>
</html>

👀 Fíjate que hemos extraído las partes comunes de nuestras dos páginas a este nuevo layout y hemos incorporado la etiqueta <slot /> propia de Astro. Ahí aparecerá luego nuestro contenido.

Modifica src/pages/index.astro y déjalo así:

---
import SiteLayout from "../layouts/SiteLayout.astro";
---

<SiteLayout>
  <div class="container col-xxl-8 px-4 py-5">
    <div class="row flex-lg-row-reverse align-items-center g-5 py-5">
      <div class="col-10 col-sm-8 col-lg-6">
        <img
          src="https://placekitten.com/g/500/400"
          class="d-block mx-lg-auto img-fluid"
          alt="Flamante Astro web"
          width="700"
          height="500"
          loading="lazy"
        />
      </div>
      <div class="col-lg-6">
        <h1 class="display-5 fw-bold lh-1 mb-3">Mi primera Astro web</h1>
        <p class="lead">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
          eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
          ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
          aliquip ex ea commodo consequat.
        </p>
        <div class="d-grid gap-2 d-md-flex justify-content-md-start">
          <a href="/about" class="btn btn-primary btn-lg px-4 me-md-2">Leer más</a>
        </div>
      </div>
    </div>
  </div>
</SiteLayout>

Aquí han pasado dos cosas importantes:

  1. Hemos usado la parte del “script” (la parte entre las líneas ---) para importar el layout. ¡Eso es JavaScript!
  2. Cambiamos el código repetido para incluirlo dentro de las etiquetas <SiteLayout>. ¡Eso es HTML!

Un layout es un “contenedor”, una plantilla donde “metemos dentro” páginas.

💪 ¡Pues acabas de conseguirlo!

💦 Repite el mismo proceso para la página de about.astro.

🔍 ¿Ves lo que está pasando? Nuestra etiqueta <title> se repite tanto en la home como en /about. Podemos resolverlo muy fácil.

Props

🔍 Las props son propiedades que podemos pasar de un sitio a otro en los componentes de Astro. Realmente SiteLayout.astro es un “componente”, aunque con algunas características especiales.

Cada componente de Astro tiene dos partes bien diferenciadas:

---
// Script del componente (JavaScript)
---
<!-- HTML del componente -->

Ahora queremos que cada page pueda indicarle al layout un title diferente. Para eso usaremos el objeto Astro, disponible en todos los componentes del proyecto.

---
const {title} = Astro.props
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content={Astro.generator} />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
      crossorigin="anonymous"
    />
    <title>{title}</title>
  </head>
  <body>
    <slot />
  </body>
</html>

Astro.props va a ser capaz de leer el atributo title cuando se lo pase una página.

Aplica los cambios a los siguientes ficheros:

<!-- src/pages/about.astro -->
<SiteLayout title="Sobre mi">
<!-- src/pages/index.astro -->
<SiteLayout title="Soy Astro">

💪 ¡Visita las páginas y lo tienes hecho!

Componentes en Astro

Es el momento de trabajar de forma más profunda los componentes de Astro.

⚡️ Este es el capítulo más importante del tutorial.

Si conoces otros frameworks orientados a web verás muchas similitudes. Si es la primera vez que ves algo así, estás de suerte, porque Astro lo hace MUY fácil.

👀 El Layout que vimos en pasos anteriores es también un componente.

🔍 Un componente es una unidad mínima de funcionalidad reutilizable. Una aplicación web es una unión de varios de estos componentes.

Componente estático

Aprovechemos el fragmento de código donde estamos colocando la foto, el título y la descripción.

Crea la nueva carpeta /src/components y un nuevo fichero dentro Hero.astro:

---
---
<div class="container col-xxl-8 px-4 py-5">
  <div class="row flex-lg-row-reverse align-items-center g-5 py-5">
    <div class="col-10 col-sm-8 col-lg-6">
      <img
        src="https://placekitten.com/g/500/400"
        class="d-block mx-lg-auto img-fluid"
        alt="Flamante página de Astro"
        width="700"
        height="500"
        loading="lazy"
      />
    </div>
    <div class="col-lg-6">
      <h1 class="display-5 fw-bold lh-1 mb-3">Mi primera Astro web</h1>
      <slot />
      <div class="d-grid gap-2 d-md-flex justify-content-md-start">
        <a href="/about" class="btn btn-primary btn-lg px-4 me-md-2">Leer más</a>
      </div>
    </div>
  </div>
</div>

Ahora cambia el index.astro y sustituye todo el contenido por el componente Hero:

---
import Hero from "../components/Hero.astro";
import SiteLayout from "../layouts/SiteLayout.astro";
---

<SiteLayout>
  <div class="container col-xxl-8 px-4 py-5">
    <Hero/>
  </div>
</SiteLayout>

👀 El componente funciona de forma similar a una etiqueta HTML. Como todavía no tenemos ningún elemento hijo (pronto sí, ya verás) la etiqueta puede abrirse y cerrarse con / en la misma etiqueta.

Añadimos props para hacerlo dinámico

Es claro que si queremos reutilizar el componente hay que dotarle de “vida”. Usemos de nuevo los props para pasar atributos desde el padre (index.astro) al componente.

---
const {title, imageUrl, buttonText, buttonLink} = Astro.props
---

<div class="row flex-lg-row-reverse align-items-center g-5 py-5">
  <div class="col-10 col-sm-8 col-lg-6">
    <img
      src={imageUrl}
      class="d-block mx-lg-auto img-fluid"
      alt="Flamante página de Astro"
      width="700"
      height="500"
      loading="lazy"
    />
  </div>
  <div class="col-lg-6">
    <h1 class="display-5 fw-bold lh-1 mb-3">{title}</h1>
    <p class="lead">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
      eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
      minim veniam, quis nostrud exercitation ullamco laboris nisi ut
      aliquip ex ea commodo consequat.
    </p>
    <div class="d-grid gap-2 d-md-flex justify-content-md-start">
      <a href={buttonLink} class="btn btn-primary btn-lg px-4 me-md-2">{buttonText}</a>
    </div>
  </div>
</div>

⚡️ Gracias a Astro.props podemos definir atributos variables. Hemos alterado el componente para que permita modificar el título, la URL de la imagen y el enlace y texto del botón.

👀 Importante: cuando el prop está entre etiquetas usamos la notación {buttonText} y cuando es el valor para un atributo hacemos lo mismo, pero eliminamos las comillas: href={buttonLink}.

Ahora ajustemos el lugar donde llamamos al componente para asignarle los parámetros adecuados:

---
import Hero from "../components/Hero.astro";
import SiteLayout from "../layouts/SiteLayout.astro";
---

<SiteLayout>
  <div class="container col-xxl-8 px-4 py-5">
    <Hero
      title="Mi primera Astro web"
      imageUrl="https://placekitten.com/g/500/400"
      buttonText="Leer más"
      buttonLink="/about"
    />
  </div>
</SiteLayout>

💪 Primer componente dinámico funcionando.

Buenas prácticas: tipos y valores

Vamos a añadir aquí un concepto de TypeScript.

🎧 Podcast: WR 257: TypeScript puede ser tu lenguaje para SIEMPRE

😱 No te asustes, es solo para sacarle partido a la potencia de Astro y su uso interno del lenguaje TypeScript para dar más poder y control al developer… ¡a ti!

Al inicio del componente Hero añadimos esto:

---
interface Props {
  title: string,
  imageUrl: string,
  buttonText: string,
  buttonLink: string
}
const { title, imageUrl, buttonText, buttonLink } = Astro.props;
---

👀 El interface Props es una declaración de variables y tipos. Astro a partir de este momento controlará que los 4 props que hemos definido tengan el tipo string (cadena de texto) y que sean obligatorios.

Si alguno de los props fuera un número podríamos utilizar number en vez de string. Pero, por ahora, tranquilo, no es esencial profundizar en esto.

💦 Vete a index.astro y en la inserción del componente <Hero> prueba a eliminar algún atributo como por ejemplo title.

💪 ¿Ves lo que pasa? Aparece en rojo y te indica que hay un error. Eso es gracias al interface.

💣 Si te encuentras con algunos problemas debidos a TypeScript puedes reducir los requisitos que tiene que cumplir tu código cambiando el contenido del fichero tsconfig.json en tu carpeta raíz:

{
  "extends": "astro/tsconfigs/base"
}

Props opcionales

Acabamos añadiendo una opción más a nuestro interface Props: la capacidad de que alguno de nuestros props sea opcional.

---
interface Props {
  title: string;
  imageUrl: string;
  buttonText?: string;
  buttonLink?: string;
}
const { title, imageUrl, buttonText, buttonLink } = Astro.props;
---

👀 Añadiendo un ? TypeScript y, por tanto, Astro entienden que esos dos props son ahora opcionales.

🔍 Dejamos para un poco más adelante cómo sacar partido a esos props opcionales, lo haremos cuando veamos la sintaxis JSX.

💦 ¿Te animas a reutilizar el componente Hero en about.astro? Es muy fácil. Recuerda importar el componente en la parte encerrada entre las tres líneas ---.

💪 Componentes listos, o casi ;)

Slots en componentes

Nos hemos dejado por el camino un fragmento que no es personalizable en nuestro componente Hero: la descripción en texto largo.

Ahora mismo es un texto dentro de una etiqueta <p> y no es configurable.

¿Podríamos pasarlo con un prop? En informática todo es posible, pero siempre hay un camino más corto.

El fantástico mundo de los slots

Entremos en el mundo de los slots (ranuras, en inglés), pensados precisamente para este caso.

Volvemos a Hero.astro y lo dejamos así:

---
interface Props {
  title: string;
  imageUrl: string;
  buttonText?: string;
  buttonLink?: string;
}
const { title, imageUrl, buttonText, buttonLink } = Astro.props;
---

<div class="row flex-lg-row-reverse align-items-center g-5 py-5">
  <div class="col-10 col-sm-8 col-lg-6">
    <img
      src={imageUrl}
      class="d-block mx-lg-auto img-fluid"
      alt="Flamante página de Astro"
      width="700"
      height="500"
      loading="lazy"
    />
  </div>
  <div class="col-lg-6">
    <h1 class="display-5 fw-bold lh-1 mb-3">{title}</h1>
    <slot />
    <div class="d-grid gap-2 d-md-flex justify-content-md-start">
      <a href={buttonLink} class="btn btn-primary btn-lg px-4 me-md-2">{buttonText}</a>
    </div>
  </div>
</div>

🔍 slot es un componente propio de Astro que nos va a permitir pasar desde el padre al componente hijo etiquetas de HTML.

Fíjate ahora en el cambio en index.astro:

<Hero
  title="Mi primera Astro web"
  imageUrl="https://placekitten.com/g/500/400"
  buttonText="Leer más"
  buttonLink="/about"
>
  <p class="lead">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
    eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
    minim veniam, quis nostrud exercitation ullamco laboris nisi ut
    aliquip ex ea commodo consequat.
  </p>
</Hero>

👀 Abrimos y cerramos <Hero> y encerrado entre las etiquetas está el HTML de la descripción. El componente trasladará ese <p> al lugar donde está definido <slot>.

¡Funciona igual que el HTML!

🔍 Los componentes de Astro permiten más de un slot por componente, pero no lo veremos en el tutorial.

Slot por defecto

Siempre es bueno considerar valores por defecto. Seguramente nos ahorre escribir menos código en la siguiente iteración.

⚡️ Recuerda que tenemos importado Hero.astro tanto en index.astro como en about.astro.

Modificamos el componente de nuevo:

---
interface Props {
  title: string;
  imageUrl: string;
  buttonText?: string;
  buttonLink?: string;
}
const { title, imageUrl, buttonText, buttonLink } = Astro.props;
---

<div class="row flex-lg-row-reverse align-items-center g-5 py-5">
  <div class="col-10 col-sm-8 col-lg-6">
    <img
      src={imageUrl}
      class="d-block mx-lg-auto img-fluid"
      alt="Flamante página de Astro"
      width="700"
      height="500"
      loading="lazy"
    />
  </div>
  <div class="col-lg-6">
    <h1 class="display-5 fw-bold lh-1 mb-3">{title}</h1>
    <slot>
      <p class="lead">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
        eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
        minim veniam, quis nostrud exercitation ullamco laboris nisi ut
        aliquip ex ea commodo consequat.
      </p>
    </slot>
    <div class="d-grid gap-2 d-md-flex justify-content-md-start">
      <a href={buttonLink} class="btn btn-primary btn-lg px-4 me-md-2">{buttonText}</a>
    </div>
  </div>
</div>

👀 Usamos el mismo procedimiento que antes, pero dentro del slot. Ese será ahora el valor por defecto.

Vamos a index.astro y lo simplificamos:

---
import Hero from "../components/Hero.astro";
import SiteLayout from "../layouts/SiteLayout.astro";
---

<SiteLayout>
  <div class="container col-xxl-8 px-4 py-5">
    <Hero
      title="Mi primera Astro web"
      imageUrl="https://placekitten.com/g/500/400"
      buttonText="Leer más"
      buttonLink="/about"
    >
    </Hero>
  </div>
</SiteLayout>

💦 Comprueba que todo funciona y haz cambios en about.astro para sobrescribir el valor por defecto del <slot>.

💪 ¡Tienes controlados los componentes!

Estilos CSS y su alcance

Empezamos rematando un poco algunos flecos del portfolio que estamos montando.

Añadiendo componentes de navegación

Crea NavBar.astro en /src/components y copia este contenido:

<header
  class="d-flex flex-wrap justify-content-center justify-content-sm-between px-4 py-3 mb-4 border-bottom"
>
  <a
    href="/"
    class="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-body-emphasis text-decoration-none"
  >
    <span class="fs-4">Mi Astro Web</span>
  </a>

  <ul class="nav nav-pills">
    <li class="nav-item">
      <a href="/" class="nav-link">Inicio</a>
    </li>
    <li class="nav-item"><a href="/about" class="nav-link">Sobre mí</a></li>
  </ul>
</header>

Y ahora lo mismo con Footer.astro:

<footer class="mt-4 border-top fixed-bottom bg-primary">
  <p class="text-center text-white">
    <small>Creado gracias al tutorial de <a href="https://webreactiva.com">Web Reactiva</a></small>
  </p>
</footer>

👀 En este caso nos basta con tener componentes estáticos. Fíjate cómo no hemos usado el separador --- ya que no es obligatorio si no vas a emplear el poder de JavaScript.

⚡️ El enlace no se ve del todo bien, pronto lo vamos a arreglar.

💦 Piensa un momento en dónde crees que puedes incorporar estos componentes. ¡Es arquitectura de software!

Es en SiteLayout.astro, nuestro “supercomponente” que nos deja tener elementos comunes en toda la web.

Así quedaría:

---
import Footer from "../components/Footer.astro";
import NavBar from "../components/NavBar.astro";
const {title} = Astro.props
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content={Astro.generator} />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
      crossorigin="anonymous"
    />
    <title>{title}</title>
  </head>
  <body>
    <NavBar></NavBar>
    <slot />
    <Footer></Footer>
  </body>
</html>

Estilos CSS y alcance

Una parte que no hemos visto hasta ahora de los componentes .astro es su capacidad para soportar definiciones de estilos CSS.

Corrijamos el color del enlace en el componente Footer. Podríamos hacerlo sin ningún problema con las clases de Bootstrap, pero lo haremos más divertido.

Añade en Footer.astro esto al final:

<style>
  a {
    color: white;
  }
</style>

💣 ¿Demasiado arriesgado? Parece que va a cambiar TODOS los enlaces para que tengan color blanco.

Pues no.

🔍 Recuerda que Astro hace cosas por nosotros y en este caso este estilo está solo en el alcance (scope) del componente Footer.

💦 Abre en el navegador las herramientas de desarrollador y comprueba cómo el estilo aplicado a <a> en el Footer tiene unos caracteres extra que lo hacen único.

Estilos globales

Veamos cómo hacer una definición de estilo CSS global para todo el sitio.

Abre SiteLayout.astro y escribe esto al final:

<style is:global>
  body {
    background-color: lemonchiffon;
  }
</style>

Aparecerá un fondo parecido al amarillo en todas las páginas. Si bien es cierto que el componente de layout afecta a todas las páginas, es una buena práctica usar el modificador is:global para que Astro sepa que todos los body deben tener ese color de fondo.

👀 Considero una buena práctica aunar los estilos globales en un solo componente o en un fichero CSS específico para que luego, si el proyecto crece, no nos volvamos locos buscando dónde están.

Captura de datos con fetch

Es el momento de añadir tus proyectos al portfolio. Aprovechemos lo que, seguramente, ya tienes comenzado en Github.

🎧 Podcast: WR 193: Tu portfolio tiene que ser como un cocodrilo

Accediendo a la lista de repositorios de Github

Github posee una API muy potente para trabajar desde fuera de la plataforma con los datos y acciones.

🔍 Una herramienta práctica y abierta sin necesidad de registro es una API que devuelve los datos de los repositorios públicos de un usuario.

Si accedes a https://api.github.com/users/webreactiva-devs/repos verás un JSON con los repos en abierto de la cuenta de Web Reactiva.

Cambia webreactiva-devs por tu nombre de usuario y, ¡ya los tienes!

Es el momento de sacar partido al poder de JavaScript y de Astro para generar un bloque capturando estos datos. Crea un nuevo Projects.astro en /src/components:

---
const response = await fetch('https://api.github.com/users/webreactiva-devs/repos');
const projects = await response.json();
console.log(projects)
---

<div>
  <h2>Mis proyectos en Github</h2>
</div>

👀 Fíjate cómo en la parte del componente Astro encerrada entre --- puedes colocar JavaScript normal.

⚡️ Usamos await para esperar la respuesta de la API de Github. No hace falta colocarlo dentro de un async porque Astro lo hace por nosotros.

Incluye este nuevo componente en index.astro:

---
import Hero from "../components/Hero.astro";
import Projects from "../components/Projects.astro";
import SiteLayout from "../layouts/SiteLayout.astro";
---

<SiteLayout>
  <div class="container col-xxl-8 px-4 py-5">
    <Hero
      title="Mi primera Astro web"
      imageUrl="https://placekitten.com/g/500/400"
      buttonText="Leer más"
      buttonLink="/about"
    >
    </Hero>
    <Projects />
  </div>
</SiteLayout>

💦 Recarga la página y luego fíjate en la terminal donde arrancaste npm run dev.

👀 Podrás ver lo que contiene un JSON con el array projects que hemos pintado con el console.log.

Para hacerlo accesible vamos a cambiar un poco el Projects.astro:

---
const response = await fetch('https://api.github.com/users/webreactiva-devs/repos');
const projects = await response.json();
const firstProject = {
  name: projects[0].name,
  html_url: projects[0].html_url
}
---
<div class="border p-4 my-3">
  <h2>Mis proyectos en Github</h2>
  <div><a href={firstProject.html_url}>{firstProject.name}</a></div>
</div>

👀 Para facilitarte la vida es mejor capturar un dato concreto que analizarlos todos. projects es un array, tomamos el primer valor [0]. Dentro de cada elemento del array hay un objeto con varias claves; nos quedamos con name y html_url.

Creando un componente solo con un SVG

⚡️ En el siguiente paso vamos a completar nuestra lista de proyectos, pero ahora vamos a sacar provecho a las ventajas de los componentes.

El formato SVG representa las imágenes como si fueran líneas, círculos, cuadrados y vectores. Eso permite que sea de tipo texto.

Crea un fichero nuevo en src/components/icons/GithubIcon.astro y pega dentro esto:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>

👀 No hace falta que tenga la extensión .svg. Astro se encargará de inyectar este HTML dentro del resto de la web.

💦 Carga este componente en Projects.astro y observa cómo aparecerá el logotipo de Github.

👉 Puedes utilizar el SVG que más te guste, en SVGrepo hay muchos más.

💪 Otro paso de gigantes si llegaste hasta aquí. ¡Vamos a por un conocimiento muy pro!

Sintaxis JSX en Astro

Astro entiende la sintaxis JSX popularizada sobre todo por React.js. En la parte del HTML del componente podremos usar esta forma de escribir código para hacer condicionales, loops o jugar con las variables.

⚡️ Eso no quiere decir que haga falta crear componentes de React (aunque Astro permita esas integraciones), solo es que entiende esa sintaxis.

🎧 Podcast: WR 241: Preguntas y respuestas sobre React

Ejemplo de JSX como condicional

Vamos a retomar el Hero.astro para comprender la sintaxis más útil.

💦 A mí me costó un poco entenderlo, así que tómatelo con paciencia.

En ese componente teníamos un botón con un texto y un enlace que eran opcionales, pero aún así, se pintaban. Vamos a controlar con un condicional ese comportamiento. Justo debajo de <slot /> cambia el código por este:

---
interface Props {
  title: string;
  imageUrl: string;
  buttonText?: string;
  buttonLink?: string;
}
const { title, imageUrl, buttonText, buttonLink } = Astro.props;
---

<div class="row flex-lg-row-reverse align-items-center g-5 py-5">
  <div class="col-10 col-sm-8 col-lg-6">
    <img
      src={imageUrl}
      class="d-block mx-lg-auto img-fluid"
      alt="Flamante página de Astro"
      width="700"
      height="500"
      loading="lazy"
    />
  </div>
  <div class="col-lg-6">
    <h1 class="display-5 fw-bold lh-1 mb-3">{title}</h1>
    <slot>
      <p class="lead">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
        eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
        minim veniam, quis nostrud exercitation ullamco laboris nisi ut
        aliquip ex ea commodo consequat.
      </p>
    </slot>
    {
      buttonText && (
          <div class="d-grid gap-2 d-md-flex justify-content-md-start">
            <a href={buttonLink} class="btn btn-primary btn-lg px-4 me-md-2">
              {buttonText}
            </a>
          </div>
        )
    }
  </div>
</div>

👀 Si buttonText está asignada, entonces sí mostramos el enlace. Incluso podríamos añadir la condición buttonText && buttonLink para hacerlo más poderoso.

Ejemplo de loop con JSX

En el componente Projects.astro nos quedábamos solo con un proyecto, pero queremos mostrar al menos los 5 últimos.

En vez de crear nuestro propio array de proyectos vamos a usar un par de trucos con JavaScript y JSX: slice y map.

Modifica todo el componente:

---
import GithubIcon from "./icons/GithubIcon.astro";
const response = await fetch(
  "https://api.github.com/users/webreactiva-devs/repos"
);
const projects = await response.json();
---

<div class="border p-4 my-3">
  <h2>Mis proyectos en Github</h2>
  <div class="list-group">
    {
      projects.slice(0,5).map((project) => {
        return (
          <a
            href={project.html_url}
            class="js-open-new-window list-group-item list-group-item-action d-flex gap-3 py-3"
          >
            <GithubIcon class="rounded-circle flex-shrink-0" />
            <div class="d-flex gap-2 w-100 justify-content-between">
              <div>
                <h6 class="mb-0">{project.name}</h6>
                <p class="mb-0 opacity-75">{project.description}</p>
              </div>
            </div>
          </a>
        );
      })
    }
  </div>
</div>

⚡️ Recuerda cambiar webreactiva-devs por tu usuario.

👀 Explicamos el código con detalle:

  • Define una lista HTML usando la clase list-group.
  • Dentro de esta lista, el código utiliza una expresión JavaScript entre llaves {}. En Astro y otros marcos similares, las expresiones entre llaves permiten incorporar JavaScript directamente en el HTML.
  • projects.slice(0,5).map((project) => { ... }) es una expresión de JavaScript que recorre los primeros 5 elementos del array projects. Para cada proyecto, el método .map() genera un nuevo elemento de la lista.
  • Dentro de la función .map(), se devuelve un elemento de la lista HTML usando la sintaxis JSX. El atributo href de este enlace es la URL del repositorio del proyecto en Github.
  • Dentro del enlace, se incluye el componente GithubIcon y un div que contiene el nombre del proyecto y su descripción.

💣 Si tienes problemas con map((project) => puede deberse a tu configuración local de TypeScript. En el paso de componentes vimos cómo cambiar la configuración, aunque también puedes cambiarlo por map((project: any) =>.

💪 Ya tienes la web prácticamente al completo. ¡Hurra por ti!

Páginas con markdown

Astro trae compatibilidad por defecto con el formato Markdown y, también, con el formato MDX (un Markdown con superpoderes).

Nos quedaremos con el primero para que veas la fuerza que tiene a la hora de crear nuevas páginas.

🎧 Podcast: WR 262: Crea gráficos y presentaciones con Markdown

Vamos a crear una ruta /about más funcional así que renombra o borra el archivo /src/pages/about.astro (es muy importante para no duplicar rutas) y crea /src/pages/about.md.

Permíteme el lujo de incorporar un texto más real en el “Sobre mí”. Adáptalo como quieras, es solo un ejemplo:

# Sobre mí

¡Hola! Mi nombre es Cris.

Soy una desarrolladora web apasionada por el diseño y la implementación de sitios web únicos e innovadores. Me especializo en el uso de **CSS** y **JavaScript puro** (también conocido como Vanilla JavaScript) para crear experiencias web de alta calidad que son tanto estéticamente agradables como funcionales.

## Mis habilidades

- **CSS:** Tengo un amplio conocimiento y experiencia trabajando con CSS, incluyendo flexbox, grid y animaciones CSS. Me encanta usar CSS para hacer que los sitios web sean visualmente atractivos y únicos.

- **JavaScript:** Puedo usar JavaScript puro para agregar interactividad a cualquier sitio web, sin la necesidad de bibliotecas o marcos adicionales. Tengo experiencia con el manejo de eventos, manipulación del DOM, y más.

## Mis proyectos

He trabajado en una variedad de proyectos, desde sitios web de comercio electrónico hasta aplicaciones web interactivas. Estoy siempre en la búsqueda de nuevas oportunidades para expandir mis habilidades y crear algo especial.

Si quieres ver algunos de los proyectos en los que he trabajado, puedes visitar mi [portafolio](#).

## Contacto

Si te gusta mi trabajo y crees que podría ser un buen ajuste para tu proyecto, no dudes en ponerte en [contacto](#) conmigo. ¡Siempre estoy abierta a nuevas oportunidades y desafíos!

¡Gracias por tomarte el tiempo de conocerme un poco mejor!

⚡️ Visita http://localhost:3000/about y verás que ha desaparecido la foto y todos los estilos, pero que este texto tiene formato con listas y negritas.

Astro hace por ti varias cosas aquí:

  • Genera rutas directamente desde ficheros md.
  • Convierte el Markdown en HTML.

Usando layouts anidados con páginas en markdown

Los separadores --- aparecieron en los ficheros Markdown de la mano de Jekyll, un generador de sitios estáticos.

El objetivo estaba en poder incorporar configuraciones y variables que luego pudieran usarse en la construcción del sitio.

👉 A esta forma de incluir datos se la llama frontmatter.

Astro hereda ese poder, así que si añades esto al comienzo de about.md:

---
layout: ../layouts/SiteLayout.astro
title: Sobre mí
---

⚡️ Astro va a entenderlo todo, que quieres un title para la página y que el contenido va dentro de nuestro layout. Pero si cargas /about verás que no funciona del todo: ni el título aparece en la pestaña del navegador, ni el texto se ve bien colocado.

👉 Vamos a aprovecharnos de otra cualidad de Astro: los layouts anidados.

Crea src/layouts/MarkdownLayout.astro y copia esto dentro:

---
import SiteLayout from "../layouts/SiteLayout.astro";
const {frontmatter} = Astro.props;
---

<SiteLayout title={frontmatter.title}>
  <div class="container">
    <slot />
  </div>
</SiteLayout>

Y ahora, en src/pages/about.md haz este cambio:

---
layout: ../layouts/MarkdownLayout.astro
title: Sobre mí
---

⚡️ ¡Ahora funciona y se ve bien colocado y con estilos!

¿Qué hemos hecho?

  1. Primero, se importa el layout por defecto SiteLayout.
  2. Luego, se extrae la propiedad frontmatter de Astro.props. En Astro, Astro.props es un objeto especial que contiene todas las propiedades pasadas a un componente. La variable frontmatter contiene metadatos para la página actual, como el título, la descripción, la fecha de publicación, etc.
  3. Dentro del bloque HTML, se utiliza el componente SiteLayout importado. A este componente se le pasa una prop title que es igual a frontmatter.title.
  4. Usamos la etiqueta especial <slot /> para que cualquier contenido que se coloque dentro de este componente cuando se utilice en otro lugar aparecerá en el lugar del <slot />.

💣 Podríamos haber intentado hacer otras cosas como incluir el HTML con los estilos, intentar llamar al markdown desde el layout… pero quédate con esto: los componentes (y layouts) son capaces de envolverse entre ellos para darte toda la flexibilidad que necesitas.

💪 ¡Listo para desplegar!

Desplegar tu web de Astro en la nube

🤩 ¡Solo nos queda un paso!

Hay muchas formas de desplegar en la nube una web creada con Astro. Vamos a ver dos que son rápidas y son menos habituales.

🎧 Podcast: ⭐️ WRP 103. Así despliego en producción

Crea una build de tu desarrollo

Primero de todo, ejecuta esto en tu carpeta de proyecto:

npm run build

Si todo va bien, al terminar tendrás una carpeta /dist en tu proyecto.

👀 Fíjate en lo que hay dentro: HTML, CSS, JavaScript y los recursos que hayas añadido del tipo imagen, fuentes, ficheros…

Desplegar con arrastrar y soltar en Netlify

Primera opción, la más rápida de todas.

💦 Date de alta en netlify.com. Para lo que vamos a necesitar basta con la cuenta gratuita.

Cuando hayas completado la información, vete a “Sites” en el menú principal. Verás al final de la página un área para arrastrar y soltar.

Se trata justo de eso. Arrastra la carpeta /dist de la que hablamos antes y suéltala allí.

Netlify va a entender lo que tienes dentro y va a comenzar su proceso de despliegue. Puedes verlo en la pestaña “Deploys”.

💪 Unos engranajes después tendrás el resultado.

Te regala un subdominio del tipo nombre-proyecto.netlify.app.

⚡️ En Netlify también puedes desplegar de forma más eficaz para subir actualizaciones a través de su línea de comandos o conectando un repositorio de Github o Gitlab a tu proyecto.

Desplegar con el CLI de Vercel

Todas estas compañías quieren ponértelo fácil.

En Vercel podemos utilizar su herramienta de línea de comandos para hacer el despliegue y pasar a producción.

💦 Primero, date de alta en vercel.com. De nuevo su parte gratuita te será más que suficiente.

Ahora descarga su herramienta de CLI:

npm i -g vercel

En la carpeta de proyecto ejecuta:

vercel

Te hará varias preguntas:

  • Primero tendrás que autorizar el proceso (se hace a través del navegador, el asistente te guiará por todo el proceso).
  • Si es un proyecto nuevo (evidentemente sí).
  • Nombre del proyecto.
  • Los comandos para lanzarlo (npm run build es el que define por defecto, está correcto).

Procesará todo lo que tienes y, si va bien, te devolverá la URL de tu proyecto.

Verás que si entras en el dashboard de Vercel tendrás ya tu nuevo proyecto con todas sus opciones.

Vercel marca una diferencia entre los “deploys” que van a producción y los que no.

Aunque tu portfolio con Astro ya está disponible, mejor si tenemos una URL más amigable.

💦 Ejecuta vercel --prod en tu carpeta de trabajo del portfolio de Astro.

El proceso se lanza de nuevo, con menos preguntas, y te devolverá la URL definitiva del tipo nombre-proyecto.vercel.app.

👀 Vercel también te permite tener un dominio apuntando a tu proyecto y otras configuraciones interesantes sin salirte de su versión gratuita.

¡Terminaste!

💪 Acabas de construir un portfolio funcional con Astro: con sus páginas, sus layouts, sus componentes con props, slots y estilos, su fetch a una API real y desplegado en la nube. No está nada mal para un solo tutorial.

Si te ha servido este tutorial de Astro, suscríbete a la newsletter dominical de Web Reactiva: cada domingo, 12 recursos para developers modernos sin relleno.

Suscríbete gratis →
Imagen de Daniel Primo

Daniel Primo

CEO en pantuflas de Web Reactiva. Programador y formador en tecnologías que cambian el mundo y a las personas. Activo en linkedin, en substack y canal @webreactiva en telegram

12 recursos para developers cada domingo en tu bandeja de entrada

Además de una skill práctica bien explicada, trucos para mejorar tu futuro profesional y una pizquita de humor útil para el resto de la semana. Gratis.