# Astro

> Astro rules - islands architecture, zero JavaScript by default, content collections (Content Layer API), MDX, Static Site Generation (SSG), Server-side Rendering (SSR), hybrid rendering via prerender opt-out, partial hydration (client:load, client:visible, client:idle, client:media, client:only), server islands (server:defer), Astro components, frontmatter, getStaticPaths, layouts, view transitions (ClientRouter), integrations and adapters, Astro Actions, astro:env, middleware, deployment

- Skill: `14bryanespinoza/astro` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 14bryanespinoza/astro`
- Raw SKILL.md: https://api.skillmd.com/api/skills/14bryanespinoza/astro/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: 14BryanEspinoza (https://skillmd.com/u/14bryanespinoza)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/14bryanespinoza/astro

---


# Astro — Rules and Conventions

---

## 1. Philosophy

1. **Zero-JS by default** — HTML-first. JavaScript only when explicitly opted in via hydration directives.
2. **Islands architecture** — Interactive components as islands in a sea of static HTML. No framework overhead for static content.
3. **Content-first** — Content Collections + MDX as first-class citizens. Type-safe content authoring.
4. **Framework agnostic** — Bring your own UI framework (React, Vue, Svelte, Solid) or use vanilla.
5. **Performance baseline** — Static output by default. SSR/hybrid only when needed.

---

## 2. Minimum Versions

| Technology | Minimum Version |
| ---------- | --------------- |
| Astro      | 4.15+           |
| Node.js    | 22+             |
| pnpm       | 11+             |

---

## 3. Setup and Project Structure

### Initialize

```bash
# New project
pnpm create astro@latest my-app -- --template minimal

# Add integrations
pnpm astro add react tailwind sitemap
```

### Minimal structure

```text
src/
  components/     # .astro + framework components
  content/        # Content Collections (see §7)
  layouts/        # Layout components
  pages/          # File-based routing
  styles/         # Global CSS
  env.d.ts        # Type declarations
astro.config.mjs
tsconfig.json
```

---

## 4. Astro Components (`.astro`)

### Structure

```astro
---
// Frontmatter: server-only code (runs at build/request time)
import Layout from '../layouts/Layout.astro'
import { getCollection } from 'astro:content'

const posts = await getCollection('blog')
const { title } = Astro.props
---

<!-- Template: HTML + component syntax -->
<Layout title={title}>
  <h1>{title}</h1>
  <ul>
    {posts.map(post => <li><a href={`/blog/${post.slug}`}>{post.data.title}</a></li>)}
  </ul>
</Layout>

<style>
  /* Scoped styles by default */
  h1 { color: var(--color-primary); }
