Claude Code Astro CLAUDE.md AI Coding 2026

Claude Code for Astro: CLAUDE.md Rules for Content Layer, Server Islands, and the Astro 7 Background Dev Server (2026)

The Prompt Shelf ·

Astro’s own philosophy — ship zero JavaScript by default, hydrate only what needs to be interactive — is exactly the kind of decision an AI coding agent can’t infer from the file it’s editing. A .astro component with no client:* directive and one with client:load look almost identical in a diff, but one ships a client-side React bundle and the other doesn’t. Without a CLAUDE.md that states the hydration policy explicitly, Claude Code defaults to whatever’s most common in its training data — which, for a framework whose entire pitch is “less JavaScript,” is usually the wrong default. This guide covers a full CLAUDE.md template for Astro 5+, the Content Layer’s loader API, server:defer islands, and a detail most guides miss entirely: Astro 7 now auto-detects AI coding agents and changes how its dev server behaves.

Astro also isn’t a small framework anymore. Cloudflare acquired the Astro team in January 2026 specifically to back it as infrastructure for content-driven sites, and it’s used widely enough that our own gallery has two Astro rule sets — Astro + TypeScript and Astro Framework — CLAUDE.md — neither of which is a general-purpose app template, which is itself worth knowing before you copy either one.

Why Astro Needs More Explicit Rules Than It Looks Like

The framework’s core feature is a decision it refuses to make for you. Next.js decides where your code runs (server by default, client on 'use client'). Astro flips that around: everything is server-rendered by default, and you decide what gets interactive with a hydration directive. That’s a feature for a human who’s read the docs. For an agent pattern-matching against a training set full of client-heavy React apps, it’s an invitation to add client:load to everything “to be safe” — which quietly turns a content site back into a JavaScript-heavy one, one component at a time.

Two gallery entries with the same name solve different problems. Our own Astro + TypeScript rule set is written for building an Astro app — Tailwind, conventional commits, component structure. Our Astro Framework — CLAUDE.md entry, despite the name, is actually written for contributing to Astro’s own monorepo (packages/astro, pnpm run test, integration hook internals) — not for a typical project. If you paste that one into a marketing site’s repo root, half of it won’t apply. That mismatch is common enough with community-sourced rule files that it’s worth checking which kind of “Astro CLAUDE.md” you actually copied before trusting it.

The Content Collections API changed shape in Astro 5, and a lot of published guidance still shows the old one. defineCollection() with a local Zod schema still works, but the Content Layer’s loader API — pulling collections from a CMS, a database, or a remote API, not just local Markdown — is the version Astro’s own docs now lead with. An agent trained on pre-5.0 Astro content will reach for the old pattern by default.

Complete CLAUDE.md Template for Astro Projects

This targets Astro 5.x or later on Node.js 22 LTS, with TypeScript strict mode.

# Astro Project: [ProjectName]

## Build & Run
- Dev server: `npm run dev` (Astro 7+: auto-detects AI agents and runs in the background — see "Background Dev Server" below)
- Build: `npm run build`
- Preview production build: `npm run preview`
- Type check: `npm run check` (astro check)
- Run tests: `npm test`

## Hydration Policy (do not default to client:load)
- No directive = server-rendered, zero JS shipped — this is the default for all new components unless stated otherwise
- `client:visible` — below-the-fold interactive components (default choice for most cases)
- `client:idle` — interactive but not urgent (comment widgets, secondary UI)
- `client:load` — only for above-the-fold components that must be interactive immediately (cart icon, nav toggle) — requires explicit justification in the PR/commit, not a default
- `client:media`/`client:only` — used only when documented here with a reason
- Never add a `client:*` directive to a component "to be safe" — an undirected `.astro` component with no interactivity should ship with no directive at all

## Content Collections (Content Layer API — Astro 5+)
- Collections defined in `src/content.config.ts` (not `src/content/config.ts` — that path is legacy pre-5.0)
- Use `loader:` for anything beyond local Markdown/MDX — glob loader for local files, custom loaders for CMS/API/DB sources
- All collection schemas use `zod` via the `z` export from `astro:content` — no untyped frontmatter access
- Do not hand-roll a fetch-and-cache layer for external content — that's what a custom loader is for

## Server Islands (server:defer)
- Used only for per-request personalized content on an otherwise static/cached page (auth state, cart contents, recommendations)
- A server island component NEVER reads secrets or session tokens that the surrounding static shell doesn't already scope to — it renders on-demand, but the page around it is cached and shared across users
- Provide a `<div slot="fallback">` for every server island — this is what's shown in the cached static shell before the island resolves
- Do not use `server:defer` as a substitute for `client:visible` — one runs on the server per-request, the other runs in the browser; picking the wrong one either leaks personalization into a cached page or ships unnecessary JS

## Project Structure
- `src/pages/` — file-based routes only, no business logic
- `src/components/``.astro` components (static) and framework islands (`.tsx`/`.svelte`/`.vue`), clearly separated by directory if the project uses more than one framework for islands
- `src/content/` — Markdown/MDX content matched to `src/content.config.ts` collections
- `src/layouts/` — shared page shells; `BaseLayout.astro` owns OG tags and canonical URLs
- `astro.config.mjs` — do not edit without asking; integrations and adapters are configured here and a wrong change breaks the build silently in dev, loudly in production

