---
title: "Astro: Best Practices"
slug: "astro"
category: "frontend"
tags: ["astro", "ssg", "frontend"]
status: "stable"
last_updated: 2026-08-14
summary: "When to pick Astro 6, content collections, hydration directives, integrations to install, and deployment defaults."
related: ["[[frontend/tailwind]]", "[[frontend/react]]", "[[seo/technical]]", "[[ops/vercel]]", "[[ops/github-pages]]"]
---

## 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. For the head-to-head selection rules, see [[comparisons/nextjs-vs-astro]].

## 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. Define collections with a Content Layer `loader` (`glob()` for a folder, `file()` for a single data file); the legacy `type: "content"` property was removed in Astro 6. The schema runs at build time and fails the build on missing or malformed frontmatter. See [[frontend/astro-content-collections]] for the full query and reference patterns.

```ts
// src/content.config.ts
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";

const posts = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/content/posts" }),
  schema: z.object({
    title: 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.

```astro
---
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/`. See [[seo/image-seo]] for the alt-text, filename, and sitemap rules.

## Partial hydration directives

Choose the directive by when the user needs the interactivity, not by what feels safe. [[glossary/hydration|Hydration]] attaches client-side event handlers to server-rendered HTML.

- `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:

- `@tailwindcss/vite` for Tailwind v4, added under `vite.plugins` with a `@import "tailwindcss";` global stylesheet. `npx astro add tailwind` wires this up. The old `@astrojs/tailwind` integration is deprecated and supports only Tailwind v3. See [[frontend/tailwind]].
- `@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 [[seo/technical]] and [[seo/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.

## Related

- [[frontend/tailwind]]
- [[frontend/react]]
- [[seo/technical]]
- [[ops/vercel]]
- [[ops/github-pages]]