</style>
```

### Rules

| Aspect      | Rule                                             | Why                                      |
| ----------- | ------------------------------------------------ | ---------------------------------------- |
| Frontmatter | Server-only, no browser APIs                     | Runs at build/SSR time                   |
| Props       | Define with `Astro.props` + TypeScript interface | Type-safe component API                  |
| Slots       | `<slot />` for content projection                | Standard composition                     |
| Styles      | Scoped by default, `is:global` for global        | Prevents style leakage                   |
| Scripts     | Hoisted to `<head>` by default                   | Non-blocking, use `is:inline` for inline |

> **HTML/CSS/JS basics**: see `html`, `css`, `javascript` skills.

---

## 5. Routing

### File-based routes

```text
src/pages/
  index.astro           → /
  blog/[...slug].astro  → /blog/* (rest param)
  blog/[slug].astro     → /blog/:slug
  api/users.json.ts     → /api/users.json (endpoint)
```

### Dynamic routes (`getStaticPaths`)

```astro
---
export async function getStaticPaths() {
  const posts = await getCollection('blog')
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post }
  }))
}
const { post } = Astro.props
---
<article>{post.body}</article>
```

### Rules Routing

- **`getStaticPaths` required** for dynamic SSG routes
- **Rest params `[...slug]`** for catch-all routes
- **Endpoints** (`.ts`/`.js` in pages/) return `Response` — see Astro Actions (§14)

---

## 6. Rendering Modes

| Mode       | Config                           | Output               | Use Case               |
| ---------- | -------------------------------- | -------------------- | ---------------------- |
| **SSG**    | `output: 'static'` (default)     | Static HTML at build | Blogs, docs, marketing |
| **SSR**    | `output: 'server'`               | HTML per request     | Auth, dynamic data     |
| **Hybrid** | `output: 'hybrid'` + `prerender` | Mix static + dynamic | Most apps              |

### Hybrid config

```js
// astro.config.mjs
export default defineConfig({
  output: "hybrid",
  adapter: node(), // or vercel, netlify, etc.
});
```

```astro
---
// src/pages/dashboard.astro
export const prerender = false  // Opt-out of SSG for this page
---
```

> **SSR/Deployment details**: see `deploy` skill.

---

## 7. Content Collections (Content Layer API)

### Define collection

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

const blog = defineCollection({
  type: "content",
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.date(),
    tags: z.array(z.string()),
    heroImage: z.string().optional(),
  }),
});

export const collections = { blog };
```

### Query content

```astro
---
import { getCollection, getEntry } from 'astro:content'

const posts = await getCollection('blog', ({ data }) => !data.draft)
const post = await getEntry('blog', 'my-post')
---
```

### Rules Content

- **`defineCollection`** in `src/content/config.ts` — single source of truth
- **Zod schema** — type-safe frontmatter validation
- **`getCollection`** for lists, **`getEntry`** for single items
- **`type: 'content'`** for MD/MDX, **`type: 'data'`** for JSON/YAML

---

## 8. Markdown and MDX

### Frontmatter

```md
---
title: "Post Title"
description: "Summary"
pubDate: 2024-01-15
tags: ["astro", "typescript"]
heroImage: "/hero.jpg"
---

Content here...
```

### MDX components

```mdx
import { Alert } from "../components/Alert.astro";
import MyReactComponent from "../components/MyReactComponent.jsx";

<Alert type="info">Astro + MDX = ❤️</Alert>
<MyReactComponent client:visible />
```

### Rules Markdown

- **MDX** = Markdown + components + JSX
- **Components in MDX** require hydration directive (`client:*`)
- **Remark/Rehype plugins** via `astro.config.mjs` → `markdown` config

---

## 9. Partial Hydration (Islands)

### Directives

| Directive        | Hydration Trigger     | Use Case                  |
| ---------------- | --------------------- | ------------------------- |
| `client:load`    | Immediately           | Critical interactive      |
| `client:visible` | IntersectionObserver  | Below-fold widgets        |
| `client:idle`    | `requestIdleCallback` | Non-urgent                |
| `client:media`   | Media query match     | Responsive islands        |
| `client:only`    | Only client, no SSR   | Framework-only components |

### Examples

```astro
---
import Counter from './Counter.jsx'
import HeavyChart from './HeavyChart.svelte'
---

<Counter client:load />
<HeavyChart client:visible />
<AdSlot client:media="(min-width: 768px)" />
<LegacyWidget client:only="react" />
```

### Rules Partial

- **Default: no JS** — static HTML only
- **Choose least eager directive** that works
- **`client:only`** skips SSR entirely — use sparingly
- **Framework components** must be in `src/components/` or installed pkg

---

## 10. UI Framework Integrations

### Install

```bash
pnpm astro add react    # or vue, svelte, solid, preact
```

### Config

```js
// astro.config.mjs
import react from "@astrojs/react";
import tailwind from "@astrojs/tailwind";

export default defineConfig({
  integrations: [react(), tailwind()],
});
```

### Usage

```astro
---
import ReactCounter from './ReactCounter.jsx'
import VueWidget from './VueWidget.vue'
---

<ReactCounter client:visible />
<VueWidget client:idle />
```

> **Tailwind/Sass**: see `tailwindcss` and `sass` skills.
> **TypeScript**: see `typescript` skill.

---

## 11. Layouts

### Basic layout

```astro
<!-- src/layouts/Layout.astro -->
---
interface Props { title: string }
const { title } = Astro.props
---
<html lang="en">
  <head><title>{title}</title></head>
  <body><slot /></body>
</html>
```

### Markdown layout

```astro
<!-- src/layouts/PostLayout.astro -->
---
import { getCollection } from 'astro:content'
const { frontmatter, ... } = Astro.props
const related = await getCollection('blog', ...)
---
<BaseLayout title={frontmatter.title}>
  <article>
    <h1>{frontmatter.title}</h1>
    <slot />
  </article>
  <aside>{related.map(...)}</aside>
</BaseLayout>
```

---

## 12. Scripts and Styles

### Scripts

```astro
---
// Hoisted to <head> (module script)
const apiUrl = import.meta.env.API_URL
---

<script>
  // Inline, not bundled — use sparingly
  console.log('Inline script')
</script>

<script is:inline>
  // Inlined as-is, not processed
</script>
```

### Styles

```astro
<style>
  /* Scoped to this component */
  h1 { color: red; }
</style>

