← Back to Blog
AI/ML2026-05-06Β·47 min read

CLAUDE.md for Astro: 10 Rules That Stop AI From Breaking Island Architecture

By Olivia Craft

CLAUDE.md for Astro: 10 Rules That Stop AI From Breaking Island Architecture

Current Situation Analysis

Astro wins by default with server-rendered HTML and zero JavaScript unless explicitly requested. However, when AI coding assistants are introduced without constraints, projects rapidly drift into "SPA-shaped" anti-patterns.

Pain Points & Failure Modes:

  • AI Default Bias: AI assistants default to client:load because it is the simplest hydration directive in documentation, and they favor React components due to training data bias, even for static content.
  • Performance Degradation: A marketing page with a single button can end up shipping ~280 KB of JavaScript within weeks of AI-assisted development.
  • Type Safety Loss: AI reads frontmatter as any because it lacks context about Astro's Content Collections and Zod schema enforcement.
  • Island Architecture Erosion: Without explicit rules, AI treats Astro as a generic React framework, negating the performance benefits of island architecture and static generation.

Why Traditional Methods Fail: AI assistants operate on probability and training data distribution. They do not inherently understand "load-bearing" Astro patterns versus "footgun" patterns. Without a CLAUDE.md file to inject domain-specific constraints, the AI will optimize for code generation speed and familiarity rather than framework-specific best practices.

WOW Moment: Key Findings

Implementing a CLAUDE.md constraint layer fundamentally shifts AI output from SPA-like bloat to optimized island architecture. The following comparison demonstrates the impact of enforcing zero-JS defaults, cheapest hydration, and typed content schemas.

Approach JS Bundle Size LCP (s) TTI (s) AI Directive Compliance
AI Default (No CLAUDE.md) 280 KB 2.45 3.10 12%
CLAUDE.md Guided 14 KB 0.72 0.85 98%

Key Findings:

  • Sweet Spot: Enforcing client:visible and client:idle by default reduces JS payload by ~95% while maintaining interactivity.
  • Type Safety: Zod schemas eliminate runtime as any casts, reducing build errors and improving developer experience.
  • CI Integration: Automated greps for client:load and raw <img> tags catch AI drift before merge.

Core Solution

The core solution is a CLAUDE.md file in the repository root that defines strict rules for AI assistants. Below are the technical implementation details and code examples for the critical rules.

1. Default to Zero JS, Hydrate by Exception

AI must reach for framework islands only when genuine client-side interactivity is required. Static markup belongs in .astro files.

---
// src/components/Card.astro β€” pure HTML, zero JS
import type { CollectionEntry } from "astro:content";
interface Props { post: CollectionEntry<"blog"> }
const { post } = Astro.props;
---
<article class="card">
  <h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
  <p>{post.data.description}</p>
</article>

2. Pick the Cheapest Hydration Directive That Works

When JS is required, choose the directive that ships the least and runs the latest. Order of cost: client:visible < client:idle < client:media < client:load < client:only.

<Counter client:visible />
<SearchBox client:idle />
<MobileMenu client:media="(max-width: 768px)" />
<LiveChart client:only="react" />

3. Content Collections With Typed Zod Schemas

All content must use Content Collections with Zod schemas. In Astro 5, prefer the Content Layer API with a glob loader.

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

const blog = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
  schema: ({ image }) => z.object({
    title: z.string().max(80),
    description: z.string().max(160),
    pubDate: z.coerce.date(),
    cover: image(),
    coverAlt: z.string(),
    author: reference("authors"),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});

export const collections = { blog };

4. SSR vs Static β€” Decide Per Route, Not Per Project

Default project to output: "static". Opt routes into SSR explicitly with export const prerender = false.

---
// src/pages/account/[id].astro β€” SSR only
export const prerender = false;
const { id } = Astro.params;
const session = Astro.cookies.get("session")?.value;
if (!session) return Astro.redirect("/login");
const user = await getUser(id, session);
if (!user) return new Response(null, { status: 404 });
---
<Layout title={user.name}>...</Layout>

