Claude Code with Astro: Content Sites and Islands Workflow
Why Astro projects need framework-specific Claude Code configuration
Claude Code knows Astro. It knows .astro component syntax, the content collections API, Astro.props, Astro.slots, server-side rendering, static output modes, and the islands architecture. It can scaffold an Astro page from a prompt.
The problem is that Astro is unusually flexible and that flexibility produces ambiguous outputs without guidance. Claude Code can generate a component with React, Svelte, Vue, or plain Astro syntax, and without direction it picks whichever feels most natural for the prompt. It might add a hydration directive when the component does not need JavaScript in the browser. It might build a content collection query without a schema, making the frontmatter untyped. It might structure a content site with hard-coded data when a type-safe collection would be cleaner.
None of these outputs are wrong. They compile, they render. But they are not what your project needs, and adjusting them after the fact costs more time than configuring Claude Code before the first session.
A project-specific CLAUDE.md gives Claude Code the context it needs to make correct decisions from the start. This guide builds that configuration and covers the content collection, island, and deployment patterns that follow from it. If you are new to Claude Code entirely, the Claude Code setup guide covers installation before any of this applies.
The Astro CLAUDE.md
An Astro CLAUDE.md needs to answer more framework-specific questions than most project configs because Astro's component model, hydration system, and content layer each introduce decisions that Claude Code must get right upfront.
# Astro project rules
## Project configuration
- Output mode: static (npx astro build → dist/ with static HTML)
OR server (npx astro build → SSR with adapter). Specify which applies.
- Adapters: Cloudflare / Vercel / Node, specify the active adapter.
- Integrations installed: [list your integrations, e.g. @astrojs/react, @astrojs/tailwind]
- TypeScript: strict mode. All .astro files use typed frontmatter.
## File structure
- Pages: src/pages/ (file-based routing)
- Layouts: src/layouts/
- Components: src/components/ (Astro components by default)
- UI framework components: src/components/{framework}/ (e.g. src/components/react/)
- Content collections: src/content/{collection}/
- Schemas: src/content/config.ts
- Assets: src/assets/ (processed by Astro) or public/ (copied verbatim)
- Path alias: @/ maps to src/
## Astro components (.astro)
- Frontmatter (---) handles all data fetching and logic. No logic in template.
- Props typed with: const { title, ...rest } = Astro.props as { title: string }
OR use TypeScript interface and Props export.
- No client-side JavaScript in .astro component frontmatter. It runs on the server.
- Slots: use <slot /> for default, <slot name="header" /> for named slots.
- Styles scoped by default. Use is:global only when explicitly needed.
## Islands architecture (hydration directives)
- Default: no hydration directive. Component renders as static HTML.
- client:load, hydrates immediately on page load. Use for above-the-fold
interactive components (nav dropdowns, search inputs).
- client:idle, hydrates when browser is idle. Use for below-the-fold
interactive components.
- client:visible, hydrates when the component enters the viewport. Use for
interactive components deep in long pages.
- client:only="{framework}", skips server render, renders in browser only.
Use for components that depend on browser APIs unavailable on the server.
- Never add a hydration directive to a component that does not need JavaScript.
## Content collections
- All content in src/content/ must have a schema in src/content/config.ts.
- Schema defined with z from astro:content (Zod).
- No untyped frontmatter. Every collection entry is validated against its schema.
- Query pattern: const { entries } = await getCollection('posts')
- Entry data type: CollectionEntry<'posts'>, import from astro:content.
## Styling
- Tailwind CSS for utility classes (if integration is installed).
- Component-scoped styles in <style> block for custom CSS.
- Global styles: src/styles/global.css, imported in the root layout.
- No inline style attributes unless dynamically generated from data.
## Testing
- Vitest for unit tests on utilities and schema validation.
- Playwright for E2E tests of rendered pages and interactive islands.
- No Vitest tests for .astro components directly, test them via Playwright.
## Hard rules
- Run npx astro check after every structural change. Fix all type errors.
- Run npx astro build before considering a structural task done.
- Never add client: directives to .astro components. Only UI framework components
(.tsx, .vue, .svelte) can be hydrated as islands.
- No hardcoded content that belongs in a content collection.
The two rules that change output quality the most: the hydration directive decision tree and the content collection enforcement. Without these, Claude Code generates interactive components with client:load on everything (including static header text) and hardcodes blog posts as arrays in page frontmatter instead of content collection entries.
Content collections
Content collections are Astro's type-safe content layer. Every collection entry is a Markdown, MDX, or JSON file in src/content/{collection}/. A schema in src/content/config.ts validates every entry at build time and generates TypeScript types you can use across the project.
The prompt that produces a complete, typed collection setup:
"Create a blog post content collection. The schema should require: title (string), description (string max 160 chars), date (date), author (string), tags (array of strings), and draft (boolean, optional, defaults to false). The collection is named 'posts'. Create the schema in src/content/config.ts and write a typed helper function getPublishedPosts() in src/lib/blog.ts that returns all non-draft posts sorted by date descending."
Claude Code generates the schema with z.object() from astro:content, the defineCollection and defineConfig exports in config.ts, and the getPublishedPosts helper using getCollection with a filter and sort. The collection entries are validated at build time against this schema, so a post with a missing title field is a build error rather than a runtime undefined.
The schema file:
// src/content/config.ts
import { defineCollection, z } from 'astro:content'
const posts = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string().max(160),
date: z.date(),
author: z.string(),
tags: z.array(z.string()),
draft: z.boolean().optional().default(false),
}),
})
export const collections = { posts }
Add a draft filter rule to your CLAUDE.md:
## Content collection conventions
- Draft posts use draft: true in frontmatter
- getPublishedPosts() always filters draft: true entries
- Production builds with drafts are a build error, not a runtime check
Islands architecture decisions
Astro's island model is the most conceptually different thing about developing with it, and it is the area where Claude Code needs the clearest guidance. The default for any component should be static HTML. Hydration is an explicit choice for components that require browser JavaScript.
The decision process your CLAUDE.md encodes:
No directive when: the component is purely presentational, displays data from a content collection or API call, has no event handlers, and has no state. This includes navigation headers with static links, blog post layouts, hero sections, footer components, and any component where interactivity is not a requirement. Static components have zero JavaScript weight in the browser.
client:load when: the component is visible immediately on page load and requires interactivity from the first moment, such as a search input in the header, a modal trigger that must be ready before the user scrolls, or a shopping cart icon with item count.
client:idle when: the component is visible on page load but not immediately critical, such as a newsletter signup form in the sidebar or a comment section below the fold on a long page.
client:visible when: the component is only reached after scrolling and there is no benefit to hydrating it before it enters the viewport. Good for interactive widgets in the middle or bottom of long-form content pages.
client:only when: the component depends on window, document, navigator, or any browser API that does not exist in server-side rendering context. Maps and chart libraries that access the DOM at initialization are common examples.
The prompt that applies this correctly:
"Build a blog post page layout. The article content, metadata, and author bio are static and come from the content collection. Add a comment count component (React) that fetches from an API. Add a share button (React) that uses the Web Share API. Configure the hydration directives correctly for each island."
With the CLAUDE.md in place, Claude Code will set client:idle on the comment count (below the content, not immediately critical) and client:only="react" on the share button (uses a browser API). Without the configuration, everything gets client:load.
Component authoring across frameworks
One of Astro's distinctive features is the ability to use components from multiple UI frameworks in a single project. A layout might use Astro components for structure, React for interactive widgets, and inline Svelte for a particularly complex animation. Claude Code handles this when the CLAUDE.md makes the decision rule clear.
The rule to add:
## Framework component selection
- Default: .astro component (zero JavaScript, fastest)
- Interactive component needing state or events: use the project's primary framework
(specify: React / Svelte / Vue)
- Component from a third-party library: use whatever framework the library targets
- client:only components: always document why server-rendering is not possible
Prompting Claude Code to author a framework component for an Astro island:
"Create a React SearchModal component in src/components/react/. It opens via a trigger button. On open, it shows a text input. As the user types, it queries the site search index at /api/search.json with the query string and displays results. Each result links to a blog post URL. The component will be used with client:load in the site header."
Claude Code generates a React component with useState for the modal open state, useState + useEffect for the search query, and correct anchor tags using paths from the search index. It does not add Astro-specific syntax inside the React file, and it does not add a hydration directive inside the component itself (that belongs in the .astro file that imports it).
The import and usage in the .astro layout:
---
import SearchModal from '@/components/react/SearchModal'
---
<SearchModal client:load />
Querying content collections in pages
The most common content query pattern in Astro: fetch all entries from a collection, filter, sort, and pass the result to a layout.
"Create a blog listing page at src/pages/blog/index.astro. It should: fetch all published posts using getPublishedPosts() from src/lib/blog.ts, render each post as a card showing the title, description, date, and tags, and paginate at 10 posts per page using Astro's built-in paginate() function. Use the BlogPostCard component from src/components/."
Claude Code generates the page with the paginate function correctly destructured from Astro.props, the collection query in the frontmatter, and the component usage in the template. The key contribution from the CLAUDE.md is that Claude knows getPublishedPosts exists in src/lib/blog.ts rather than writing a raw getCollection call inline on the page.
For dynamic routes (individual blog post pages), the pattern:
"Create a dynamic route at src/pages/blog/[slug].astro. The getStaticPaths() function should return all published posts from the posts collection. Each page should render the full article content using Astro's <Content /> component, the post frontmatter for metadata, and a prev/next navigation using the surrounding posts."
Claude Code generates the getStaticPaths function with the correct CollectionEntry<'posts'> type annotation, the Astro.props destructuring, and the <Content /> component usage. The <Content /> component is Astro-specific and renders MDX or Markdown content that Claude Code uses correctly when it knows the project uses content collections.
TypeScript and Astro check
Running npx astro check is the equivalent of tsc --noEmit for Astro projects. It checks frontmatter types against content collection schemas, validates Astro.props usage, and catches component import issues. Add it to the hard rules section of your CLAUDE.md and reference it explicitly in task completion criteria:
## Definition of done for any Astro task
1. npx astro check passes with no errors
2. npx astro build completes without errors
3. The output HTML in dist/ reflects the intended content
Claude Code runs these checks automatically when the definition of done is explicit. Without it, Claude considers a task complete when the development server is running, which misses the category of errors that only surface at build time or during type checking.
The Claude Code hooks guide covers how to wire astro check and astro build into a PostToolUse hook so they run automatically after every file change without adding the instruction to every prompt.
Deployment workflow
Astro's static output mode (the default) produces a dist/ directory of HTML, CSS, and JavaScript that deploys to any static host. Claude Code can write the configuration for the most common deployment targets.
For Cloudflare Pages:
"Add the @astrojs/cloudflare adapter to astro.config.mjs. Configure the output mode for server-side rendering. Create a wrangler.toml with the correct settings for a Cloudflare Pages deployment."
For Vercel:
"Add the @astrojs/vercel adapter and configure astro.config.mjs for Edge runtime. Update the build output for Vercel's expected directory structure."
Add your deployment target to the CLAUDE.md:
- Deployment target: Cloudflare Pages (static export)
- Build command: npx astro build
- Output dir: dist/
- No server adapter, static output only
This prevents Claude Code from suggesting SSR-specific patterns (middleware, server endpoints) for a project that deploys as static HTML.
What Astro developers get wrong first
Three failure modes appear consistently when Astro developers use Claude Code without framework-specific configuration.
Hydrating every component. Without the hydration directive decision tree, Claude Code defaults to client:load on components that have any interactivity at all, including components where the "interactivity" is just an href attribute. Every unnecessary hydration directive adds JavaScript weight. The CLAUDE.md rule "Never add a hydration directive to a component that does not need JavaScript" eliminates this pattern.
Untyped frontmatter. Developers starting a new Astro project often add Markdown files with ad-hoc frontmatter before setting up a content collection schema. Claude Code follows this pattern when it sees existing untyped files. The content collection schema rule in the CLAUDE.md routes Claude to defineCollection and z.object() from the first file, not after the codebase has accumulated untyped frontmatter.
Logic in templates. Astro frontmatter is for data fetching and computation. Templates should receive processed data and render it. Without a clear rule, Claude Code sometimes writes conditional logic and transformations in the template section rather than the frontmatter. The CLAUDE.md rule "Frontmatter handles all data fetching and logic. No logic in template" keeps the separation clean.
Building a reliable Astro workflow
The configuration in this guide produces a Claude Code setup where content collections are typed and validated, islands are hydrated only when necessary, components use the right framework for each context, and build verification runs automatically as part of the development loop.
The foundation is the CLAUDE.md template above. Add it before the first Claude Code session on a new Astro project, run one full content page cycle (collection schema, helper function, listing page, detail page), and adjust based on what Claude generates. Most Astro projects need one or two additional rules after the first session, usually around the specific UI framework integrations in use.
For the broader session management and context window techniques that apply across all Claude Code workflows, the Claude Code best practices guide is the right next read. For using Claude Code skills to extend the Astro workflow with pre-built patterns for SEO, accessibility, and image optimization, the Claude Code skills guide covers the skills model that makes this possible.
The Claude Code MCP servers guide covers how to connect Claude Code to external data sources and APIs that feed content into Astro pages at build time. And if you are considering what a Claudify subscription provides beyond the base Claude Code experience for content site development, see the Claudify plans.
More like this
Ready to upgrade your Claude Code setup?
Get Claudify