Saltar a contenido

Translate only the sections you've provided

Astro - The Web Framework for Content-Driven Websites

Use placeholder text for the untranslated sections Wait for you to provide the full text

For now, I'll translate the sections you've shared:

# Node.js version requirements
node --version  # Should be 16.12.0 or later

# Check npm version
npm --version

# Update npm if needed
npm install -g npm@latest
```# Guía Rápida de Astro
```bash
# Create new Astro project
npm create astro@latest

# Create with specific template
npm create astro@latest my-project -- --template blog
npm create astro@latest my-project -- --template portfolio
npm create astro@latest my-project -- --template docs

# Skip prompts with defaults
npm create astro@latest my-project -- --template minimal --yes

# Install dependencies and start
cd my-project
npm install
npm run dev

Astro es un generador de sitios estáticos moderno que ofrece un rendimiento ultrarrápido con una experiencia de desarrollo moderna. Construye sitios de contenido como blogs, marketing y comercio electrónico con tus componentes de interfaz favoritos y despliégalos en cualquier lugar.

# Create project directory
mkdir my-astro-project
cd my-astro-project

# Initialize package.json
npm init -y

# Install Astro
npm install astro

# Create basic structure
mkdir src src/pages src/layouts src/components
touch src/pages/index.astro
touch astro.config.mjs
```[This section appears to be empty in the original text]

## Tabla de Contenidos
- [Instalación](#instalación)
- [Creación de Proyecto](#creación-de-proyecto)
- [Estructura del Proyecto](#estructura-del-proyecto)
- [Componentes](#componentes)
- [Páginas y Enrutamiento](#páginas-y-enrutamiento)
- [Diseños](#diseños)
- [Estilos](#estilos)
- [Colecciones de Contenido](#colecciones-de-contenido)
- [Integraciones](#integraciones)
- [Arquitectura de Islas](#arquitectura-de-islas)
- [Rutas de API](#rutas-de-api)
- [Optimización de Imágenes](#optimización-de-imágenes)
- [Despliegue](#despliegue)
- [Rendimiento](#rendimiento)
- [Pruebas](#pruebas)
- [Resolución de Problemas](#resolución-de-problemas)
- [Mejores Prácticas](#mejores-prácticas)

Would you like me to continue with the rest of the document or clarify how you want me to proceed?```json
{
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro build",
    "preview": "astro preview",
    "astro": "astro"
  }
}

Project Creation

Available Templates

# Official templates
npm create astro@latest -- --template minimal
npm create astro@latest -- --template blog
npm create astro@latest -- --template portfolio
npm create astro@latest -- --template docs
npm create astro@latest -- --template with-tailwindcss
npm create astro@latest -- --template with-react
npm create astro@latest -- --template with-vue
npm create astro@latest -- --template with-svelte
npm create astro@latest -- --template with-preact
npm create astro@latest -- --template with-solid-js

# Community templates
npm create astro@latest -- --template cassidoo/shopify-react-astro
npm create astro@latest -- --template withastro/astro-theme-creek
npm create astro@latest -- --template satnaing/astro-paper

Development Commands

# Start development server
npm run dev

# Start with specific port
npm run dev -- --port 3000

# Start with specific host
npm run dev -- --host 0.0.0.0

# Start with open browser
npm run dev -- --open

# Start with verbose logging
npm run dev -- --verbose

Build Commands

# Build for production
npm run build

# Preview production build
npm run preview

# Check for issues
npx astro check

# Add missing integrations
npx astro add tailwind
npx astro add react
npx astro add vue

Project Structure

Basic Structure

my-astro-project/
├── public/                # Static assets
   ├── favicon.svg
   └── images/
├── src/
   ├── components/        # Reusable components
   ├── layouts/          # Layout components
   ├── pages/            # File-based routing
   ├── styles/           # CSS files
   └── content/          # Content collections
├── astro.config.mjs      # Astro configuration
├── package.json
└── tsconfig.json

Advanced Structure

src/
├── components/
   ├── ui/              # Basic UI components
   ├── layout/          # Layout-specific components
   └── content/         # Content-related components
├── layouts/
   ├── BaseLayout.astro
   ├── BlogLayout.astro
   └── DocsLayout.astro
├── pages/
   ├── index.astro      # Homepage
   ├── about.astro      # About page
   ├── blog/
      ├── index.astro  # Blog index
      └── [slug].astro # Blog post
   └── api/             # API routes
├── content/
   ├── blog/            # Blog posts
   └── docs/            # Documentation
├── styles/
   ├── global.css
   └── components.css
└── utils/
    ├── helpers.ts
    └── constants.ts

Configuration

// astro.config.mjs
import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';
import react from '@astrojs/react';
import mdx from '@astrojs/mdx';

export default defineConfig({
  site: 'https://example.com',
  base: '/',

  integrations: [
    tailwind(),
    react(),
    mdx()
  ],

  markdown: {
    shikiConfig: {
      theme: 'github-dark',
      wrap: true
    }
  },

  vite: {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: `@import "src/styles/variables.scss";`
        }
      }
    }
  },

  server: {
    port: 3000,
    host: true
  },

  build: {
    assets: 'assets'
  }
});

Components

Astro Components

---
// src/components/Card.astro
export interface Props {
  title: string;
  description: string;
  href?: string;
  image?: string;
}

const { title, description, href, image } = Astro.props;
---

<div class="card">
  {image && (
    <img src={image} alt={title} class="card-image" />
  )}

  <div class="card-content">
    <h3 class="card-title">{title}</h3>
    <p class="card-description">{description}</p>

    {href && (
      <a href={href} class="card-link">
        Read more →
      </a>
    )}
  </div>
</div>

<style>
  .card {
    background: white;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    overflow: hidden;
    transition: transform 0.2s ease;
  }

  .card:hover {
    transform: translateY(-2px);
  }

  .card-image {
    width: 100%;
    height: 200px;
    object-fit: cover;
  }

  .card-content {
    padding: 1.5rem;
  }

  .card-title {
    margin: 0 0 0.5rem 0;
    font-size: 1.25rem;
    font-weight: 600;
  }

  .card-description {
    margin: 0 0 1rem 0;
    color: #666;
    line-height: 1.6;
  }

  .card-link {
    color: #3b82f6;
    text-decoration: none;
    font-weight: 500;
  }

  .card-link:hover {
    text-decoration: underline;
  }
</style>

Component with Slots

---
// src/components/Modal.astro
export interface Props {
  isOpen: boolean;
  title: string;
}

const { isOpen, title } = Astro.props;
---

{isOpen && (
  <div class="modal-overlay">
    <div class="modal">
      <header class="modal-header">
        <h2>{title}</h2>
        <button class="modal-close" onclick="closeModal()">×</button>
      </header>

      <div class="modal-body">
        <slot />
      </div>

      <footer class="modal-footer">
        <slot name="footer" />
      </footer>
    </div>
  </div>
)}

<script>
  function closeModal() {
    document.querySelector('.modal-overlay').style.display = 'none';
  }
</script>

<style>
  .modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
  }

  .modal {
    background: white;
    border-radius: 8px;
    max-width: 500px;
    width: 90%;
    max-height: 80vh;
    overflow: auto;
  }

  .modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
    border-bottom: 1px solid #e5e7eb;
  }

  .modal-close {
    background: none;
    border: none;
    font-size: 1.5rem;
    cursor: pointer;
  }

  .modal-body {
    padding: 1rem;
  }

  .modal-footer {
    padding: 1rem;
    border-top: 1px solid #e5e7eb;
  }
</style>

Framework Components

---
// src/components/Counter.astro
---

<div id="counter-container">
  <h3>Counter Example</h3>

  <!-- React Component -->
  <div class="framework-example">
    <h4>React Counter</h4>
    <ReactCounter client:load />
  </div>

  <!-- Vue Component -->
  <div class="framework-example">
    <h4>Vue Counter</h4>
    <VueCounter client:load />
  </div>

  <!-- Svelte Component -->
  <div class="framework-example">
    <h4>Svelte Counter</h4>
    <SvelteCounter client:load />
  </div>
</div>

<style>
  .framework-example {
    margin: 1rem 0;
    padding: 1rem;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
  }
</style>
// src/components/ReactCounter.jsx
import { useState } from 'react';

export default function ReactCounter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <button onClick={() => setCount(count - 1)}>
        Decrement
      </button>
    </div>
  );
}

Component Scripts

---
// src/components/InteractiveCard.astro
export interface Props {
  title: string;
  content: string;
}

const { title, content } = Astro.props;
---

<div class="interactive-card" data-title={title}>
  <h3>{title}</h3>
  <p>{content}</p>
  <button class="toggle-btn">Toggle Details</button>
  <div class="details" style="display: none;">
    <p>Additional details about {title}</p>
  </div>
</div>

<script>
  // Client-side script
  document.addEventListener('DOMContentLoaded', () => {
    const cards = document.querySelectorAll('.interactive-card');

    cards.forEach(card => {
      const button = card.querySelector('.toggle-btn');
      const details = card.querySelector('.details');

      button.addEventListener('click', () => {
        const isVisible = details.style.display !== 'none';
        details.style.display = isVisible ? 'none' : 'block';
        button.textContent = isVisible ? 'Show Details' : 'Hide Details';
      });
    });
  });
</script>

<style>
  .interactive-card {
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    padding: 1rem;
    margin: 1rem 0;
  }

  .toggle-btn {
    background: #3b82f6;
    color: white;
    border: none;
    padding: 0.5rem 1rem;
    border-radius: 4px;
    cursor: pointer;
  }

  .details {
    margin-top: 1rem;
    padding: 1rem;
    background: #f9fafb;
    border-radius: 4px;
  }
</style>

Pages and Routing

File-based Routing

# Route structure
src/pages/
├── index.astro           # / (homepage)
├── about.astro           # /about
├── contact.astro         # /contact
├── blog/
   ├── index.astro       # /blog
   ├── [slug].astro      # /blog/[slug]
   └── tag/
       └── [tag].astro   # /blog/tag/[tag]
├── products/
   ├── index.astro       # /products
   ├── [id].astro        # /products/[id]
   └── [...path].astro   # /products/[...path] (catch-all)
└── api/
    ├── posts.json.ts     # /api/posts.json
    └── contact.ts        # /api/contact

Basic Page

---
// src/pages/about.astro
import Layout from '../layouts/Layout.astro';

const pageTitle = 'About Us';
const description = 'Learn more about our company and mission.';
---

<Layout title={pageTitle} description={description}>
  <main>
    <h1>About Us</h1>

    <section class="hero">
      <h2>Our Mission</h2>
      <p>
        We're dedicated to creating amazing web experiences
        that delight users and drive business results.
      </p>
    </section>

    <section class="team">
      <h2>Our Team</h2>
      <div class="team-grid">
        <div class="team-member">
          <img src="/team/john.jpg" alt="John Doe" />
          <h3>John Doe</h3>
          <p>CEO & Founder</p>
        </div>
        <div class="team-member">
          <img src="/team/jane.jpg" alt="Jane Smith" />
          <h3>Jane Smith</h3>
          <p>CTO</p>
        </div>
      </div>
    </section>
  </main>
</Layout>

<style>
  .hero {
    text-align: center;
    padding: 4rem 0;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    border-radius: 8px;
    margin: 2rem 0;
  }

  .team-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 2rem;
    margin: 2rem 0;
  }

  .team-member {
    text-align: center;
    padding: 1rem;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
  }

  .team-member img {
    width: 150px;
    height: 150px;
    border-radius: 50%;
    object-fit: cover;
    margin-bottom: 1rem;
  }
</style>

Dynamic Pages

---
// src/pages/blog/[slug].astro
import Layout from '../../layouts/BlogLayout.astro';
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const blogEntries = await getCollection('blog');

  return blogEntries.map(entry => ({
    params: { slug: entry.slug },
    props: { entry }
  }));
}

const { entry } = Astro.props;
const { Content } = await entry.render();

const { title, description, publishDate, author, tags } = entry.data;
---

<Layout title={title} description={description}>
  <article class="blog-post">
    <header class="post-header">
      <h1>{title}</h1>
      <div class="post-meta">
        <time datetime={publishDate.toISOString()}>
          {publishDate.toLocaleDateString('en-US', {
            year: 'numeric',
            month: 'long',
            day: 'numeric'
          })}
        </time>
        <span class="author">by {author}</span>
      </div>

      {tags && (
        <div class="tags">
          {tags.map(tag => (
            <a href={`/blog/tag/${tag}`} class="tag">
              #{tag}
            </a>
          ))}
        </div>
      )}
    </header>

    <div class="post-content">
      <Content />
    </div>
  </article>
</Layout>

<style>
  .blog-post {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
  }

  .post-header {
    margin-bottom: 3rem;
    text-align: center;
  }

  .post-meta {
    color: #666;
    margin: 1rem 0;
  }

  .author {
    margin-left: 1rem;
  }

  .tags {
    margin-top: 1rem;
  }

  .tag {
    display: inline-block;
    background: #f3f4f6;
    color: #374151;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    text-decoration: none;
    margin: 0 0.25rem;
    font-size: 0.875rem;
  }

  .tag:hover {
    background: #e5e7eb;
  }

  .post-content {
    line-height: 1.8;
  }

  .post-content h2 {
    margin: 2rem 0 1rem 0;
    color: #1f2937;
  }

  .post-content p {
    margin: 1rem 0;
  }

  .post-content pre {
    background: #f9fafb;
    padding: 1rem;
    border-radius: 4px;
    overflow-x: auto;
  }
</style>

Catch-all Routes

---
// src/pages/docs/[...path].astro
import Layout from '../../layouts/DocsLayout.astro';
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const docs = await getCollection('docs');

  return docs.map(doc => ({
    params: { path: doc.slug },
    props: { doc }
  }));
}

const { doc } = Astro.props;
const { Content, headings } = await doc.render();
---

<Layout title={doc.data.title}>
  <div class="docs-layout">
    <aside class="sidebar">
      <nav class="toc">
        <h3>Table of Contents</h3>
        <ul>
          {headings.map(heading => (
            <li class={`toc-${heading.depth}`}>
              <a href={`#${heading.slug}`}>
                {heading.text}
              </a>
            </li>
          ))}
        </ul>
      </nav>
    </aside>

    <main class="content">
      <h1>{doc.data.title}</h1>
      <Content />
    </main>
  </div>
</Layout>

<style>
  .docs-layout {
    display: grid;
    grid-template-columns: 250px 1fr;
    gap: 2rem;
    max-width: 1200px;
    margin: 0 auto;
    padding: 2rem;
  }

  .sidebar {
    position: sticky;
    top: 2rem;
    height: fit-content;
  }

  .toc ul {
    list-style: none;
    padding: 0;
  }

  .toc li {
    margin: 0.5rem 0;
  }

  .toc-2 {
    padding-left: 1rem;
  }

  .toc-3 {
    padding-left: 2rem;
  }

  .content {
    min-width: 0;
  }
</style>

Layouts

---
// src/layouts/Layout.astro
export interface Props {
  title: string;
  description?: string;
  image?: string;
}

const { title, description = 'Default description', image = '/og-image.jpg' } = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="description" content={description} />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="generator" content={Astro.generator} />

    <!-- Canonical URL -->
    <link rel="canonical" href={canonicalURL} />

    <!-- Open Graph -->
    <meta property="og:type" content="website" />
    <meta property="og:url" content={Astro.url} />
    <meta property="og:title" content={title} />
    <meta property="og:description" content={description} />
    <meta property="og:image" content={new URL(image, Astro.url)} />

    <!-- Twitter -->
    <meta property="twitter:card" content="summary_large_image" />
    <meta property="twitter:url" content={Astro.url} />
    <meta property="twitter:title" content={title} />
    <meta property="twitter:description" content={description} />
    <meta property="twitter:image" content={new URL(image, Astro.url)} />

    <title>{title}</title>
  </head>

  <body>
    <header class="site-header">
      <nav class="nav">
        <a href="/" class="logo">
          <img src="/logo.svg" alt="Logo" />
        </a>

        <ul class="nav-links">
          <li><a href="/">Home</a></li>
          <li><a href="/about">About</a></li>
          <li><a href="/blog">Blog</a></li>
          <li><a href="/contact">Contact</a></li>
        </ul>

        <button class="mobile-menu-toggle" aria-label="Toggle menu">
          <span></span>
          <span></span>
          <span></span>
        </button>
      </nav>
    </header>

    <slot />

    <footer class="site-footer">
      <div class="footer-content">
        <p>&copy; 2024 My Website. All rights reserved.</p>
        <div class="social-links">
          <a href="https://twitter.com" aria-label="Twitter">Twitter</a>
          <a href="https://github.com" aria-label="GitHub">GitHub</a>
        </div>
      </div>
    </footer>
  </body>
</html>

<style is:global>
  html {
    font-family: system-ui, sans-serif;
    scroll-behavior: smooth;
  }

  body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
  }

  main {
    flex: 1;
  }

  .site-header {
    background: white;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    position: sticky;
    top: 0;
    z-index: 100;
  }

  .nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem 2rem;
    max-width: 1200px;
    margin: 0 auto;
  }

  .logo img {
    height: 40px;
  }

  .nav-links {
    display: flex;
    list-style: none;
    margin: 0;
    padding: 0;
    gap: 2rem;
  }

  .nav-links a {
    text-decoration: none;
    color: #374151;
    font-weight: 500;
    transition: color 0.2s;
  }

  .nav-links a:hover {
    color: #3b82f6;
  }

  .mobile-menu-toggle {
    display: none;
    flex-direction: column;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0.5rem;
  }

  .mobile-menu-toggle span {
    width: 25px;
    height: 3px;
    background: #374151;
    margin: 3px 0;
    transition: 0.3s;
  }

  .site-footer {
    background: #1f2937;
    color: white;
    padding: 2rem;
    margin-top: auto;
  }

  .footer-content {
    max-width: 1200px;
    margin: 0 auto;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .social-links {
    display: flex;
    gap: 1rem;
  }

  .social-links a {
    color: white;
    text-decoration: none;
  }

  @media (max-width: 768px) {
    .nav-links {
      display: none;
    }

    .mobile-menu-toggle {
      display: flex;
    }

    .footer-content {
      flex-direction: column;
      gap: 1rem;
      text-align: center;
    }
  }
</style>

<script>
  // Mobile menu toggle
  document.addEventListener('DOMContentLoaded', () => {
    const toggle = document.querySelector('.mobile-menu-toggle');
    const navLinks = document.querySelector('.nav-links');

    toggle?.addEventListener('click', () => {
      navLinks?.classList.toggle('active');
    });
  });
</script>

Base Layout

---
// src/layouts/BlogLayout.astro
import Layout from './Layout.astro';

export interface Props {
  title: string;
  description?: string;
  publishDate?: Date;
  author?: string;
  image?: string;
}

const { title, description, publishDate, author, image } = Astro.props;
---

<Layout title={title} description={description} image={image}>
  <main class="blog-main">
    <div class="container">
      <article class="blog-article">
        <slot />
      </article>

      <aside class="blog-sidebar">
        <div class="sidebar-section">
          <h3>Recent Posts</h3>
          <ul class="recent-posts">
            <!-- Recent posts would be populated here -->
          </ul>
        </div>

        <div class="sidebar-section">
          <h3>Categories</h3>
          <ul class="categories">
            <li><a href="/blog/category/web-development">Web Development</a></li>
            <li><a href="/blog/category/javascript">JavaScript</a></li>
            <li><a href="/blog/category/css">CSS</a></li>
          </ul>
        </div>

        <div class="sidebar-section">
          <h3>Newsletter</h3>
          <form class="newsletter-form">
            <input type="email" placeholder="Your email" required />
            <button type="submit">Subscribe</button>
          </form>
        </div>
      </aside>
    </div>
  </main>
</Layout>

<style>
  .blog-main {
    padding: 2rem 0;
  }

  .container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 2rem;
    display: grid;
    grid-template-columns: 1fr 300px;
    gap: 3rem;
  }

  .blog-article {
    min-width: 0;
  }

  .blog-sidebar {
    position: sticky;
    top: 2rem;
    height: fit-content;
  }

  .sidebar-section {
    background: #f9fafb;
    padding: 1.5rem;
    border-radius: 8px;
    margin-bottom: 2rem;
  }

  .sidebar-section h3 {
    margin: 0 0 1rem 0;
    color: #1f2937;
  }

  .recent-posts,
  .categories {
    list-style: none;
    padding: 0;
    margin: 0;
  }

  .recent-posts li,
  .categories li {
    margin: 0.5rem 0;
  }

  .recent-posts a,
  .categories a {
    color: #374151;
    text-decoration: none;
  }

  .recent-posts a:hover,
  .categories a:hover {
    color: #3b82f6;
  }

  .newsletter-form {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .newsletter-form input {
    padding: 0.5rem;
    border: 1px solid #d1d5db;
    border-radius: 4px;
  }

  .newsletter-form button {
    background: #3b82f6;
    color: white;
    border: none;
    padding: 0.5rem;
    border-radius: 4px;
    cursor: pointer;
  }

  @media (max-width: 768px) {
    .container {
      grid-template-columns: 1fr;
      gap: 2rem;
    }

    .blog-sidebar {
      position: static;
    }
  }
</style>

Blog Layout

/* src/styles/global.css */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');

:root {
  --color-primary: #3b82f6;
  --color-secondary: #64748b;
  --color-success: #10b981;
  --color-warning: #f59e0b;
  --color-error: #ef4444;

  --color-text: #1f2937;
  --color-text-light: #6b7280;
  --color-background: #ffffff;
  --color-surface: #f9fafb;

  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  --spacing-2xl: 3rem;

  --border-radius: 0.375rem;
  --border-radius-lg: 0.5rem;

  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}

* {
  box-sizing: border-box;
}

html {
  font-family: 'Inter', system-ui, sans-serif;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  color: var(--color-text);
  background-color: var(--color-background);
  line-height: 1.6;
}

/* Typography */
h1, h2, h3, h4, h5, h6 {
  margin: 0 0 var(--spacing-md) 0;
  font-weight: 600;
  line-height: 1.3;
}

h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
h3 { font-size: 1.5rem; }
h4 { font-size: 1.25rem; }
h5 { font-size: 1.125rem; }
h6 { font-size: 1rem; }

p {
  margin: 0 0 var(--spacing-md) 0;
}

/* Links */
a {
  color: var(--color-primary);
  text-decoration: none;
  transition: color 0.2s ease;
}

a:hover {
  color: #2563eb;
  text-decoration: underline;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-sm) var(--spacing-md);
  border: none;
  border-radius: var(--border-radius);
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: all 0.2s ease;
}

