본문으로 건너뛰기 [2026] Astro 4 Complete Guide — Build Performance, Content Collections v2, View Transitions

[2026] Astro 4 Complete Guide — Build Performance, Content Collections v2, View Transitions

[2026] Astro 4 Complete Guide — Build Performance, Content Collections v2, View Transitions

이 글의 핵심

Astro 4 keeps a static-first architecture while boosting real-world productivity and perceived performance with View Transitions, the Content Layer (collections v2), the Dev Toolbar, and more. This post walks through build pipeline optimization, type-safe content, page transition animations, dark mode, and deployment settings on major hosting platforms in one flow.

Key points

Astro 4 is a framework for shipping content-centric websites quickly. It defaults to Island Architecture to minimize JavaScript. This guide ties together build and dev performance, Content Collections v2 (content layer), View Transitions, dark mode, and deployment on Vercel, Netlify, and Cloudflare Pages from a practical engineering perspective.

Practical note: For marketing sites, technical blogs, and documentation, both perceived loading and build stability when editing content matter. Astro 4 can combine a type-safe content pipeline with page transition UX, which helps team operations.

Table of contents

  1. What changed in Astro 4
  2. Project setup and layout
  3. Inspecting islands with the Dev Toolbar
  4. Build and dev performance
  5. Content Collections v2 and content.config.ts
  6. MDX and interactive content
  7. View Transitions in practice
  8. Dark mode (classes and transitions)
  9. Hybrid rendering and SSR overview
  10. Deployment: Vercel, Netlify, Cloudflare Pages
  11. SEO and structured data
  12. Checklist and troubleshooting

What changed in Astro 4

The Astro 4.x line keeps static site generation (SSG) as the default while improving productivity in these areas:

AreaDescription
Developer experienceDev Toolbar to visually inspect components, events, and island dependencies
ContentContent Layer — declaratively unify Markdown/MDX and remote sources in content.config.ts with loaders (e.g. glob)
UXView Transitions — smoother page transitions via the browser View Transitions API
InternationalizationExperimental i18n routing and routing-layer improvements (enabled per project)

These pillars (content layer, View Transitions, adapters) carry forward into Astro 5, so patterns you learn in Astro 4 map naturally to newer releases.


Project setup

Create with the CLI

npm create astro@latest

To pin Astro 4.x explicitly, after creation set "astro": "^4.16.0" (or similar) in package.json so the major version stays 4. Document that as a team standard for reproducible builds.

In the interactive prompts, pick a template (Blog, Docs, …) and TypeScript to scaffold content and pages together.

package.json scripts

{
  "scripts": {
    "dev": "astro dev",
    "build": "astro build",
    "preview": "astro preview"
  }
}

astro build produces the production bundle; astro preview serves the build locally for verification. On hosting platforms, publishing the npm run build output is the usual flow.


Dev Toolbar

The Dev Toolbar is an overlay shown while the dev server runs. Use it to quickly review island boundaries, client directives, and performance hints. It is not included in production builds, so it does not affect bundle size or SEO.

In practice it helps with:

  • Spotting unnecessary client:load: Check whether off-screen components hydrate too eagerly
  • Tracing layout shift: Investigate CLS tied to images and fonts
  • Debugging View Transitions: See which DOM should persist vs. swap during transitions

Add a single line to onboarding docs — “Lighthouse on staging, Dev Toolbar locally” — to reduce performance regressions.


Build performance

1) Output mode and adapters

  • Static (output: 'static'): HTML and assets are generated at build time by default. CDN caching works very well.
  • Server (output: 'server') or hybrid: For APIs and SSR, attach an adapter (@astrojs/node, @astrojs/vercel, @astrojs/netlify, @astrojs/cloudflare, etc.).
// astro.config.mjs 예시 — 정적 사이트
import { defineConfig } from 'astro/config';

export default defineConfig({
  output: 'static',
  vite: {
    build: {
      cssMinify: true,
    },
  },
});

2) Vite-level tuning

Astro uses Vite internally. You can adjust bundle splitting and dependency pre-bundling via vite.build, vite.ssr, vite.optimizeDeps, and related options. Heavy customization can invalidate build caches easily, so prefer minimal changes after measuring.

3) Image optimization

