--- id: frontend-astro-patterns title: Astro — Islands / Static-first / Multi-framework category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [frontend, astro, ssg, vibe-coding] tech_stack: { language: "TS / Astro", applicable_to: ["Frontend"] } applied_in: [] aliases: [Astro, islands architecture, static-first, content-driven, multi-framework] --- # Astro > Static-first + 작은 JS. **Islands architecture**. React / Vue / Svelte / Solid 동시 사용 가능. Content-heavy site (blog, marketing) 의 sweet spot. ## 📖 핵심 개념 - Static (default): 0 JS shipped. - Island: 인터랙션 component 만 hydrate. - Multi-framework: React + Vue + Svelte 한 site. - Content collection: type-safe MDX. ## 💻 코드 패턴 ### 시작 ```bash npm create astro@latest ``` ### 기본 page ```astro --- // Server-side (build time 또는 SSR) const users = await fetch('https://api.example.com/users').then(r => r.json()); --- Users

Users

``` → Static HTML 가 generate. 0 JS. ### Island (인터랙션) ```astro --- import { Counter } from '../components/Counter.tsx'; ---

Static heading

More static

``` ```tsx // components/Counter.tsx (React) import { useState } from 'react'; export function Counter() { const [count, setCount] = useState(0); return ; } ``` ### client:* directives ```astro ``` → 작은 island 만 JS load. ### Multi-framework ```astro --- import ReactCounter from './ReactCounter.tsx'; import VueCounter from './VueCounter.vue'; import SvelteCounter from './SvelteCounter.svelte'; --- ``` → 같은 page 안 다른 framework. Migration 또는 team별. ### Content collections (type-safe MDX) ```ts // src/content/config.ts import { defineCollection, z } from 'astro:content'; const blog = defineCollection({ schema: z.object({ title: z.string(), date: z.date(), tags: z.array(z.string()), draft: z.boolean().default(false), }), }); export const collections = { blog }; ``` ```mdx --- title: My First Post date: 2026-05-09 tags: [intro] --- # Hello World This is my first **blog post**. import Counter from '../components/Counter.tsx'; ``` ```astro --- import { getCollection } from 'astro:content'; const posts = await getCollection('blog', ({ data }) => !data.draft); posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime()); --- ``` → Type-safe content + frontmatter. ### Dynamic route ```astro --- // src/pages/blog/[slug].astro import { getCollection, getEntry } from 'astro:content'; export async function getStaticPaths() { const posts = await getCollection('blog'); return posts.map(post => ({ params: { slug: post.slug }, props: { post }, })); } const { post } = Astro.props; const { Content } = await post.render(); ---

{post.data.title}