## Images & Assets
- Always use the `Image`/`Picture` components from `astro:assets` for content images — never a raw `<img>` tag for anything in `src/`
- Static, unprocessed assets only go in `public/`

## Testing
- Vitest for component/unit logic, `@astrojs/vitest` container API for testing `.astro` components in isolation
- Playwright for end-to-end flows that cross a server island or involve hydration behavior
- A component that only renders server-side (no `client:*`) does not need a browser test — a Vitest render assertion is enough

The Hydration Directive Decision Tree

This is the single highest-leverage rule in an Astro CLAUDE.md, because every directive compiles and every page still renders — the failure is silent performance regression, not a build error.

Does this component need to run any JavaScript in the browser at all?
├─ No → no directive. Ship it as server-rendered HTML.
└─ Yes → Is it visible without scrolling on first load?
    ├─ Yes, and it must be interactive immediately (nav toggle, cart icon)
    │   → client:load
    ├─ Yes, but interactivity can wait a beat (below a hero, above the fold)
    │   → client:idle
    └─ No, it's below the fold
        → client:visible
---
// Bad — Claude Code defaults to client:load because that's what most
// React-heavy training data does for "interactive" components
import Comments from '../components/Comments.tsx';
---
<Comments client:load client:only="react" />

<!-- Good — comments are below the fold and not needed for first paint -->
<Comments client:visible />

The gallery’s Astro + TypeScript rule set doesn’t mention this decision tree at all — it covers TypeScript strictness and commit conventions, both real, but leaves the hydration call entirely up to whatever Claude Code defaults to. That’s the gap this template exists to close.

Content Layer: Loaders Instead of Hand-Rolled Fetching

Astro 5’s Content Layer replaced the old local-only defineCollection() pattern with a loader API that treats any content source — Markdown, a headless CMS, a database, a REST API — as a typed collection.

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

// Local Markdown/MDX — glob loader
const blog = defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
    tags: z.array(z.string()).default([]),
  }),
});

// External source — custom loader (CMS/API/DB), not a manual fetch-in-component
const products = defineCollection({
  loader: async () => {
    const res = await fetch('https://api.example.com/products');
    const data = await res.json();
    return data.map((p: { id: string }) => ({ id: p.id, ...p }));
  },
  schema: z.object({
    name: z.string(),
    price: z.number(),
  }),
});

export const collections = { blog, products };

The old pattern — defineCollection() reading only from src/content/<name>/, no loader field — still works in Astro 5+ for backward compatibility, but it’s the pattern most pre-2025 tutorials (and the gallery’s own Astro Framework — CLAUDE.md entry, which predates the Content Layer) still show. If your project uses the loader API, say so explicitly — otherwise an agent asked to “add a new content type” has a roughly even chance of writing the legacy shape instead.

Server Islands: The Personalization Footgun

Server islands (server:defer) are Astro 5’s answer to a problem that used to force an all-or-nothing choice between a fully static page and full SSR: how do you show a logged-in user’s name on an otherwise cacheable, mostly-static page?

---
// src/components/UserGreeting.astro — a server island
const user = await getSessionUser(Astro.request);
---
{user ? <p>Welcome back, {user.name}</p> : <p>Welcome</p>}
---
// src/pages/index.astro
import UserGreeting from '../components/UserGreeting.astro';
---
<Layout>
  <UserGreeting server:defer>
    <div slot="fallback">Welcome</div>
  </UserGreeting>
  <!-- the rest of this page is static and cached identically for every visitor -->
</Layout>

The rule that matters here isn’t syntax — it’s a boundary. server:defer runs on the server, per request, inside a page that’s otherwise cached and shared. That means a server island is exactly where session tokens and per-user secrets are safe to read, and exactly where a mistake (rendering something into the static shell instead of the deferred slot) would leak one user’s data into every cached copy of the page other users see. CLAUDE.md should say explicitly which components are server islands and what they’re allowed to touch — this is not a rule an agent can safely infer from Astro’s docs alone, because the failure mode is a caching bug, not a compile error.

The Astro 7 Background Dev Server: What Changes for Claude Code

This is new enough that most existing Astro CLAUDE.md guidance — including every rule set currently in our own gallery — predates it. As of Astro 7.0, astro dev automatically detects when it’s being run by an AI coding agent and starts as a detached background process instead of blocking the terminal. A lock file at .astro/dev.json records the running server’s URL, port, and PID, which prevents duplicate servers from piling up when an agent runs npm run dev more than once in the same session.

astro dev --background   # explicit background start (automatic when an agent is detected)
astro dev status         # check if a server is already running, and where
astro dev stop           # stop a running background server
astro dev logs           # tail logs from the background server
## Dev Server (Astro 7+)
- Before starting a new dev server, run `astro dev status` — a background server may already be running from a previous turn
- Never manually background `npm run dev` with `&` or `nohup` — Astro 7 already does this for detected AI agents; a manual backgrounding on top of it just orphans a second process on a different port
- To stop the dev server between tasks: `astro dev stop`, not a raw `kill`

