Curso de Astro

🎯 Objetivo del proyecto

Construir una web personal o portfolio profesional con:

  • Página principal
  • Página “Sobre mí”
  • Listado de proyectos (usando Markdown)
  • Contacto
  • Soporte multilenguaje (ES / EN)
  • Layouts y componentes reutilizables
  • Estilos con Tailwind CSS
  • Despliegue en Vercel

🧱 Paso 1. Crear el proyecto

En la terminal:

1npm create astro@latest mi-portfolio
2cd mi-portfolio
3npm install
4npm run dev

Tu servidor local estará en http://localhost:4321.


🎨 Paso 2. Instalar Tailwind CSS

1npx astro add tailwind

Esto creará los archivos necesarios:

  • tailwind.config.mjs
  • postcss.config.mjs

Y añadirá los estilos de Tailwind automáticamente.


🧩 Paso 3. Crear la estructura base

src/
├── components/
│   ├── Header.astro
│   ├── Footer.astro
│   └── ProjectCard.astro
├── layouts/
│   └── BaseLayout.astro
├── content/
│   └── projects/
│       ├── primer-proyecto.md
│       └── segundo-proyecto.md
├── i18n/
│   ├── es.json
│   └── en.json
└── pages/
    ├── [lang]/
    │   ├── index.astro
    │   ├── about.astro
    │   └── contact.astro
    └── api/
        └── message.js

🧱 Paso 4. Layout base

📄 src/layouts/BaseLayout.astro

1---
2import Header from '../components/Header.astro';
3import Footer from '../components/Footer.astro';
4const { title, lang, children } = Astro.props;
5---
6
7<html lang={lang}>
8  <head>
9    <meta charset="UTF-8" />
10    <title>{title}</title>
11    <meta name="description" content="Portfolio creado con Astro" />
12    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
13  </head>
14
15  <body class="bg-gray-50 text-gray-900">
16    <Header lang={lang} />
17    <main class="max-w-4xl mx-auto p-6">{children}</main>
18    <Footer />
19  </body>
20</html>

🧭 Paso 5. Componentes principales

📄 src/components/Header.astro

1---
2const { lang } = Astro.props;
3---
4
5<header class="bg-indigo-600 text-white p-4 flex justify-between">
6  <h1 class="text-lg font-bold">Mi Portfolio</h1>
7  <nav class="space-x-4">
8    <a href={`/${lang}/`} class="hover:underline">Inicio</a>
9    <a href={`/${lang}/about`} class="hover:underline">Sobre mí</a>
10    <a href={`/${lang}/contact`} class="hover:underline">Contacto</a>
11  </nav>
12</header>

📄 src/components/Footer.astro

1<footer class="text-center text-gray-500 mt-10 py-6 border-t">
2  © {new Date().getFullYear()} — Desarrollado con 💜 Astro
3</footer>

📄 Paso 6. Crear contenido con Markdown

Ejemplo de un proyecto: 📄 src/content/projects/primer-proyecto.md

1---
2title: "Blog con Astro"
3date: "2025-10-06"
4description: "Un blog moderno usando Markdown y Astro"
5tech: ["Astro", "Tailwind", "Markdown"]
6---
7
8Este proyecto demuestra cómo usar contenido dinámico con Astro.  
9Los posts se crean automáticamente desde archivos `.md`.

⚙️ Paso 7. Mostrar proyectos en la página principal

📄 src/pages/[lang]/index.astro

1---
2import BaseLayout from '../../layouts/BaseLayout.astro';
3import { getCollection } from 'astro:content';
4import ProjectCard from '../../components/ProjectCard.astro';
5import es from '../../i18n/es.json';
6import en from '../../i18n/en.json';
7
8const { lang } = Astro.params;
9const t = lang === 'es' ? es : en;
10const projects = await getCollection('projects');
11---
12
13<BaseLayout title={t.home.title} lang={lang}>
14  <h1 class="text-3xl font-bold text-center mb-8">{t.home.heading}</h1>
15  <div class="grid md:grid-cols-2 gap-6">
16    {projects.map((p) => <ProjectCard data={p.data} />)}
17  </div>
18</BaseLayout>

📄 src/components/ProjectCard.astro

1---
2const { data } = Astro.props;
3---
4
5<div class="p-6 bg-white shadow-md rounded-lg hover:shadow-lg transition">
6  <h2 class="text-xl font-semibold text-indigo-600">{data.title}</h2>
7  <p class="text-gray-700">{data.description}</p>
8  <p class="text-sm text-gray-500 mt-2">Tecnologías: {data.tech.join(", ")}</p>
9</div>

🧩 Paso 8. Añadir idiomas (i18n)

📄 src/i18n/es.json

1{
2  "home": {
3    "title": "Inicio",
4    "heading": "Mis proyectos"
5  },
6  "about": {
7    "title": "Sobre mí",
8    "text": "Soy desarrollador web apasionado por la enseñanza y las tecnologías modernas."
9  },
10  "contact": {
11    "title": "Contacto",
12    "text": "Puedes escribirme a mi correo o conectar por redes sociales."
13  }
14}

📄 src/i18n/en.json

1{
2  "home": {
3    "title": "Home",
4    "heading": "My Projects"
5  },
6  "about": {
7    "title": "About me",
8    "text": "I'm a web developer passionate about teaching and modern technologies."
9  },
10  "contact": {
11    "title": "Contact",
12    "text": "Feel free to reach me by email or social media."
13  }
14}

✉️ Paso 9. Crear una pequeña API para el formulario de contacto

📄 src/pages/api/message.js

1export async function POST({ request }) {
2  const data = await request.json();
3  console.log("Mensaje recibido:", data);
4  return new Response(JSON.stringify({ ok: true }), {
5    headers: { "Content-Type": "application/json" },
6  });
7}

📄 src/pages/[lang]/contact.astro

1---
2import BaseLayout from '../../layouts/BaseLayout.astro';
3import es from '../../i18n/es.json';
4import en from '../../i18n/en.json';
5const { lang } = Astro.params;
6const t = lang === 'es' ? es : en;
7---
8
9<BaseLayout title={t.contact.title} lang={lang}>
10  <h1 class="text-2xl font-bold mb-4">{t.contact.title}</h1>
11  <p class="mb-4">{t.contact.text}</p>
12
13  <form id="contact-form" class="space-y-4">
14    <input type="text" placeholder="Tu nombre" required class="border p-2 w-full" />
15    <textarea placeholder="Tu mensaje" required class="border p-2 w-full h-24"></textarea>
16    <button type="submit" class="bg-indigo-600 text-white px-4 py-2 rounded">Enviar</button>
17  </form>
18
19  <script>
20    const form = document.querySelector("#contact-form");
21    form.addEventListener("submit", async (e) => {
22      e.preventDefault();
23      const data = { name: form[0].value, message: form[1].value };
24      await fetch("/api/message", {
25        method: "POST",
26        headers: { "Content-Type": "application/json" },
27        body: JSON.stringify(data),
28      });
29      alert("Mensaje enviado correctamente!");
30      form.reset();
31    });
32  </script>
33</BaseLayout>

🌍 Paso 10. Despliegue final

  1. Ejecuta:

    1npm run build
    2npm run preview
  2. Crea un repositorio GitHub y súbelo.

  3. Despliega con:

    1vercel
  4. 🎉 Tu web estará disponible en minutos en:

    https://mi-portfolio.vercel.app