Overview

Astro is a content-first framework that ships zero JavaScript by default and lets you opt into client-side islands per component. This page covers when to reach for it, the file conventions worth following, and the deployment defaults.

Use Astro when

Reach for Astro when the site is content-heavy and mostly static.

  • Marketing sites and landing pages.
  • Blogs and documentation sites.
  • Brochureware with occasional interactive widgets.
  • Static-first apps that need a handful of client islands (a search box, a theme toggle, a graph).

Avoid Astro when

Pick a different framework when the workload is interactive end-to-end.

  • A real app with cross-page client state (use Next.js, Remix, or SvelteKit).
  • Realtime dashboards where most pages run live data fetches.
  • Anything that needs server-side rendering of authenticated user views as the default. Astro supports SSR, but Next.js has the better story.

File structure conventions

Follow Astro’s defaults; do not relocate src/pages/ or src/content/.

src/
  pages/          # File-based routes. .astro, .md, .mdx.
  layouts/        # Page wrappers; one BaseLayout.astro is usually enough.
  components/     # .astro components and framework islands.
  content/        # Content collections (managed by Astro).
  styles/         # Global CSS, design tokens.
public/           # Static assets served at /. Favicons, robots.txt, llms.txt.
astro.config.mjs

Keep one BaseLayout.astro. Put <head> metadata, the canonical link, Open Graph tags, and JSON-LD inside it. Pass per-page values in as props.

Content collections with Zod schemas

Use a Zod schema for every collection. The schema runs at build time and fails the build on missing or malformed frontmatter.

// src/content.config.ts
import { defineCollection, z } from "astro:content";
 
const posts = defineCollection({
  type: "content",
  schema: z.object({
    title: z.string(),
    slug: z.string(),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    summary: z.string(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});
 
export const collections = { posts };

Query with getCollection("posts") in .astro files. Filter drafts there, not in templates.

Image optimization

Use <Image /> from astro:assets for any image inside src/. It generates responsive sources, sets explicit dimensions, and prevents layout shift.

---
import { Image } from "astro:assets";
import hero from "../assets/hero.jpg";
---
<Image src={hero} alt="Description" widths={[400, 800, 1200]} sizes="(max-width: 720px) 100vw, 800px" />

For images in public/, Astro does not transform them. Put icons, OG images, and pre-sized assets there; put source photos in src/.

Partial hydration directives

Choose the directive by when the user needs the interactivity, not by what feels safe.

  • client:load: hydrate on page load. Use for above-the-fold interactivity the user touches immediately (theme toggle, sticky nav).
  • client:idle: hydrate during browser idle time. Use for non-critical widgets the user may touch later (a footer newsletter form).
  • client:visible: hydrate when the component scrolls into view. Use for below-the-fold widgets (a comments section, an embedded chart). This is the right default for most islands.
  • client:only="framework": skip server rendering entirely; only render on the client. Use when the component cannot render on the server (a chart library that reads window, a draggable canvas).

Do not use client:load as a blanket default. Each hydrated component ships a framework runtime to the user; client:visible and client:idle defer that cost.

Integrations to default-install

For most sites, install these on day one:

  • @astrojs/tailwind for Tailwind v4 (or use Tailwind v4’s first-party Vite plugin).
  • @astrojs/sitemap to generate /sitemap.xml.
  • @astrojs/mdx if any content needs JSX inside markdown.

Add @astrojs/check to the dev dependencies and run it in CI. It type-checks .astro files.

SEO baseline

Set these in BaseLayout.astro. Every page gets them for free.

  • Canonical link: <link rel="canonical" href={canonicalURL} />.
  • Title and meta description, driven by props.
  • Open Graph tags: og:title, og:description, og:url, og:image, og:type.
  • Twitter Card tags: twitter:card, twitter:title, twitter:description, twitter:image.
  • JSON-LD structured data: Article for blog posts, WebSite and Organization on the homepage.

See technical and structured-data for the wider story.

Deployment

  • GitHub Pages: build with astro build, deploy dist/ via actions/deploy-pages@v4. Set site and base in astro.config.mjs if hosting under a project path.
  • Vercel: zero config. Astro detects automatically. Set the project’s framework preset to Astro and the output directory to dist.

For static-only sites, prefer GitHub Pages or Cloudflare Pages for cost. Move to Vercel when you need SSR, edge functions, or preview deployments per PR.