<style is:global>
  /* Global — use for CSS resets, variables */
  :root { --color-primary: #0066cc; }
</style>
```

### Rules Script and Style

- **Default scoped** — no class naming convention needed
- **`is:global`** only for reset, variables, keyframes
- **CSS variables** for theming — see `css` skill

---

## 13. Images and Assets

### Astro Image

```astro
---
import { Image } from 'astro:assets'
import hero from '../assets/hero.jpg'
---

<Image src={hero} alt="Hero" width={800} height={400} format="avif" />
```

### Rules Image and Assets

- **`astro:assets`** for local images — optimized, hashed, responsive
- **`format="avif"`** — best compression, fallback handled
- **Public assets** in `public/` — served as-is, no optimization
- **Remote images** — use `<img>` with `loading="lazy"`

---

## 14. View Transitions

### ClientRouter (SPA-like navigation)

```js
// astro.config.mjs
export default defineConfig({
  viewTransitions: true,
});
```

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

### Rules View transitions

- **`viewTransitions: true`** enables client-side navigation
- **`<ViewTransitions />`** in `<head>` — required
- **`transition:name`** for element-level animations
- **Fallback** — works without JS (full page reload)

---

## 15. Astro Actions

### Define action

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

export const server = {
  signup: defineAction({
    input: z.object({ email: z.string().email() }),
    handler: async ({ email }) => {
      // Server-only code (DB, email, etc.)
      return { success: true };
    },
  }),
};
```

### Use in component

```astro
---
import { actions } from 'astro:actions'
---
<script>
  const form = document.querySelector('form')
  form.addEventListener('submit', async (e) => {
    e.preventDefault()
    const result = await actions.signup({ email: form.email.value })
    if (result.error) console.error(result.error)
  })
</script>
```

### Rules Astro actions

- **Type-safe** — input validated via Zod, return type inferred
- **Server-only** — runs on server, never in browser
- **Form integration** — works with standard `<form>` + `action` attr

---

## 16. Middleware and SSR APIs

### Middleware

```ts
// src/middleware.ts
import { defineMiddleware } from "astro:middleware";

export const onRequest = defineMiddleware(async (context, next) => {
  // Auth, logging, redirects
  if (context.url.pathname.startsWith("/admin")) {
    const session = await getSession(context.cookies);
    if (!session) return context.redirect("/login");
  }
  return next();
});
```

### SSR APIs

```astro
---
// Access request/response
const { request, response, cookies, locals, params, url } = Astro
---
```

### Rules Middleware

- **Middleware** runs before every request (SSR/hybrid only)
- **`locals`** for request-scoped data (user, session)
- **`cookies`** — sign/unsign with `astro:env` secret

---

## 17. Environment Variables

### `astro:env` (type-safe)

```ts
// env.d.ts
/// <reference types="astro/client" />

interface ImportMetaEnv {
  readonly PUBLIC_API_URL: string;
  readonly SECRET_DB_URL: string;
}
```

```astro
---
// Public: available in browser
const apiUrl = import.meta.env.PUBLIC_API_URL

// Secret: server-only (SSS/hybrid only)
const dbUrl = import.meta.env.SECRET_DB_URL
---
```

### Rules environment

- **`PUBLIC_` prefix** — exposed to client (like Vite's `VITE_`)
- **No prefix** — server-only, stripped from client bundle
- **`astro:env`** — validates at build, TypeScript types auto-generated

---

## 18. Methodology

Before using ANY Astro config/integration/pattern not documented in
this skill:

1. **MCP Context7** (priority): `context7_resolve-library-id` +
   `context7_query-docs` for Astro/integrations.
2. **Official docs**: astro.build — verify current API + integrations.
3. **Project config**: `astro.config.mjs`, `tsconfig.json`,
   `src/content/config.ts` — verify against actual setup.
4. **HARD RULE**: If not in this skill AND cannot be verified against
   2 authoritative sources → DO NOT USE IT. Document as assumption or risk in
   report to orchestrator.

---

## 19. Prohibitions

- ❌ Do not use `client:load` by default — choose least eager directive
- ❌ Do not put secrets in `PUBLIC_` env vars — server-only without prefix
- ❌ Do not skip `getStaticPaths` for dynamic SSG routes
- ❌ Do not use framework components without hydration directive
- ❌ Do not put client-side logic in frontmatter — runs on server
- ❌ Do not use `client:only` unless framework has no SSR support
- ❌ Do not commit `dist/` or `.astro/` — add to `.gitignore`
- ❌ Do not bypass Content Collections schema — loses type safety

---

## 20. References

> **Note:** For HTML conventions, see [HTML](../html/SKILL.md)
> **Note:** For CSS conventions, see [CSS](../css/SKILL.md)
> **Note:** For JavaScript conventions, see [JavaScript](../javascript/SKILL.md)
> **Note:** For TypeScript rules, see [TypeScript](../typescript/SKILL.md)
> **Note:** For package manager conventions, see
> [Package Manager](../package-manager/SKILL.md)
> **Note:** For deployment (SSR/adapters), see [Deploy](../deploy/SKILL.md)
> **Note:** For performance (Core Web Vitals), see
> [Performance](../performance/SKILL.md)
> **Note:** For accessibility (WCAG), see
> [Accessibility](../accessibility/SKILL.md)
> **Note:** For Vite integration, see [Vite](../vite/SKILL.md)
> **Note:** For Sass/Tailwind, see [Sass](../sass/SKILL.md) /
> [Tailwind CSS](../tailwindcss/SKILL.md)

---

Last updated: 2026-08

