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.mjspostcss.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
-
Ejecuta:
1npm run build 2npm run preview -
Crea un repositorio GitHub y súbelo.
-
Despliega con:
1vercel -
🎉 Tu web estará disponible en minutos en:
https://mi-portfolio.vercel.app