Astro AI Rules

Rules for Astro content sites: islands architecture, content collections with Zod schemas, View Transitions, and integration patterns. Keeps the AI from over-using framework islands.

TypeScriptAstro#astro#content-collections#islands#typescriptLast updated 2026-05-05
tune

Want to customize this rules file? Open the generator with this stack pre-loaded.

Open in generatorarrow_forward

Save at .cursor/rules/main.mdc

Astro

Project context

This is an Astro content site — primarily static, with islands of interactivity. We use content collections for everything authored as markdown, view transitions for navigation, and ship as little JavaScript as possible.

Stack

  • Astro 4+
  • TypeScript strict
  • Content collections with Zod schemas
  • Tailwind CSS 4 via @astrojs/tailwind
  • View Transitions (built into Astro)
  • MDX via @astrojs/mdx for rich content
  • Optional islands: React, Solid, or Vue (only when interactivity demands it)
  • pnpm

Folder structure

src/
  content/
    config.ts            — content collections schemas (Zod)
    blog/
      post-1.mdx
      post-2.mdx
    docs/
  pages/
    index.astro
    blog/
      index.astro
      [...slug].astro
  layouts/
    BaseLayout.astro
    PostLayout.astro
  components/
    Header.astro
    PostCard.astro
    InteractiveBit.tsx   — only when interactivity is needed
  styles/
    global.css
  assets/                — images / fonts that get optimized
public/                  — files served as-is

Content collections

Define every collection's schema in src/content/config.ts using Zod. Astro generates types from this — you get autocompletion in getCollection() and getEntry().

import { defineCollection, z } from 'astro:content'

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
})

export const collections = { blog }
  • One schema per collection; never hand-write types
  • Use z.coerce.date() for date fields — frontmatter dates parse as strings
  • Validate strict — frontmatter typos are easier to catch at build time than at render time

Pages and routing

  • File-based routing — src/pages/<path>.astro
  • Dynamic routes: [slug].astro for one segment, [...slug].astro for catch-all
  • Use getStaticPaths() for pre-rendered dynamic routes
---
import { getCollection } from 'astro:content'

export async function getStaticPaths() {
  const posts = await getCollection('blog', ({ data }) => !data.draft)
  return posts.map((post) => ({ params: { slug: post.slug }, props: { post } }))
}

const { post } = Astro.props
const { Content } = await post.render()
---

<Layout title={post.data.title}>
  <article><Content /></article>
</Layout>

Islands

Astro renders to static HTML by default. Use framework islands sparingly — every island ships JavaScript to the browser.

  • <Component client:load /> — hydrate immediately (rare; reserve for above-the-fold interactive UI)
  • <Component client:idle /> — hydrate when the browser is idle (default for non-critical interactive UI)
  • <Component client:visible /> — hydrate when scrolled into view (best for below-fold)
  • <Component client:only="react" /> — never SSR; client-only (avoid unless necessary)

Default to client:visible. Audit every client:load — most aren't needed.

View Transitions

Add <ViewTransitions /> in your base layout once; Astro handles the rest. Use transition:name on shared elements to morph them between pages.

---
import { ViewTransitions } from 'astro:transitions'
---
<head>
  <ViewTransitions />
</head>

Patterns to follow

  • Default to .astro components; reach for React/Solid/Vue only when an interactive island demands it
  • Optimize images via <Image /> from astro:assets — never raw <img> for in-content images
  • Use Astro.glob() only for non-collection content; prefer getCollection() everywhere else
  • Configure output: 'static' (or 'hybrid' if you have a few SSR routes); 'server' only when you really need it

Patterns to avoid

  • Sprinkling client:load everywhere — defeats the entire point of Astro
  • Raw <img> tags — use <Image /> for optimization
  • Hand-typed frontmatter — define a Zod schema in config.ts
  • Mixing multiple frameworks for islands without need (React + Vue + Svelte all in one project = bigger bundle)
  • Astro.glob() for content — use collections instead
  • Storing CSS in public/ — let Astro process it through Vite

Testing

  • Vitest for utility-function tests
  • Playwright for end-to-end content rendering checks
  • Use astro check for type-checking content + components

Tooling

  • pnpm dev — Astro dev server
  • pnpm build — produces static HTML to dist/
  • pnpm preview — serves the built output
  • pnpm astro check — type-checks .astro files (run in CI)

AI behavioral rules

  • Default to .astro components; only reach for React/Solid/Vue islands when interactivity actually requires it
  • Use <Image /> from astro:assets for any image; never raw <img>
  • Define every collection's schema in src/content/config.ts; never hand-type
  • Default to client:visible for islands; never client:load without a reason
  • Use getCollection / getEntry for content; not Astro.glob
  • Run astro check and any tests before declaring a task done

Frequently asked

How do I use this Astro rules file with Cursor?

Pick "Cursor (.cursor/rules/*.mdc)" from the format dropdown above and click Copy. Save it at .cursor/rules/main.mdc in your project root and restart Cursor. The legacy .cursorrules format still works if you're on an older Cursor version — pick that option instead.

Can I use this with Claude Code (CLAUDE.md)?

Yes — pick "Claude Code (CLAUDE.md)" from the format dropdown above and copy. Save the file as CLAUDE.md at your repo root. Claude Code reads it automatically on every session. For monorepos, you can also drop nested CLAUDE.md files in subdirectories — Claude merges them when working in those paths.

Where exactly do I put this file?

It depends on the AI tool. Cursor reads .cursorrules or .cursor/rules/*.mdc at the project root. Claude reads CLAUDE.md at the project root. Copilot reads .github/copilot-instructions.md. The "Save at" path under each format in the dropdown shows the exact location for the format you picked.

Can I customize these Astro rules for my project?

Yes — that's what the generator is for. Click "Open in generator" above and the wizard loads with this stack's defaults pre-selected. Toggle on or off the conventions you want, then re-export in your AI tool's format.

Will using this rules file slow down my AI tool?

No. Rules files count toward the model's context window but not toward latency in any noticeable way. The file is loaded once per session, not per token. The library files target 250–400 lines, well within every tool's recommended budget.

Should I commit this file to git?

Yes. The rules file is project documentation that benefits every developer using the AI tool. Commit it. The exception is personal-global settings (e.g. ~/.claude/CLAUDE.md) which are user-scoped and stay out of the repo.

Related stacks