Before Astro 7, a CLAUDE.md for any framework’s dev server had to either tell the agent to run it in true background mode manually or accept that the server would block the session. Astro now does the backgrounding itself, which removes a whole category of “agent ran npm run dev and now can’t run anything else” friction — but only if CLAUDE.md tells the agent to check astro dev status first instead of reflexively starting a new one. Set ASTRO_DEV_BACKGROUND=0 in a project’s CLAUDE.md if the team wants to opt out and keep the old blocking behavior.

Hook-Driven Verification for Astro Projects

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "astro check"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "test \"$CLAUDE_FILE_PATH\" != \"astro.config.mjs\" || echo 'astro.config.mjs edits require explicit confirmation' >&2 && exit 0"
          }
        ]
      }
    ]
  }
}

astro check catches both TypeScript errors and a class of Astro-specific mistakes (invalid props passed to a framework island, a malformed content collection schema reference) that tsc --noEmit alone won’t. It’s fast enough to run on every edit, unlike a full astro build.

AGENTS.md Compatible Version

# AGENTS.md — Astro Project

## Commands
- dev: `npm run dev`
- build: `npm run build`
- check: `astro check`
- test: `npm test`

## Critical Rules
1. No client:* directive by default — server-rendered HTML unless a component genuinely needs browser JS
2. client:visible for below-the-fold interactivity, client:load only for above-the-fold components that must be interactive immediately
3. Content collections defined in src/content.config.ts using the loader API (glob for local files, custom loaders for external sources)
4. server:defer only for per-request personalized content on otherwise-static pages — always provide a fallback slot
5. Never edit astro.config.mjs without asking — integration/adapter changes fail silently in dev
6. Before running the dev server, check `astro dev status` — Astro 7+ backgrounds it automatically for AI agents
7. Image/Picture components from astro:assets for all content images — no raw <img> in src/

## Testing
- Vitest + @astrojs/vitest container API for component-level tests
- Playwright only for flows touching server islands or hydration behavior

Common AI + Astro Mistakes to Watch For

client:load added to a component that doesn’t need to be interactive at all. The most common one by far — it compiles, the page still works, and the only symptom is a bundle that’s larger than it should be. Worth a periodic check of which components actually carry client:* directives versus which ones genuinely need them.

A content collection schema still using the pre-5.0 defineCollection() shape when the rest of the project has moved to loaders. Both patterns work side by side, which means Claude Code can add a new collection in the old style without anything failing — it just leaves the project with two content patterns instead of one.

Session data or a secret read inside the static (non-deferred) part of a page that also has a server island. The server island boundary only protects what’s inside it — reading Astro.locals.session outside a server:defer block on an otherwise-static page bakes it into the cached HTML for every visitor.

A manually backgrounded dev server (npm run dev &) on Astro 7+, stacking a second background process on top of the one Astro already starts for detected AI agents. Both keep running on different ports, and only astro dev status reveals there are two.

Raw <img> tags for content images instead of the Image component from astro:assets, which skips Astro’s build-time optimization (resizing, format conversion, layout-shift prevention) entirely.


Astro’s own build-with-AI documentation points teams toward its docs MCP server (https://mcp.docs.astro.build/mcp) for live API lookups rather than a static rules file — and that’s a good complement to CLAUDE.md, not a replacement for it. The MCP server keeps Claude Code current on Astro’s own APIs; CLAUDE.md is where your project’s hydration policy, content source structure, and server island boundaries live, none of which Astro’s docs can know on your behalf.

Browse our Astro + TypeScript and Astro Framework — CLAUDE.md gallery entries for comparison points, or browse all frontend rule sets for more framework-specific examples.


FAQ

Does Astro 7’s automatic background dev server mean I don’t need a CLAUDE.md rule about it? No — the automatic backgrounding solves the “dev server blocks the terminal” problem, but it doesn’t stop an agent from starting a second server instead of checking whether one is already running via astro dev status. The rule is still worth writing down; it just changes from “how to background it” to “check before you start one.”

Should I use client:load on anything above the fold to be safe? Only if it genuinely needs to be interactive immediately — a static hero image or heading above the fold needs no directive at all. “Above the fold” and “needs JavaScript” are independent questions; conflating them is exactly the default an undocumented CLAUDE.md tends to produce.

Is the old defineCollection() pattern (no loader) deprecated? No, it still works for local Markdown/MDX collections and Astro hasn’t announced a removal date. But it’s the older of two supported patterns, and if your project has moved to loaders for other collections, name that in CLAUDE.md so a new collection doesn’t get added in the legacy style by accident.

Can a server island read cookies or session tokens? Yes — that’s specifically what server:defer is for, since it runs on the server per request rather than being baked into the cached static shell. The rule to enforce is the boundary: that kind of read should only happen inside the deferred component, never in the static parts of the same page.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules