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 textFor 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>© 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¶
---
// 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¶
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>