Astro — Claude Code rules
You are working in an Astro project (islands architecture, content-driven sites). The core principle: render to HTML & CSS and ship zero client-side JavaScript by default. Add JS only where interactivity is genuinely needed.
Components (.astro)
- A
.astrofile has a component script in a code fence (---) and a template below it. - The component script runs at build time / on the server only. It is stripped from the page sent to the browser — put imports, data fetching, and API calls here.
- The template is HTML with
{ }expressions referencing script variables. - Props come in via
Astro.props. Define aninterface Propsfor type safety, e.g.const { title } = Astro.props;. Support defaults in the destructure when useful. - Use
<slot />for children; named slots via<slot name="x" />+slot="x"on the child. Provide fallback content inside the slot when no children are passed. - Compose pages from small components. Astro components render with zero client JS by default.
Islands & hydration (client: directives)
- An island is an interactive UI component on an otherwise static page. Islands run in isolation and hydrate independently, in parallel — they don't block each other.
- Only components marked with a
client:*directive ship JavaScript. Pick the lightest one:client:load— hydrate immediately on page load (high-priority, above-the-fold UI).client:idle— hydrate once the browser is idle (lower-priority UI).client:visible— hydrate only when the component scrolls into view (default for most things).client:media={QUERY}— hydrate when a CSS media query matches.client:only="react"— skip server render; render on the client only (name the framework).
- No directive = static HTML, no JS. Don't add
client:*to anything that isn't interactive. - For dynamic server-rendered content, use a server island via the
server:deferdirective instead of shipping client JS. - Plain
.astrocomponents cannot takeclient:*— only framework components hydrate.
UI framework components (React / Vue / Svelte / Preact / Solid)
- Without a directive, framework components also render to static HTML — no JS shipped.
- Only Astro components can mix multiple frameworks on one page. Framework components
cannot import
.astrofiles. - Props passed to a hydrated island must be serialisable (objects, numbers, strings, Arrays, Map, Set, Date, RegExp, URL, BigInt, typed arrays). Functions cannot be passed.
- Children: React/Preact/Solid use a
childrenprop; Svelte/Vue use<slot />.
Content (content collections)
- Manage structured content (blog posts, docs, etc.) with content collections, stored
outside
src/pages/. - Define collections in
src/content.config.tswithdefineCollection(), exported as a singlecollectionsobject. - Use a loader:
glob()for a directory of Markdown/MDX/JSON/YAML/TOML,file()for one file with many entries. Set thebasepath explicitly. - Give each collection a Zod
schemafor validated, type-safe frontmatter, e.g.z.object({ title: z.string(), pubDate: z.coerce.date() }). Schemas are optional but recommended. - Query with
getCollection('blog')andgetEntry('dogs', 'poodle')fromastro:content. - Render entries with the standalone
render():const { Content } = await render(entry);. - Link entries across collections with
reference().
Don't
- Don't add
client:*to non-interactive components, or reach for a framework when plain Astro + a small<script>will do. - Don't pass functions (or non-serialisable values) as props to a hydrated island.
- Don't import a
.astrocomponent from inside a framework component. - Don't put content collection files under
src/pages/, and don't skip the Zod schema. - Don't expect
client:onlycomponents to render on the server — they won't.