Astro Cheatsheet
Astro - Das Web-Framework für Content-Driven Websites
Astro ist ein moderner statischer Site-Generator, der Blitz-schnelle Leistung mit einem modernen Entwicklererlebnis liefert. Erstellen Sie Inhalte-Websites wie Blogs, Marketing und E-Commerce mit Ihren Lieblings-UI-Komponenten und setzen Sie überall. < p>
Inhaltsverzeichnis
- [Installation](LINK_0 -%20(__LINK_0___)
- [Projektstruktur](LINK_0 -%20Komponenten
- [Zahlen und Routing](_LINK_0__ -%20(__LINK_0___)
- (Styling)(_LINK_0__)
- Content Collections
- [Integration](_LINK_0__ -%20[Islands%20Architektur](LINK_0 -%20API%20Routen
- [Bildoptimierung](LINK_0__ -%20[Bestellung](LINK_0 -%20[Performance](LINK_0 -%20Test
- (__LINK_0___)
- Beste Praktiken
Installation
Voraussetzungen
# 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
```_
### Astro Project erstellen
```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
```_
### Manuelle Installation
```bash
# 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
```_
### Paket.json Scripts
```json
{
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
}
}
```_
## Projekterstellung
### Verfügbare Vorlagen
```bash
# 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
```_
### Entwicklungskommandos
```bash
# 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
```_
### Befehle erstellen
```bash
# 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
```_
## Projektstruktur
### Grundstruktur
```bash
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
```_
### Erweiterte Struktur
```bash
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
```_
### Konfiguration
```javascript
// 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'
}
});
```_
## Komponenten
### Astro-Komponenten
```astro
---
// 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>
```_
### Komponenten mit Ablagefächern
```astro
---
// 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>
```_
### Rahmenkomponenten
```astro
---
// 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>
```_
```jsx
// 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>
);
}
```_
### Komponentenskripte
```astro
---
// 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>
```_
## Seiten und Routing
### Dateibasiertes Routing
```bash
# 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
```_
### Seite
```astro
---
// 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>
```_
### Dynamische Seiten
```astro
---
// 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>
```_
### Alle Routen
```astro
---
// 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
### Basis-Layout
```astro
---
// 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>
```_
### Blog zum Thema
```astro
---
// 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>
```_
## Styling
### Global Styles
```css
/* 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);
}
}
```_
### Komponententypen
```astro
---
// 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>
```_
### Tailwind CSS Integration
```bash
# Install Tailwind CSS
npx astro add tailwind
```_
```astro
---
// 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>
```_
## Inhalt Sammlungen
### Konfiguration
```typescript
// 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
};
```_
### Inhaltsverzeichnis
```markdown
---
# 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:
```bash
npm erstellen astro@latest
cd my-Projekt
npm install
npm run dev
Your First Component
Here's a simple Astro component:
--
const grüße = "Hallo, Astro!";
--
<div class="greeing">
<h1>
<p>Welcome to the future of web development.</p>
</div>
<= <= <============================================================================================================================================================================================================================================
.greeing {
Text-Ausrichtung: Mitte;
Polsterung: 2rem;
}
</style>
Conclusion
Astro provides an excellent developer experience while delivering exceptional performance. Its unique approach to JavaScript hydration makes it perfect for content-focused websites.
### Verwendung von Sammlungen
```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>
```_
## Integration
### React Integration
```bash
# Add React integration
npx astro add react
```_
```jsx
// 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>
);
}
```_
```astro
---
// 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>
```_
### Vue Integration
```bash
# Add Vue integration
npx astro add vue
```_
```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>
```_
### MDX Integration
```bash
# Add MDX integration
npx astro add mdx
```_
```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
```javascript
Funktion Gruß(Name) {
zurück `Hello, ${name}!`;
}
And we can continue with regular markdown content...
## Architektur der Inseln
### Kundenrichtlinien
```astro
---
// 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>
```_
### Leistungsoptimierung
```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>
```_
## API Routen
### Grundlegende API Routen
```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'
}
});
};
```_
### Dynamische API Routen
```typescript
// 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' }
});
};
```_
### Form Handling
```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);
}
```_
## Bildoptimierung
### Astro Assets
```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>
```_
### Bildbearbeitung
```typescript
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
image: {
service: {
entrypoint: 'astro/assets/services/sharp'
}
},
vite: {
optimizeDeps: {
include: ['sharp']
}
}
});
```_
## Bereitstellung
### Statische Bereitstellung
```bash
# Build static site
npm run build
# Output will be in dist/ directory
# Deploy to any static hosting service
```_
### Vercel Bereitstellung
```json
// vercel.json
{
"framework": "astro",
"buildCommand": "npm run build",
"devCommand": "npm run dev",
"installCommand": "npm install"
}
```_
### Netlify Deployment
```toml
# netlify.toml
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "18"
[[redirects]]
from = "/*"
to = "/404.html"
status = 404
```_
### Einsatz von 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;"]
```_
### Server-Side Rendering
```typescript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone'
})
});
```_
## Leistung
### Bundanalyse
```bash
# Analyze bundle size
npm run build -- --analyze
# Check lighthouse scores
npx lighthouse http://localhost:3000
```_
### Optimierungstechnik
```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>
```_
## Prüfung
### Einheitsprüfung
```bash
# Install testing dependencies
npm install --save-dev vitest @astrojs/test-utils
```_
```typescript
// 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');
});
```_
### E2E Prüfung
```bash
# Install Playwright
npm install --save-dev @playwright/test
```_
```typescript
// 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');
});
```_
## Fehlerbehebung
### Gemeinsame Themen
#### Fehler erstellen
```bash
# Clear cache and rebuild
rm -rf node_modules/.astro dist
npm install
npm run build
# Check for TypeScript errors
npx astro check
```_
#### Hydratationsprobleme
```astro
<!-- Fix hydration mismatches -->
<div class="client-only">
<ClientComponent client:only="react" />
</div>
<!-- Use proper client directives -->
<InteractiveComponent client:load />
<LazyComponent client:visible />
```_
#### Leistungsfragen
```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>
```_
## Best Practices
### Projektorganisation
```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
Performance Best Practices
```astro
// Optimize for performance import { Image } from 'astro:assets';
```_
SEO Best Practices
```astro
// SEO optimization const { title, description, image } = Astro.props; const canonicalURL = new URL(Astro.url.pathname, Astro.site);
```_
--
Zusammenfassung
Astro ist ein leistungsstarker statischer Website-Generator, der sich beim Aufbau von Content-getriebenen Websites mit außergewöhnlicher Leistung auszeichnet. Zu den wichtigsten Merkmalen gehören:
- *Islands Architektur: Schiff minimal JavaScript mit selektiver Hydratation
- *Framework Agnostic: Verwenden Sie React, Vue, Svelte oder irgendein Framework
- *Content Collections: Typsicheres Content Management mit Validierung
- Dateibasiertes Routing: Intuitive Routing basierend auf Dateistruktur
- *Bildoptimierung: Eingebaute Bildverarbeitung und Optimierung
- *Performance First: Zero JavaScript standardmäßig mit Opt-in-Interaktivität
Für optimale Ergebnisse nutzen Sie die statische Erzeugung für Inhaltsseiten, nutzen Sie die Clientrichtlinien für Interaktivität, optimieren Sie Bilder mit Astros integrierten Tools und folgen Sie den Inhaltserfassungsmustern für skalierbares Content Management.
<= <= <= <================================================================================= Funktion copyToClipboard() {\cHFFFF} const commands = document.querySelectorAll('code'); alle Befehle = ''; Befehle. Für jede(cmd) => alle Befehle += cmd.textContent + '\n'); navigator.clipboard.writeText (allCommands); Alarm ('Alle Befehle, die in die Zwischenablage kopiert werden!'); }
Funktion generierenPDF() { Fenster.print(); }