.btn-primary {
  background-color: var(--color-primary);
  color: white;
}

.btn-primary:hover {
  background-color: #2563eb;
  text-decoration: none;
}

.btn-secondary {
  background-color: var(--color-secondary);
  color: white;
}

.btn-outline {
  background-color: transparent;
  color: var(--color-primary);
  border: 1px solid var(--color-primary);
}

/* Utilities */
.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--spacing-md);
}

.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }

.mt-0 { margin-top: 0; }
.mt-1 { margin-top: var(--spacing-xs); }
.mt-2 { margin-top: var(--spacing-sm); }
.mt-4 { margin-top: var(--spacing-md); }
.mt-6 { margin-top: var(--spacing-lg); }
.mt-8 { margin-top: var(--spacing-xl); }

.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: var(--spacing-xs); }
.mb-2 { margin-bottom: var(--spacing-sm); }
.mb-4 { margin-bottom: var(--spacing-md); }
.mb-6 { margin-bottom: var(--spacing-lg); }
.mb-8 { margin-bottom: var(--spacing-xl); }

/* Responsive */
@media (max-width: 768px) {
  h1 { font-size: 2rem; }
  h2 { font-size: 1.75rem; }
  h3 { font-size: 1.25rem; }

  .container {
    padding: 0 var(--spacing-sm);
  }
}

Styling

---
// src/components/Hero.astro
export interface Props {
  title: string;
  subtitle: string;
  backgroundImage?: string;
}

const { title, subtitle, backgroundImage } = Astro.props;
---

<section class="hero" style={backgroundImage ? `background-image: url(${backgroundImage})` : ''}>
  <div class="hero-content">
    <h1 class="hero-title">{title}</h1>
    <p class="hero-subtitle">{subtitle}</p>
    <div class="hero-actions">
      <a href="/get-started" class="btn btn-primary">Get Started</a>
      <a href="/learn-more" class="btn btn-outline">Learn More</a>
    </div>
  </div>
</section>

<style>
  .hero {
    position: relative;
    min-height: 60vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    background-size: cover;
    background-position: center;
    color: white;
    text-align: center;
  }

  .hero::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 1;
  }

  .hero-content {
    position: relative;
    z-index: 2;
    max-width: 800px;
    padding: 2rem;
  }

  .hero-title {
    font-size: 3.5rem;
    font-weight: 700;
    margin-bottom: 1rem;
    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
  }

  .hero-subtitle {
    font-size: 1.25rem;
    margin-bottom: 2rem;
    opacity: 0.9;
  }

  .hero-actions {
    display: flex;
    gap: 1rem;
    justify-content: center;
    flex-wrap: wrap;
  }

  @media (max-width: 768px) {
    .hero-title {
      font-size: 2.5rem;
    }

    .hero-subtitle {
      font-size: 1.125rem;
    }

    .hero-actions {
      flex-direction: column;
      align-items: center;
    }
  }
</style>

Global Styles

# Install Tailwind CSS
npx astro add tailwind
---
// src/components/Card.astro with Tailwind
export interface Props {
  title: string;
  description: string;
  image?: string;
}

const { title, description, image } = Astro.props;
---

<div class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
  {image && (
    <img 
      src={image} 
      alt={title} 
      class="w-full h-48 object-cover"
    />
  )}

  <div class="p-6">
    <h3 class="text-xl font-semibold text-gray-900 mb-2">{title}</h3>
    <p class="text-gray-600 leading-relaxed">{description}</p>

    <div class="mt-4">
      <a 
        href="#" 
        class="inline-flex items-center text-blue-600 hover:text-blue-800 font-medium"
      >
        Read more
        <svg class="ml-1 w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
          <path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path>
        </svg>
      </a>
    </div>
  </div>
</div>

Component Styles

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    publishDate: z.date(),
    author: z.string(),
    image: z.string().optional(),
    tags: z.array(z.string()).optional(),
    featured: z.boolean().default(false),
    draft: z.boolean().default(false)
  })
});

const docs = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    category: z.string(),
    order: z.number().optional(),
    lastUpdated: z.date().optional()
  })
});

const team = defineCollection({
  type: 'data',
  schema: z.object({
    name: z.string(),
    role: z.string(),
    bio: z.string(),
    avatar: z.string(),
    social: z.object({
      twitter: z.string().optional(),
      github: z.string().optional(),
      linkedin: z.string().optional()
    }).optional()
  })
});

export const collections = {
  blog,
  docs,
  team
};

Tailwind CSS Integration

---
# src/content/blog/getting-started-with-astro.md
title: "Getting Started with Astro"
description: "Learn how to build fast, content-focused websites with Astro"
publishDate: 2024-01-15
author: "John Doe"
image: "/blog/astro-getting-started.jpg"
tags: ["astro", "web-development", "static-sites"]
featured: true
---

# Getting Started with Astro

Astro is a modern static site generator that delivers lightning-fast performance with a modern developer experience. In this guide, we'll explore how to get started with Astro and build your first website.

## What is Astro?

Astro is designed for building content-rich websites like blogs, marketing sites, and documentation. It uses a unique "Islands Architecture" that allows you to use your favorite UI framework while shipping minimal JavaScript to the browser.

## Key Features

- **Zero JavaScript by default**: Astro generates static HTML with no client-side JavaScript unless you explicitly opt-in
- **Framework agnostic**: Use React, Vue, Svelte, or any other framework
- **File-based routing**: Create pages by adding files to the `src/pages` directory
- **Content collections**: Organize and validate your content with TypeScript

## Installation

Getting started with Astro is simple:

Content Collections

## Your First Component

Here's a simple Astro component:

Configuration

{greeting}

Welcome to the future of web development.

bash npm create astro@latest cd my-project npm install npm run dev

## Conclusion

Astro provides an excellent developer experience while delivering exceptional performance. Its unique approach to JavaScript hydration makes it perfect for content-focused websites.
```astro
---
const greeting = "Hello, Astro!";
---

<div class="greeting">
  ```astro
---
// src/pages/blog/index.astro
import Layout from '../../layouts/Layout.astro';
import Card from '../../components/Card.astro';
import { getCollection } from 'astro:content';

const allBlogPosts = await getCollection('blog', ({ data }) => {
  return !data.draft;
});

// Sort by publish date
const sortedPosts = allBlogPosts.sort((a, b) => 
  b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
);

// Get featured posts
const featuredPosts = sortedPosts.filter(post => post.data.featured);
---

<Layout title="Blog" description="Read our latest blog posts">
  <main class="blog-index">
    <div class="container">
      <header class="page-header">
        <h1>Blog</h1>
        <p>Insights, tutorials, and updates from our team</p>
      </header>

      {featuredPosts.length > 0 && (
        <section class="featured-posts">
          <h2>Featured Posts</h2>
          <div class="posts-grid">
            {featuredPosts.map(post => (
              <Card
                title={post.data.title}
                description={post.data.description}
                href={`/blog/${post.slug}`}
                image={post.data.image}
              />
            ))}
          </div>
        </section>
      )}

      <section class="all-posts">
        <h2>All Posts</h2>
        <div class="posts-grid">
          {sortedPosts.map(post => (
            <Card
              title={post.data.title}
              description={post.data.description}
              href={`/blog/${post.slug}`}
              image={post.data.image}
            />
          ))}
        </div>
      </section>
    </div>
  </main>
</Layout>

<style>
  .blog-index {
    padding: 2rem 0;
  }

  .page-header {
    text-align: center;
    margin-bottom: 3rem;
  }

  .page-header h1 {
    font-size: 3rem;
    margin-bottom: 1rem;
  }

  .featured-posts,
  .all-posts {
    margin-bottom: 3rem;
  }

  .posts-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 2rem;
    margin-top: 2rem;
  }
</style>

# Add React integration
npx astro add react
// src/components/ReactCounter.jsx
import { useState } from 'react';

export default function ReactCounter({ initialCount = 0 }) {
  const [count, setCount] = useState(initialCount);

  return (
    <div className="counter">
      <h3>React Counter</h3>
      <p>Count: {count}</p>
      <div className="counter-buttons">
        <button onClick={() => setCount(count - 1)}>-</button>
        <button onClick={() => setCount(count + 1)}>+</button>
        <button onClick={() => setCount(0)}>Reset</button>
      </div>
    </div>
  );
}
---
// Using React component in Astro
import Layout from '../layouts/Layout.astro';
import ReactCounter from '../components/ReactCounter.jsx';
---

<Layout title="Interactive Demo">
  <main>
    <h1>Interactive Components</h1>

    <!-- Hydrate on page load -->
    <ReactCounter client:load initialCount={5} />

    <!-- Hydrate when visible -->
    <ReactCounter client:visible initialCount={10} />

    <!-- Hydrate on idle -->
    <ReactCounter client:idle />

    <!-- Hydrate on media query -->
    <ReactCounter client:media="(max-width: 768px)" />
  </main>
</Layout>

Using Collections

# Add Vue integration
npx astro add vue
<!-- src/components/VueCounter.vue -->
<template>
  <div class="counter">
    <h3>Vue Counter</h3>
    <p>Count: {{ count }}</p>
    <div class="counter-buttons">
      <button @click="decrement">-</button>
      <button @click="increment">+</button>
      <button @click="reset">Reset</button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const props = defineProps({
  initialCount: {
    type: Number,
    default: 0
  }
});

const count = ref(props.initialCount);

const increment = () => count.value++;
const decrement = () => count.value--;
const reset = () => count.value = 0;
</script>

<style scoped>
.counter {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 1rem;
  margin: 1rem 0;
}

.counter-buttons {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
}

button {
  padding: 0.5rem 1rem;
  border: 1px solid #d1d5db;
  border-radius: 4px;
  background: white;
  cursor: pointer;
}

button:hover {
  background: #f9fafb;
}
</style>

Integrations

# Add MDX integration
npx astro add mdx
---
# src/pages/interactive-post.mdx
title: "Interactive Blog Post"
description: "A blog post with interactive components"
---

import ReactCounter from '../components/ReactCounter.jsx';
import VueCounter from '../components/VueCounter.vue';

# Interactive Blog Post

This is a regular markdown paragraph. But we can also include interactive components:

<ReactCounter client:load initialCount={5} />

Here's another component with different hydration:

<VueCounter client:visible initialCount={10} />

## Code Examples

React Integration

Hello, ${name}!

Vue Integration

And we can continue with regular markdown content...

MDX Integration

---
// src/pages/islands-demo.astro
import Layout from '../layouts/Layout.astro';
import ReactCounter from '../components/ReactCounter.jsx';
import VueChart from '../components/VueChart.vue';
import SvelteWidget from '../components/SvelteWidget.svelte';
---

<Layout title="Islands Architecture Demo">
  <main>
    <h1>Islands Architecture</h1>

    <!-- Load immediately -->
    <ReactCounter client:load />

    <!-- Load when component becomes visible -->
    <VueChart client:visible />

    <!-- Load when browser is idle -->
    <SvelteWidget client:idle />

    <!-- Load only on mobile -->
    <ReactCounter client:media="(max-width: 768px)" />

    <!-- Load only on specific event -->
    <VueChart client:only="vue" />

    <!-- Never hydrate (static only) -->
    <ReactCounter />
  </main>
</Layout>
```javascript
function greet(name) {
  return ```astro
---
// Optimize component loading
import { Image } from 'astro:assets';
import HeavyComponent from '../components/HeavyComponent.jsx';
---

<div class="optimized-page">
  <!-- Optimized images -->
  <Image 
    src="/hero.jpg" 
    alt="Hero image"
    width={800}
    height={600}
    loading="eager"
  />

  <!-- Lazy load heavy components -->
  <HeavyComponent client:visible />

  <!-- Preload critical resources -->
  <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />
</div>
```;
}
```typescript
// src/pages/api/posts.json.ts
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';

export const GET: APIRoute = async ({ params, request }) => {
  const posts = await getCollection('blog');

  return new Response(JSON.stringify(posts), {
    status: 200,
    headers: {
      'Content-Type': 'application/json'
    }
  });
};

export const POST: APIRoute = async ({ request }) => {
  const data = await request.json();

  // Process the data
  console.log('Received:', data);

  return new Response(JSON.stringify({ success: true }), {
    status: 201,
    headers: {
      'Content-Type': 'application/json'
    }
  });
};

Islands Architecture

// src/pages/api/posts/[id].json.ts
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';

export const GET: APIRoute = async ({ params }) => {
  const { id } = params;

  if (!id) {
    return new Response(JSON.stringify({ error: 'ID is required' }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' }
    });
  }

  const posts = await getCollection('blog');
  const post = posts.find(p => p.slug === id);

  if (!post) {
    return new Response(JSON.stringify({ error: 'Post not found' }), {
      status: 404,
      headers: { 'Content-Type': 'application/json' }
    });
  }

  return new Response(JSON.stringify(post), {
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  });
};
```### Manejo de Formularios
```typescript
// src/pages/api/contact.ts
import type { APIRoute } from 'astro';

export const POST: APIRoute = async ({ request }) => {
  try {
    const formData = await request.formData();
    const name = formData.get('name') as string;
    const email = formData.get('email') as string;
    const message = formData.get('message') as string;

    // Validate data
    if (!name || !email || !message) {
      return new Response(JSON.stringify({ 
        error: 'All fields are required' 
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }

    // Process form (send email, save to database, etc.)
    await sendEmail({ name, email, message });

    return new Response(JSON.stringify({ 
      success: true,
      message: 'Thank you for your message!' 
    }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' }
    });

  } catch (error) {
    return new Response(JSON.stringify({ 
      error: 'Internal server error' 
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
};

async function sendEmail(data: { name: string; email: string; message: string }) {
  // Email sending logic
  console.log('Sending email:', data);
}
```## Optimización de Imágenes

### Recursos de Astro
```astro
---
// src/components/OptimizedImages.astro
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
import profileImage from '../assets/profile.png';
---

<div class="image-gallery">
  <!-- Local images with optimization -->
  <Image 
    src={heroImage}
    alt="Hero image"
    width={800}
    height={600}
    loading="eager"
    class="hero-image"
  />

  <!-- Responsive images -->
  <Image 
    src={profileImage}
    alt="Profile"
    widths={[240, 540, 720]}
    sizes="(max-width: 360px) 240px, (max-width: 720px) 540px, 720px"
    class="profile-image"
  />

  <!-- Remote images -->
  <Image 
    src="https://example.com/image.jpg"
    alt="Remote image"
    width={400}
    height={300}
    loading="lazy"
  />

  <!-- Background images -->
  <div class="hero-section">
    <Image 
      src={heroImage}
      alt=""
      width={1200}
      height={600}
      class="background-image"
    />
    <div class="hero-content">
      <h1>Hero Title</h1>
      <p>Hero description</p>
    </div>
  </div>
</div>

<style>
  .image-gallery {
    display: grid;
    gap: 2rem;
    padding: 2rem;
  }

  .hero-image {
    width: 100%;
    height: auto;
    border-radius: 8px;
  }

  .profile-image {
    width: 200px;
    height: 200px;
    border-radius: 50%;
    object-fit: cover;
  }

  .hero-section {
    position: relative;
    height: 400px;
    border-radius: 8px;
    overflow: hidden;
  }

  .background-image {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 1;
  }

  .hero-content {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 100%;
    color: white;
    text-align: center;
    background: rgba(0, 0, 0, 0.4);
  }
</style>
```### Procesamiento de Imágenes
```typescript
// astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  image: {
    service: {
      entrypoint: 'astro/assets/services/sharp'
    }
  },

  vite: {
    optimizeDeps: {
      include: ['sharp']
    }
  }
});
```## Despliegue

### Despliegue Estático
```bash
# Build static site
npm run build

# Output will be in dist/ directory
# Deploy to any static hosting service
```### Despliegue en Vercel
```json
// vercel.json
{
  "framework": "astro",
  "buildCommand": "npm run build",
  "devCommand": "npm run dev",
  "installCommand": "npm install"
}
```### Despliegue en Netlify
```toml
# netlify.toml
[build]
  command = "npm run build"
  publish = "dist"

[build.environment]
  NODE_VERSION = "18"

[[redirects]]
  from = "/*"
  to = "/404.html"
  status = 404
```### Despliegue con Docker
```dockerfile
# Dockerfile
FROM node:18-alpine AS build

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```### Renderizado del Lado del Servidor
```typescript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'server',
  adapter: node({
    mode: 'standalone'
  })
});
```## Rendimiento

### Análisis de Paquetes
```bash
# Analyze bundle size
npm run build -- --analyze

# Check lighthouse scores
npx lighthouse http://localhost:3000
```### Técnicas de Optimización
```astro
---
// Performance optimizations
import { Image } from 'astro:assets';
---

<!-- Preload critical resources -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />

<!-- Optimize images -->
<Image 
  src="/hero.jpg"
  alt="Hero"
  width={800}
  height={600}
  loading="eager"
  decoding="async"
/>

<!-- Lazy load non-critical components -->
<HeavyComponent client:visible />

<!-- Minimize JavaScript -->
<script>
  // Only essential JavaScript
  console.log('Page loaded');
</script>
```## Pruebas

### Pruebas Unitarias
```bash
# Install testing dependencies
npm install --save-dev vitest @astrojs/test-utils
// tests/components/Card.test.ts
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import { expect, test } from 'vitest';
import Card from '../src/components/Card.astro';

test('Card component renders correctly', async () => {
  const container = await AstroContainer.create();
  const result = await container.renderToString(Card, {
    props: {
      title: 'Test Card',
      description: 'Test description'
    }
  });

  expect(result).toContain('Test Card');
  expect(result).toContain('Test description');
});
```### Pruebas E2E
```bash
# Install Playwright
npm install --save-dev @playwright/test
// tests/e2e/homepage.spec.ts
import { test, expect } from '@playwright/test';

test('homepage loads correctly', async ({ page }) => {
  await page.goto('/');

  await expect(page).toHaveTitle(/My Astro Site/);
  await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
});

test('navigation works', async ({ page }) => {
  await page.goto('/');

  await page.getByRole('link', { name: 'About' }).click();
  await expect(page).toHaveURL('/about');
});
```## Resolución de Problemas

### Problemas Comunes

#### Errores de Compilación
```bash
# Clear cache and rebuild
rm -rf node_modules/.astro dist
npm install
npm run build

# Check for TypeScript errors
npx astro check
```#### Problemas de Hidratación
```astro
<!-- Fix hydration mismatches -->
<div class="client-only">
  <ClientComponent client:only="react" />
</div>

<!-- Use proper client directives -->
<InteractiveComponent client:load />
<LazyComponent client:visible />
```#### Problemas de Rendimiento
```astro
---
// Optimize component loading
---

<!-- Use appropriate client directives -->
<HeavyComponent client:idle />

<!-- Optimize images -->
<Image src="/large-image.jpg" width={800} height={600} loading="lazy" />

<!-- Minimize JavaScript -->
<script is:inline>
  // Inline only critical scripts
</script>
```## Mejores Prácticas

### Organización del Proyecto
```bash
# Recommended structure
src/
├── components/
   ├── ui/           # Reusable UI components
   ├── layout/       # Layout components
   └── content/      # Content-specific components
├── layouts/          # Page layouts
├── pages/            # Routes
├── content/          # Content collections
├── assets/           # Images, fonts, etc.
├── styles/           # Global styles
└── utils/            # Utility functions
```### Mejores Prácticas de Rendimiento
```astro
---
// Optimize for performance
import { Image } from 'astro:assets';
---

<!-- Use appropriate loading strategies -->
<Image src="/hero.jpg" alt="Hero" loading="eager" />
<Image src="/gallery.jpg" alt="Gallery" loading="lazy" />

<!-- Minimize client-side JavaScript -->
<InteractiveComponent client:visible />

<!-- Preload critical resources -->
<link rel="preload" href="/critical.css" as="style" />
```### Mejores Prácticas de SEO
```astro
---
// SEO optimization
const { title, description, image } = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
---

<head>
  <title>{title}</title>
  <meta name="description" content={description} />
  <link rel="canonical" href={canonicalURL} />

  <!-- Open Graph -->
  <meta property="og:title" content={title} />
  <meta property="og:description" content={description} />
  <meta property="og:image" content={image} />

  <!-- Structured data -->
  <script type="application/ld+json">
    {JSON.stringify({
      "@context": "https://schema.org",
      "@type": "WebPage",
      "name": title,
      "description": description
    })}
  </script>
</head>

Resumen

Astro es un potente generador de sitios estáticos que sobresale en la construcción de sitios web orientados a contenido con un rendimiento excepcional. Sus características principales incluyen:

  • Arquitectura de Islas: Enviar JavaScript mínimo con hidratación selectiva
  • Independiente de Framework: Usar React, Vue, Svelte o cualquier framework
  • Colecciones de Contenido: Gestión de contenido con seguridad de tipos y validación
  • Enrutamiento Basado en Archivos: Enrutamiento intuitivo basado en la estructura de archivos
  • Optimización de Imágenes: Procesamiento y optimización de imágenes integrados
  • Enfoque en Rendimiento: Cero JavaScript por defecto con interactividad opcional

Para obtener los mejores resultados, aproveche la generación estática para páginas de contenido, use directivas de cliente con prudencia para interactividad, optimice imágenes con las herramientas integradas de Astro y siga los patrones de colecciones de contenido para una gestión de contenido escalable.

Note: For sections 1, 5-19 where no specific text was provided, I've left them as placeholders or translated the section headers. If you have specific text for those sections, please provide them and I'll translate accordingly.