Use the Image component from astro:assets to optimize local images at build time. That directly improves CLS (Cumulative Layout Shift) and LCP (Largest Contentful Paint).

---
import { Image } from 'astro:assets';
import hero from '../assets/hero.jpg';
---
<Image src={hero} alt="히어로 이미지" width={1200} height={630} loading="eager" />

4) Keeping client JS small (islands)

Framework components should be wrapped only with client:* directives. Sprinkling unnecessary client:load everywhere worsens TTI (Time to Interactive).

---
import Counter from '../components/Counter.tsx';
---
<!-- 뷰포트에 들어올 때만 수화 -->
<Counter client:visible />

Content Collections v2

The recommended pattern since Astro 4 is to define collections in content.config.ts at the project root and collect files under src/content with glob or other loaders. Zod schemas validate frontmatter so typos and missing fields fail at build time.

content.config.ts example

import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const blog = defineCollection({
  loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
    level: z.enum(['초급', '중급', '고급']).optional(),
  }),
});

export const collections = { blog };

Querying from a page

---
import { getCollection } from 'astro:content';

const posts = await getCollection('blog', ({ data }) => !data.draft);
const sorted = posts.sort(
  (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);
---
<ul>
  {sorted.map((post) => (
    <li>
      <a href={`/blog/${post.id}`}>{post.data.title}</a>
    </li>
  ))}
</ul>

post.id depends on the glob loader’s base and file paths. For a Korean blog in this repo (pkglog.com), if [...slug].astro passes post.id into params.slug, list href values must follow the same rules or links will break.

For dynamic routes, call render(post) to render MDX/Markdown bodies. Use getEntry('blog', slug) when you need a single entry.

Why use content v2

  • Schema validation: Catch bad frontmatter early instead of broken builds in production
  • Loader extensibility: Room to unify local MDX and remote CMS in one pipeline
  • Type generation: Better editor autocomplete and safer data field access

MDX content

If the collection pattern allows **/*.{md,mdx}, posts can import UI components and place them alongside prose. MDX shines when combined with islands. Prefer small chunks of interactivity with client:visible or client:idle for safer delayed hydration.

---
title: '샘플 MDX'
description: 'MDX에서 컴포넌트 사용. [2026] Astro 4 Complete Guide — Build Performance, Content Collections v2, View Transitions에 대한 완전한 가이드입니다. 실전 예제와 함께 핵심 개념부터 고급 활용까지 다룹니다.'
pubDate: '2024-07-03'
---

import Chart from '../../components/Chart.tsx';

## 실시간 예제

아래 차트는 스크롤 후에만 수화됩니다.

<Chart client:visible />

To avoid bloated client bundles in MDX, wrap shared charts or code editors in one or two abstractions and reuse them across posts.


View Transitions

View Transitions keep global layout and add smooth transitions between navigations. Astro exposes this via astro:transitions.

Enable globally in the layout

---
import { ViewTransitions } from 'astro:transitions';
---
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>
<a href="/blog/hello" transition:animate="slide">글로 이동</a>

Keep sidebar and header with transition:persist

Headers, sidebars, and audio players on the same layout often should persist across navigations. Add transition:persist on the root element so the DOM snapshot stays without flicker.

<aside transition:persist="sidebar">
  <nav>...</nav>
</aside>

Scripts after transitions: astro:page-load

View Transitions swap HTML, so scripts that only listen for DOMContentLoaded may not run again. Astro provides the astro:page-load custom event as a hook after each navigation.

<script>
  document.addEventListener('astro:page-load', () => {
    // 페이지마다 초기화해야 하는 분석·짧은 UI 스크립트
  });
</script>

Scroll and state quirks

  • On long posts, scroll restoration may differ from expectations; use transition:persist to keep specific DOM when needed.
  • Avoid duplicate event listeners by scoping client components clearly with client:* and lifecycle boundaries.

Dark mode

Dark mode usually combines a class strategy (e.g. html.dark) with prefers-color-scheme. With View Transitions, an inline bootstrap script that applies preference first is common to prevent flash of unstyled content (FOUC).

Layout example: localStorage + class

---
const title = 'Astro 4 가이드';
---
<!doctype html>
<html lang="ko" class="bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{title}</title>
    <script is:inline>
      (function () {
        const k = 'theme';
        const stored = localStorage.getItem(k);
        const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
        if (stored === 'dark' || (!stored && prefersDark)) {
          document.documentElement.classList.add('dark');
        } else {
          document.documentElement.classList.remove('dark');
        }
      })();
    </script>
  </head>
  <body>
    <button
      type="button"
      id="theme-toggle"
      class="rounded border px-3 py-1 text-sm dark:border-gray-600"
    >
      테마 전환
    </button>
    <slot />
    <script>
      const btn = document.getElementById('theme-toggle');
      btn?.addEventListener('click', () => {
        const root = document.documentElement;
        const next = root.classList.toggle('dark') ? 'dark' : 'light';
        localStorage.setItem('theme', next === 'dark' ? 'dark' : 'light');
      });
    </script>
  </body>
</html>

With Tailwind CSS, pair darkMode: 'class' with this pattern. CSS variables for color tokens let a single class toggle update the whole theme consistently.

If the theme misapplies for a frame with View Transitions enabled, you can re-sync classes on document.documentElement in an astro:after-swap hook—but that adds complexity, so inline initialization to stop FOUC should come first.


Hybrid rendering

To server-render only some routes and keep the rest static, consider output: 'hybrid' (or, in later versions, the documented output: 'static' + adapter combinations). In Astro 4, teams often add SSR only on a few routes where static generation is a poor fit—auth-gated pages or data that must be fresh per request.

Hybrid and SSR complicate caching (CDN, Cache-Control, edge keys) compared with a static site. Prefer static by default and dynamism only where truly needed.


Deployment

Common: astro build output

In static mode, dist/ is the default output. Each platform points its build command at that directory.

Vercel

  • Framework preset: Astro
  • Build Command: npm run build
  • Output Directory: dist
  • For SSR, use the @astrojs/vercel adapter with output: 'server' or hybrid
// astro.config.mjs — SSR 예시 (필요 시)
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'server',
  adapter: vercel(),
});

Netlify

  • Build command: npm run build
  • Publish directory: dist
  • For SSR, use the @astrojs/netlify adapter
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify';

export default defineConfig({
  output: 'server',
  adapter: netlify(),
});

Cloudflare Pages

Static deploys still publish dist. For SSR or the edge, use @astrojs/cloudflare. This repo (pkglog.com) often assumes Cloudflare Pages; align environment variables, cache invalidation, and Node compatibility with the runtime docs.

import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';

export default defineConfig({
  output: 'server',
  adapter: cloudflare(),
});

SEO and structured data

Meta and structured data

  • title / description: Core to search snippets—make them unique per page.
  • Open Graph / Twitter cards: Affect social click-through.
  • Semantic HTML: One h1, clear article, header, nav, main.

Patterns in Astro

Centralize canonical URLs, OG images, and JSON-LD in a shared component such as BaseHead.astro, and inject summary and dates from frontmatter in posts. For this blog, designing schemas so fields like summaryTop and faq can feed rich results is a good goal.

Article JSON-LD example

---
const { title, description, pubDate, canonicalURL } = Astro.props;
const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'Article',
  headline: title,
  description,
  datePublished: pubDate?.toISOString?.() ?? pubDate,
  mainEntityOfPage: canonicalURL,
};
---
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />

Search engines consolidate duplicates with consistent date formats and canonical URLs. When migrating blog platforms, plan redirects and canonical together.


Troubleshooting

SymptomWhat to check
Content schema errors at buildZod fields in content.config.ts match Markdown frontmatter keys
Duplicate scripts after View TransitionsScope of client:only / client:load and use of transition:persist
Broken image pathsastro:assets often expects relative import paths
Cloudflare SSR build failuresNode-only packages not leaking into the edge bundle; compatibility in adapter docs

Summary

Astro 4 keeps fast first loads with minimal JavaScript, uses Content Collections v2 to enforce content quality at build time, and can lift perceived UX with View Transitions. Add dark mode, images, and island loading, and you can balance performance, operations, and search for technical blogs, docs, and marketing sites.

Deploy on Vercel, Netlify, or Cloudflare Pages with astro build as the standard step, choosing an adapter only when SSR is required. After changes, follow git push then npm run deploy, and verify Lighthouse and real route transitions in production.