Guide

Astro fundamentals explained

A documentation site with two hundred pages should not ship two megabytes of React on every route. Astro is a content-focused web framework that defaults to zero client-side JavaScript and hydrates interactive widgets only where needed through its islands architecture. Built on Vite, Astro renders .astro components to static HTML at build time, optionally mixing in React, Vue, or Svelte islands with explicit client:* directives. Content collections give typed Markdown and MDX with schema validation; output modes span fully static sites, server-rendered pages, and hybrid per-route choices. This guide covers project structure, the islands hydration model, content collections, integrations and adapters, image optimization, a Harbor Archive docs portal worked example, a framework decision table, common pitfalls, and a production checklist — complementing our rendering modes guide and Next.js guide without repeating their app-router focus.

What Astro optimizes for

Most marketing sites, blogs, and documentation portals are read-heavy: visitors scroll text, follow links, and maybe submit a search or open a lightbox. Shipping a full single-page application runtime on every page wastes bandwidth and hurts Core Web Vitals — especially Largest Contentful Paint and Total Blocking Time. Astro inverts the default: pages are server-rendered HTML strings with no hydration unless you opt in per component.

  • Content-first routing — file-based routes in src/pages/; dynamic segments via [slug].astro and rest parameters.
  • Partial hydration — only components marked with client:load, client:visible, client:idle, or client:media ship JavaScript to the browser.
  • Framework agnostic — use React for a date picker, Svelte for a chart, Vue for a modal on the same page without converting the whole site.
  • Vite under the hood — fast dev server, HMR, and Rollup production builds familiar from other modern frontends.

Astro is a strong fit when SEO, performance, and editorial velocity matter more than rich client-side state. It is a weaker default for highly interactive dashboards where most of the screen updates every few seconds — those projects often prefer SPA-style state management from the start.

Project structure and .astro components

A typical Astro 4+ project separates concerns cleanly:

  • src/pages/ — routes; index.astro becomes /, nested folders mirror URL paths.
  • src/layouts/ — shared shells (header, footer, meta tags) wrapped around page content via slots.
  • src/components/ — reusable UI; .astro for static markup, framework extensions for islands.
  • src/content/ — Markdown/MDX files managed by content collections (see below).
  • public/ — assets copied verbatim without processing (favicons, robots.txt).
  • astro.config.mjs — integrations, output mode, site URL for sitemaps, adapter selection.

Anatomy of a .astro file

Each .astro file has a component script (frontmatter between --- fences) and a template that looks like HTML with expressions. The script runs only at build time (or on the server in SSR mode); its imports and data fetching never reach the client bundle unless referenced by a hydrated island.

---
// Runs at build time only
import BaseLayout from '../layouts/BaseLayout.astro';
const title = 'Release notes';
const posts = await fetchPosts();
---
<BaseLayout title={title}>
  <h1>{title}</h1>
  <ul>
    {posts.map(p => <li><a href={p.url}>{p.title}</a></li>)}
  </ul>
</BaseLayout>

Scoped styles live in <style> tags; Astro adds unique attributes so rules do not leak globally. Use <style is:global> sparingly for third-party widget overrides.

Islands architecture and client directives

The islands model treats each interactive widget as an isolated archipelago of JavaScript on an otherwise static HTML continent. You import a React component and choose when it hydrates:

  • client:load — hydrate immediately on page load; use for above-the-fold UI that must work instantly (search bar, theme toggle).
  • client:idle — hydrate after requestIdleCallback; good for analytics widgets and secondary controls.
  • client:visible — hydrate when the component enters the viewport; ideal for comments, charts, and embeds far down the page.
  • client:media — hydrate when a CSS media query matches; useful for mobile-only menus.
  • client:only — skip server render; render only on client (browser APIs, chart libraries that break SSR).

Each directive is a budget decision. A docs site with fifty pages and one client:visible search island ships far less JS than the same site as a Next.js app where the root layout hydrates everything. Measure with Lighthouse and bundle analyzers after adding each island; it is easy to accidentally mark too many components client:load and recreate SPA weight.

Passing props to islands

Islands receive serializable props from the Astro template. Non-serializable values (functions, class instances) cannot cross the server/client boundary. Fetch data in the Astro frontmatter and pass plain objects into framework components.

Content collections

Blogs, changelogs, and API reference pages benefit from content collections — typed content directories with Zod schema validation in src/content/config.ts.

  • Schema enforcement — required frontmatter fields (title, date, draft flag) fail the build if missing, catching editorial errors before deploy.
  • getCollection() and getEntry() — query APIs replace fragile glob imports; filter drafts, sort by date, paginate.
  • MDX support — embed React/Vue components inside Markdown for callouts and live demos while keeping prose in git.
  • Generated types — TypeScript autocomplete for frontmatter keys in templates.

Dynamic routes like src/pages/guides/[slug].astro call getStaticPaths() to enumerate slugs at build time for static output, or use server mode for on-demand rendering. Pair collections with SEO fundamentals — unique titles, canonical URLs, and JSON-LD per entry are trivial when layout components read collection metadata.

Integrations, images, and output modes

Official integrations

npx astro add react tailwind sitemap wires common stacks. Integrations register Vite plugins, JSX handling, and type shims. Popular choices:

  • @astrojs/react, vue, svelte — island frameworks.
  • @astrojs/tailwind — pairs with our Tailwind guide.
  • @astrojs/mdx — MDX in content collections.
  • @astrojs/sitemap — auto-generate sitemap.xml from built routes.

Image optimization

The <Image /> component from astro:assets resizes, converts to WebP/AVIF, and sets width/height to prevent layout shift. Local images in src/assets/ are processed; remote images need explicit dimension hints or authorized domains in config.

Static, server, and hybrid output

  • output: 'static' (default) — pre-render every page at build; deploy to any static host or CDN.
  • output: 'server' — SSR on each request; requires an adapter (Node, Vercel, Netlify, Cloudflare).
  • output: 'hybrid' — static by default with export const prerender = false on routes that need SSR (authenticated previews, A/B tests).

Content and marketing sites should default static; add SSR only for routes that truly need per-request data or cookies.

Worked example: Harbor Archive docs portal

Harbor Archive maintains four hundred internal how-to articles for warehouse staff. Requirements: sub-second LCP on cheap Android scanners, full-text search, versioned API reference from OpenAPI, and a single interactive “try this request” panel on API pages. The team chose Astro over a React SPA.

  1. Content collectionsguides and api collections with Zod schemas; drafts excluded from production builds via draft: true filter.
  2. LayoutsDocsLayout.astro injects sidebar nav from collection tree, breadcrumb JSON-LD, and AdSense-safe meta tags on every page.
  3. Search island — Pagefind indexes static HTML at build time; a client:idle React search box loads the Pagefind WASM bundle only after first paint.
  4. API try-it panel — one client:visible React island per API endpoint page; prose above the fold stays zero-JS.
  5. Deployoutput: 'static' to Cloudflare Pages; preview branches per pull request; sitemap integration lists only published slugs.

Result: median LCP dropped from 3.1s (previous Gatsby SPA) to 0.9s; JavaScript transferred on article pages fell from 380 KB to 42 KB (search island only after idle). Editorial workflow stayed Markdown-in-git with CI schema checks.

Framework decision table

Your situation Favor Astro when Consider alternatives
Marketing site, blog, docs Mostly static HTML, few interactive widgets, SEO-critical Next.js if every page needs authenticated SSR
Mixed framework legacy Islands let you embed existing React/Vue components incrementally Micro-frontends if teams deploy independently
Editorial team writes Markdown Content collections + MDX with build-time validation Headless CMS + any framework if non-technical authors need WYSIWYG
Real-time dashboard Not ideal — too much client state React/Vue SPA or Next.js with heavy client components
Global CDN static hosting output: 'static' deploys anywhere cheaply Plain HTML if you do not need components or collections
Already on Vite + React SPA Migrate content routes to Astro; keep app shell as island or separate subdomain Stay on Vite if migration cost exceeds perf gains

Common pitfalls

  • Hydrating everything with client:load — defeats islands; audit each component and prefer client:visible or client:idle.
  • Fetching in island instead of Astro frontmatter — duplicates requests and exposes API keys if env vars leak into client bundles; fetch at build/SSR layer.
  • Mixing output modes without planning — hybrid SSR routes need adapters and cold-start awareness on serverless.
  • Ignoring image dimensions — skipping <Image /> width/height props hurts CLS scores.
  • Over-large MDX bundles — importing heavy chart libraries in every MDX file bloats pages; isolate charts in lazy islands.
  • Assuming Astro replaces your CMS — git-based Markdown scales to mid-size teams; larger orgs still need workflow UI or headless CMS webhooks.
  • Duplicate routing with public/ — files in public/ bypass Astro processing and can collide with src/pages paths.

Production checklist

  • Set site in astro.config.mjs for canonical URLs and sitemap generation.
  • Define content collection schemas; fail CI on validation errors.
  • Audit islands with bundle analyzer; document why each uses its client:* directive.
  • Configure @astrojs/sitemap or equivalent; exclude draft and admin routes.
  • Optimize images via astro:assets; self-host fonts per web font guide.
  • Run Lighthouse on representative content pages (not just homepage).
  • Choose output mode explicitly; add adapter only if SSR/hybrid routes exist.
  • Set security headers at CDN (CSP, HSTS); Astro does not replace edge configuration.
  • Preview deploys on every PR; compare LCP and JS bytes vs production baseline.

Key takeaways

  • Astro ships HTML-first pages with optional JavaScript islands — ideal for content-heavy sites where performance and SEO dominate.
  • .astro components run at build time; framework components hydrate only when you add explicit client:* directives.
  • Content collections provide typed, validated Markdown/MDX with query APIs for blogs, docs, and changelogs.
  • Integrations cover React, Vue, Svelte, Tailwind, MDX, and sitemaps; Vite powers dev and production builds.
  • Default to static output; reach for SSR or hybrid only on routes that genuinely need per-request logic.

Related reading