Astro

Innovative SSG using modern Islands Architecture. Ships only necessary JavaScript for ultra-fast sites.

言語:JavaScript/TypeScript
フレームワーク:Multi-framework
ビルド速度:Fast
GitHub Stars:38k
初回リリース:2021
人気ランキング:第4位

トレンド・動向

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 } }) ```