Astro
Innovative SSG using modern Islands Architecture. Ships only necessary JavaScript for ultra-fast sites.
トレンド・動向
Rapidly gaining attention as next-generation SSG. Achieves superior performance over traditional SSGs with partial hydration.
# Static Site Generator
Astro
## Overview
Astro is an innovative static site generator that pioneered the modern "Islands Architecture" pattern, revolutionizing how we build web applications. Developed specifically for content-focused websites, Astro delivers exceptional performance by shipping zero JavaScript by default while enabling selective hydration only where interactivity is needed. With support for multiple UI frameworks (React, Vue, Svelte, etc.) within a single project and 38k+ GitHub stars, Astro represents the next generation of static site generators, focusing on delivering ultra-fast websites through compile-time optimization and intelligent partial hydration.
## Details
Astro 2025 continues to lead the static site generation space with its groundbreaking Islands Architecture implementation and performance-first approach. The framework's core philosophy of "fast by default" is achieved through aggressive static HTML generation, where only explicitly marked interactive components receive JavaScript. Astro's multi-framework support allows developers to use React, Vue, Svelte, Preact, and other frameworks simultaneously within the same project, making it ideal for teams transitioning between technologies or leveraging the best aspects of each framework. The latest releases include Server Islands for deferred rendering, enhanced content collections with TypeScript safety, and advanced view transitions using browser-native APIs.
### Key Features
- **Islands Architecture**: Selective hydration for optimal performance
- **Zero JS by default**: Ships minimal JavaScript for maximum speed
- **Multi-framework support**: Use React, Vue, Svelte, and more together
- **Content Collections**: Type-safe content management with frontmatter validation
- **View Transitions**: Smooth page transitions with browser-native APIs
- **SSG + SSR hybrid**: Flexible rendering strategies per page
## Pros and Cons
### Pros
- Industry-leading performance with minimal JavaScript payload (90% less than typical React apps)
- Revolutionary Islands Architecture allows precise control over hydration
- Multi-framework flexibility enables gradual migration and best-of-breed component usage
- Excellent developer experience with zero configuration and intuitive content management
- Strong TypeScript integration with automatic type generation for content
- Future-proof architecture leveraging web standards and browser-native features
### Cons
- Relatively new ecosystem compared to established generators like Gatsby or Next.js
- Learning curve for developers unfamiliar with Islands Architecture concepts
- Limited server-side rendering capabilities compared to full-stack frameworks
- Smaller community and fewer third-party plugins than mature alternatives
- Component hydration strategies require careful planning for complex interactive applications
- Documentation and best practices still evolving for advanced use cases
## Reference Pages
- [Astro Official Website](https://astro.build/)
- [Astro Documentation](https://docs.astro.build/)
- [Astro GitHub Repository](https://github.com/withastro/astro)
## Usage Examples
### Project Setup and Basic Configuration
```bash
# Create new Astro project
npm create astro@latest my-astro-site
cd my-astro-site
# Choose template (blog, portfolio, minimal, etc.)
# Install with recommended settings
npm install
# Start development server
npm run dev
# Alternative with specific template
npm create astro@latest -- --template blog
# Install additional UI framework integrations
npx astro add react vue svelte
npx astro add tailwind
```
### Page Creation and Routing
```astro
---
// src/pages/index.astro
// Frontmatter (server-side code)
const title = "Welcome to Astro"
const posts = await Astro.glob('../content/posts/*.md')
---
<!-- Component Template (HTML-like syntax) -->
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
</head>
<body>
<main>
<h1>{title}</h1>
<p>Build faster websites with Astro's modern architecture.</p>
<!-- Blog posts list -->
<section>
<h2>Recent Posts</h2>
{posts.slice(0, 3).map((post) => (
<article>
<h3><a href={post.url}>{post.frontmatter.title}</a></h3>
<p>{post.frontmatter.description}</p>
<time>{post.frontmatter.date}</time>
</article>
))}
</section>
</main>
</body>
</html>
<!-- Dynamic routing with [...slug].astro -->
---
// src/pages/blog/[...slug].astro
export async function getStaticPaths() {
const posts = await Astro.glob('../content/posts/*.md')
return posts.map((post) => ({
params: {
slug: post.frontmatter.slug
},
props: {
post
}
}))
}
const { post } = Astro.props
---
<html>
<head>
<title>{post.frontmatter.title}</title>
</head>
<body>
<article>
<h1>{post.frontmatter.title}</h1>
<post.Content />
</article>
</body>
</html>
```
### Configuration and Plugin Management
```js
// astro.config.mjs
import { defineConfig } from 'astro/config'
import react from '@astrojs/react'
import vue from '@astrojs/vue'
import tailwind from '@astrojs/tailwind'
import sitemap from '@astrojs/sitemap'
export default defineConfig({
site: 'https://mysite.com',
// Multiple framework support
integrations: [
react(),
vue(),
tailwind(),
sitemap()
],
// Output configuration
output: 'static', // 'static', 'server', or 'hybrid'
adapter: undefined, // For SSR deployment
// Build configuration
build: {
assets: 'assets',
assetsPrefix: '/cdn'
},
// Development configuration
server: {
port: 3000,
host: true
},
// Markdown configuration
markdown: {
shikiConfig: {
theme: 'github-dark',
langs: ['js', 'ts', 'astro', 'jsx']
}
},
// Vite configuration passthrough
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "src/styles/global.scss";`
}
}
}
}
})
// Content collections configuration
// src/content/config.ts
import { defineCollection, z } from 'astro:content'
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.date(),
updatedDate: z.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([])
})
})
export const collections = {
blog
}
```
### Islands and Interactive Components
```astro
---
// src/pages/interactive.astro
import ReactCounter from '../components/ReactCounter.jsx'
import VueForm from '../components/VueForm.vue'
import SvelteChart from '../components/SvelteChart.svelte'
---
<html>
<head>
<title>Interactive Components</title>
</head>
<body>
<h1>Astro Islands Architecture</h1>
<!-- Static by default -->
<ReactCounter />
<!-- Hydrate immediately on page load -->
<ReactCounter client:load />
<!-- Hydrate when component becomes visible -->
<VueForm client:visible />
<!-- Hydrate when browser is idle -->
<SvelteChart client:idle />
<!-- Hydrate based on media query -->
<ReactCounter client:media="(max-width: 768px)" />
<!-- Only render on client (no SSR) -->
<ReactCounter client:only="react" />
<!-- Server-side deferred rendering (Server Islands) -->
<SvelteChart server:defer />
</body>
</html>
<!-- React component example -->
// 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>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
</div>
)
}
<!-- Vue component example -->
// src/components/VueForm.vue
<template>
<form @submit.prevent="handleSubmit" class="vue-form">
<h3>Vue Contact Form</h3>
<div>
<label>Name:</label>
<input v-model="form.name" type="text" required />
</div>
<div>
<label>Email:</label>
<input v-model="form.email" type="email" required />
</div>
<div>
<label>Message:</label>
<textarea v-model="form.message" required></textarea>
</div>
<button type="submit">Send Message</button>
<p v-if="submitted" class="success">Thank you for your message!</p>
</form>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const form = ref({
name: '',
email: '',
message: ''
})
const submitted = ref(false)
const handleSubmit = () => {
// Process form submission
console.log('Form submitted:', form.value)
submitted.value = true
// Reset form after delay
setTimeout(() => {
form.value = { name: '', email: '', message: '' }
submitted.value = false
}, 3000)
}
return {
form,
submitted,
handleSubmit
}
}
}
</script>
```
### Content Collections and Type Safety
```typescript
// Using content collections
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content'
import Layout from '../../layouts/Layout.astro'
const allBlogPosts = await getCollection('blog')
const sortedPosts = allBlogPosts.sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
)
---
<Layout title="Blog">
<main>
<h1>Blog Posts</h1>
<section class="posts-grid">
{sortedPosts.map((post) => (
<article class="post-card">
<h2>
<a href={`/blog/${post.slug}/`}>
{post.data.title}
</a>
</h2>
<p>{post.data.description}</p>
<time>{post.data.pubDate.toLocaleDateString()}</time>
<div class="tags">
{post.data.tags.map((tag) => (
<span class="tag">{tag}</span>
))}
</div>
</article>
))}
</section>
</main>
</Layout>
<!-- Individual blog post -->
---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content'
import Layout from '../../layouts/Layout.astro'
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()
---
<Layout title={entry.data.title}>
<article>
<header>
<h1>{entry.data.title}</h1>
<p>{entry.data.description}</p>
<time>{entry.data.pubDate.toLocaleDateString()}</time>
</header>
{entry.data.heroImage && (
<img src={entry.data.heroImage} alt={entry.data.title} />
)}
<Content />
<footer>
<div class="tags">
{entry.data.tags.map((tag) => (
<a href={`/tags/${tag}/`} class="tag">{tag}</a>
))}
</div>
</footer>
</article>
</Layout>
```
### Deployment and Optimization
```bash
# Build for production
npm run build
# Preview production build
npm run preview
# Build with specific adapter for deployment
# Vercel
npx astro add vercel
npm run build
# Netlify
npx astro add netlify
npm run build
# Cloudflare Pages
npx astro add cloudflare
npm run build
# Node.js server
npx astro add node
npm run build
# Static hosting (GitHub Pages, etc.)
npm run build
# Deploy dist/ folder
# Docker deployment
# Dockerfile
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM base AS build
COPY . .
RUN npm run build
FROM nginx:alpine AS runtime
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# Performance optimization
# Analyze bundle size
npm run build -- --analyze
# Environment-specific builds
NODE_ENV=production npm run build
# Prerender configuration for SSR
// astro.config.mjs
export default defineConfig({
output: 'hybrid',
adapter: vercel(),
experimental: {
prerender: true
}
})
```