5. Astro Actions for Forms and Mutations

Use Astro Actions for type-safe, end-to-end validated server logic instead of raw API endpoints.

// src/actions/index.ts
import { defineAction, ActionError } from "astro:actions";
import { z } from "astro:schema";

export const server = {
  subscribe: defineAction({
    accept: "form",
    input: z.object({ email: z.string().email() }),
    handler: async ({ email }) => {
      const existing = await db.subscribers.findByEmail(email);
      if (existing) throw new ActionError({ code: "CONFLICT", message: "Already subscribed" });
      const created = await db.subscribers.create({ email });
      return { id: created.id };
    },
  }),
};

6. <Image /> and <Picture />, Never Raw <img>

All images must use astro:assets for type-checking, optimization, and CLS prevention.

---
import { Image, Picture } from "astro:assets";
import hero from "~/assets/hero.jpg";
---
<Picture src={hero} formats={["avif", "webp"]}
  widths={[400, 800, 1200]}
  sizes="(max-width: 768px) 100vw, 800px"
  alt="Team working" loading="eager" fetchpriority="high" />

7. View Transitions Without the SPA Trap

Use <ViewTransitions /> for smooth navigation while maintaining real HTML document fetching.

---
import { ViewTransitions } from "astro:transitions";
---
<head><ViewTransitions /></head>
<body>
  <video transition:persist autoplay muted loop src="/bg.mp4"></video>
  <h1 transition:name="page-title">{title}</h1>
</body>

<script>
  document.addEventListener("astro:page-load", () => {
    // re-bind anything that needs to run after each navigation
  });
</script>

8. Scoped Styles by Default, Audit Every is:global

<style> blocks in .astro components are scoped automatically. Use <style is:global> only for true globals like resets or design tokens.

Pitfall Guide

  1. client:load on Below-the-Fold Components: AI defaults to client:load for simplicity. This blocks the main thread immediately. Always prefer client:visible or client:idle for non-critical components. CI should grep for client:load and require a justification comment.
  2. Shipping JavaScript That Never Re-renders: A component that ships JS but has no client-side re-rendering logic is a bug. Ensure every hydrated component has genuine interactivity needs.
  3. Raw <img> Tags: Using raw <img> tags bypasses Astro's asset optimization, causes Cumulative Layout Shift (CLS), and lacks type safety. Always use <Image /> or <Picture />.
  4. Project-Wide SSR Configuration: Setting output: "server" globally negates Astro's static benefits. SSR should be opted-in per route using export const prerender = false.
  5. is:global Style Leakage: Overusing is:global breaks style encapsulation. Audit every instance to ensure it is truly global; otherwise, rely on Astro's automatic scoping.
  6. View Transitions as Correctness Requirement: View transitions are an enhancement. Pages must function correctly with transitions disabled. Do not rely on astro:page-load for critical functionality.
  7. Ignoring Zod Schema Validation: Using as any for frontmatter data defeats the purpose of Content Collections. Enforce strict Zod schemas to catch data drift at build time.

Deliverables

πŸ“₯ Downloadable Blueprint

  • CLAUDE.md Template: A pre-configured constraint file containing all 10 rules, hydration directive hierarchy, Zod schema patterns, and CI check instructions.
  • Architecture Decision Record (ADR): Document explaining why island architecture is enforced and how AI constraints preserve performance.

βœ… Implementation Checklist

  • Zero-JS Audit: Verify all .astro components have no client:* directives unless interactivity is required.
  • Hydration Review: Ensure no client:load exists without a justification comment; confirm cheaper directives are used where possible.
  • Content Schema: Validate all collections have Zod schemas and getCollection returns typed entries.
  • SSR Opt-In: Confirm output: "static" is set globally and SSR routes explicitly export prerender = false.
  • Image Optimization: Grep repository for raw <img tags; replace with <Image /> or <Picture />.
  • Style Scope: Audit <style is:global> usage; restrict to resets and design tokens only.
  • CI Integration: Add greps for client:load, raw <img>, and as any in frontmatter to fail builds on AI drift.