``` → Static generation 모든 post. ### SSR mode ```ts // astro.config.mjs import { defineConfig } from 'astro/config'; import vercel from '@astrojs/vercel/serverless'; export default defineConfig({ output: 'server', // 또는 'hybrid' adapter: vercel(), }); ``` → Static (default) 또는 SSR per route. ### API routes ```ts // src/pages/api/users.ts import type { APIRoute } from 'astro'; export const GET: APIRoute = async ({ request }) => { const users = await db.user.findMany(); return new Response(JSON.stringify(users), { headers: { 'Content-Type': 'application/json' }, }); }; export const POST: APIRoute = async ({ request }) => { const data = await request.json(); // ... }; ``` ### View Transitions (built-in) ```astro --- import { ViewTransitions } from 'astro:transitions'; --- ``` ```astro About ``` ### Image optimization ```astro --- import { Image } from 'astro:assets'; import heroImg from '../assets/hero.jpg'; --- Hero ``` → Build 시 다양 size + format 자동 generate. ### Tailwind / styling ```bash npx astro add tailwind ``` ```astro
...
``` ### Markdown / MDX rendering ```mdx --- title: ... --- # Heading import Chart from '../components/Chart.tsx'; Code: ```ts function hello() { return 'world'; } ``` ``` → Content + interactive component. ### Pagination ```ts // src/pages/blog/[page].astro export async function getStaticPaths({ paginate }) { const posts = await getCollection('blog'); return paginate(posts, { pageSize: 10 }); } const { page } = Astro.props; ``` ```astro
    {page.data.map(post =>
  • ...
  • )}
{page.url.prev && Prev} {page.url.next && Next} ``` ### RSS feed ```ts // src/pages/rss.xml.ts import rss from '@astrojs/rss'; import { getCollection } from 'astro:content'; export async function GET(context) { const posts = await getCollection('blog'); return rss({ title: 'My Blog', description: '...', site: context.site, items: posts.map(post => ({ title: post.data.title, pubDate: post.data.date, link: `/blog/${post.slug}`, })), }); } ``` ### Performance ``` Static page (no island): 0 JS. Marketing site: 95+ Lighthouse. Blog: 100/100 가능. → 작은 JS = 빠른 load. ``` ### vs Next.js ``` Astro: + Static-first + 0 JS default + Multi-framework + Content-driven - Less interactive (heavy SPA 어려움) Next: + App Router (RSC) + 큰 ecosystem + Vercel optimization - More JS (SPA-friendly) - Single framework (React) ``` → Marketing / blog / docs = Astro. App = Next. ### vs SvelteKit / Nuxt ``` Astro: framework-agnostic, content-first. SvelteKit: Svelte SPA + SSR. Nuxt: Vue + meta-framework. ``` ### Use cases ``` ✅ Blog / personal site ✅ Marketing site ✅ Documentation ✅ Landing page ✅ E-commerce (catalog) ✅ Portfolio ⚠️ Heavy interactive app (SPA 가 낫음) ``` ### Deploy ``` - Vercel / Netlify (Static + SSR) - Cloudflare Pages - GitHub Pages (static only) - 자체 server (Node) ``` ### CMS 통합 ``` - Sanity / Contentful / Strapi - Markdoc - Decap CMS (git-based) - Astro DB (built-in) ``` ```ts // Sanity import { sanityClient } from 'sanity:client'; const posts = await sanityClient.fetch(`*[_type == "post"]`); ``` ### Astro DB ```ts // db/config.ts import { defineDb, defineTable, column } from 'astro:db'; const Comment = defineTable({ columns: { id: column.number({ primaryKey: true }), body: column.text(), postSlug: column.text(), createdAt: column.date({ default: NOW }), }, }); export default defineDb({ tables: { Comment } }); ``` → libSQL 기반. 빠른 시작. ### i18n ```ts // astro.config.mjs i18n: { defaultLocale: 'en', locales: ['en', 'ko', 'ja'], routing: { prefixDefaultLocale: false }, }, ``` ``` src/pages/ ├── index.astro ├── ko/index.astro └── ja/index.astro ``` ### Streaming SSR ``` Astro 4+ 가 streaming. Suspense-like — 일부 부분 점진 send. ``` ### Test ```bash yarn add -D vitest @vitest/ui yarn vitest ``` ```ts import { describe, it, expect } from 'vitest'; import { experimental_AstroContainer } from 'astro/container'; import Card from './Card.astro'; it('renders title', async () => { const container = await experimental_AstroContainer.create(); const result = await container.renderToString(Card, { props: { title: 'Hello' } }); expect(result).toContain('Hello'); }); ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | Blog / docs / marketing | Astro | | Content-first | Astro + content collection | | 일부 interactive | Astro + island | | Heavy SPA | Next / Tanstack Start | | Multi-framework migration | Astro | | Static export only | Astro / Hugo / 11ty | ## ❌ 안티패턴 - **모든 게 client:load**: JS bundle 폭발. - **Big SPA in Astro**: 잘못 선택. Next / Remix. - **content schema 무**: type 안전 X. - **Image plain ``**: optimization 없음. Use ``. - **Build 매 변경 (큰 site)**: incremental build 필요. - **SSR 모든 page**: 정적 generation 가 더 빠름. ## 🤖 LLM 활용 힌트 - Static + island = 빠른 site. - Content collection 으로 type-safe. - View Transitions built-in. - Multi-framework 가 migration 친화. ## 🔗 관련 문서 - [[Frontend_Progressive_Enhancement]] - [[Frontend_View_Transitions_Deep]] - [[React_Server_Components]]