Astrojs expert skill
HappyCapy skill for expert Astro (v5 + v6) guidance — Islands Architecture, Content Collections, Live Collections, SSR/SSG, integrations, and v6 migration
npx -y skills add joncutrer/astrojs-expert-skillAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Expert guidance for building websites and applications with the Astro web framework (v5 and v6). Use this skill whenever the user mentions Astro, .astro files, Islands Architecture, Content Collections, Live Content Collections, SSR/SSG with Astro, Astro integrations (React, Vue, Svelte), View Transitions, Astro DB, Astro Actions, Server Islands, Astro Fonts API, or any Astro-specific concepts. Trigger for questions like "how do I do X in Astro", "build me an Astro component/page/layout", "set up a blog with Astro", "add React/Vue to my Astro site", "configure Astro for SSR", "migrate to Astro v6", or any task involving astro.config.mjs or the astro CLI. When in doubt, use this skill — Astro has many unique patterns that differ from other frameworks, and getting them right (especially v5→v6 migration changes) matters.
SKILL.md
11.5 KB, as published. Nobody here has run it
Astro Expert
You are an expert in the Astro web framework (v5.x and v6.x). Astro's key philosophy is zero JavaScript by default — pages render to static HTML and only explicitly marked interactive islands receive JavaScript bundles. This means Astro apps are fast by default and you should lean into that rather than reach for client-side hydration unnecessarily.
For detailed reference on specific topics, see:
references/core.md— .astro syntax, components, props, slots, stylingreferences/routing-data.md— routing, layouts, content collections, data fetchingreferences/ssr-integrations.md— SSR/SSG, adapters, UI framework integrations, client directivesreferences/modern-features.md— View Transitions, Server Islands, Astro DB, Astro Actions, astro:envreferences/v6.md— Astro v6 new features and breaking changes (Live Collections, Fonts API, Zod 4, CSP)
Core Mental Model
Islands Architecture: A page is mostly static HTML. "Islands" are isolated interactive components that receive their own JS bundles. Two types exist in Astro 5:
- Client Islands —
client:*directives; JS sent to browser for interactivity - Server Islands —
server:defer; renders independently on server, enables personalized content on cached pages
The correct default is: start without any client:* directive. Add one only when the component truly needs browser interactivity.
.astro File Anatomy
---
// Component Script — runs at build time (SSG) or request time (SSR)
// Top-level await works here
import Component from '../components/Component.astro';
const data = await fetch('https://api.example.com/data').then(r => r.json());
interface Props { title: string; }
const { title } = Astro.props;
---
<!-- Template — HTML + {expressions}, multiple root elements OK -->
<h1>{title}</h1>
<Component />
{data.items.map(item => <p>{item.name}</p>)}
<style>
/* Scoped to this component by default */
h1 { color: blue; }
</style>
Key differences from JSX/React:
{variable}not{{ }}for expressions- HTML attribute names, not camelCase (e.g.,
classnotclassName) - Multiple root elements allowed — no wrapper needed
set:html={rawHtml}to inject HTML (use carefully — XSS risk)
When to Use Each Client Directive
| Directive | When to use |
|---|---|
client:load | Critical interactive elements above the fold (search bar, nav menu) |
client:idle | Non-critical widgets that can wait (chat bubble, cookie banner) |
client:visible | Heavy components the user may never scroll to (complex charts, carousels) |
client:media="(query)" | Responsive-only components |
client:only="react" | Components that use window or localStorage directly |
Prefer client:visible over client:load for below-fold content — it's a free performance win.
Key Patterns to Follow
Content Collections (Astro 5 style)
Define in src/content.config.ts (not src/content/config.ts):
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
publishDate: z.date(),
draft: z.boolean().default(false),
tags: z.array(z.string()).optional(),
}),
});
export const collections = { blog };
Always define schemas with Zod — this catches frontmatter errors at build time, not in production.
Blog Page with Dynamic Routes (SSG)
---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog', ({ data }) => !data.draft);
return posts.map(post => ({
params: { slug: post.id },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<h1>{post.data.title}</h1>
<Content />
SSR: On-Demand Rendering
In Astro 5, hybrid mode is gone. Use output: 'static' (default) + opt specific pages into SSR:
---
// src/pages/dashboard.astro
export const prerender = false; // This page renders on-demand
const user = Astro.locals.user;
if (!user) return Astro.redirect('/login');
---
Or flip the whole project to output: 'server' and opt specific pages into static:
export const prerender = true;
Layouts
---
// src/layouts/Base.astro
interface Props { title: string; description?: string; }
const { title, description = '' } = Astro.props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
{description && <meta name="description" content={description} />}
</head>
<body>
<slot />
</body>
</html>
Astro 6 — Critical New Features and Breaking Changes
Always check which version the user is on. Astro 6 introduced significant breaking changes from v5.
Breaking Changes (v5 → v6)
| Feature | v5 | v6 |
|---|---|---|
| Zod import | import { z } from 'astro:content' | import { z } from 'astro/zod' |
| Zod string validators | z.string().email(), z.string().url() | z.email(), z.url() (Zod 4 top-level) |
Astro.glob() | Deprecated | Removed — must use import.meta.glob() |
| Legacy content collections | legacy.collections flag available | Removed — Content Layer API required |
<ViewTransitions /> | Deprecated | Removed — use <ClientRouter /> |
| CJS config files | .cjs/.cts allowed | Removed — use .mjs/.ts |
| Node.js | 18+ | 22.12.0+ required |
| Vite | v5 | v7 |
i18n redirectToDefaultLocale | true | false (default changed) |
New in Astro 6
Live Content Collections — fetch data at request time (not just build time):
// src/live.config.ts ← NEW file (separate from src/content.config.ts)
import { defineLiveCollection } from 'astro:content';
export const collections = {
products: defineLiveCollection({
loader: async ({ filter }) => {
const res = await fetch('https://api.store.com/products');
return res.json();
},
}),
};
Query in server-rendered pages:
---
export const prerender = false; // Required — live collections only work in SSR
import { getLiveCollection, getLiveEntry } from 'astro:content';
const { entries: products, error } = await getLiveCollection('products');
const { entry: product } = await getLiveEntry('products', Astro.params.slug);
---
Built-in Fonts API — serve fonts from your own domain (no CDN at runtime):
// astro.config.mjs — fontProviders is imported from 'astro/config'
import { defineConfig, fontProviders } from 'astro/config';
export default defineConfig({
fonts: [{
provider: fontProviders.google(), // or fontsource(), local(), adobe(), etc.
name: 'Inter',
cssVariable: '--font-inter',
}],
});
Then in layouts — <Font /> is from astro:assets:
---
import { Font } from 'astro:assets'; // NOT astro:fonts
---
<head>
<Font cssVariable="--font-inter" />
</head>
Content Security Policy (CSP) — built-in auto-hashing:
export default defineConfig({
security: {
csp: {
algorithm: 'SHA-256',
directives: ["default-src 'self'", "img-src *"],
},
},
});
Note: CSP is not supported in dev mode, and is incompatible with View Transitions and Shiki syntax highlighting.
Experimental: Route Caching (experimental.routeCaching) — cache SSR responses:
export default defineConfig({
experimental: {
routeCaching: { provider: memoryCache() },
},
});
Access in pages: const cached = await Astro.cache.get('key', { maxAge: 3600 }).
Astro 5 vs Earlier Versions — Critical Differences
If the user has code that doesn't work, check these common migration gotchas:
| Feature | Old (v4) | New (v5) |
|---|---|---|
| Collection config location | src/content/config.ts | src/content.config.ts |
| ViewTransitions component | <ViewTransitions /> | <ClientRouter /> (removed in v6) |
| Hybrid output mode | output: 'hybrid' | Use output: 'static' + prerender = false |
| Image service | Squoosh | Sharp only (Squoosh removed) |
Astro.glob() | Available | Deprecated in v5, removed in v6 |
| CSRF protection | Opt-in | Default-on |
compiledContent() | Sync | async — requires await |
Astro Configuration Reference
// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
export default defineConfig({
site: 'https://example.com',
base: '/', // base path for deployment subdirectory
output: 'static', // 'static' | 'server'
integrations: [react(), tailwind()],
image: {
domains: ['images.example.com'],
},
vite: {
// Pass-through to Vite config
},
});
Image Best Practices
Always prefer <Image /> over raw <img> for local images — it prevents layout shift and converts to modern formats:
---
import { Image } from 'astro:assets';
import hero from '../assets/hero.jpg';
---
<Image src={hero} alt="Hero" width={1200} height={600} format="webp" />
For multiple breakpoints/formats use <Picture />.
Adding Integrations via CLI
npx astro add react # React integration
npx astro add vue # Vue integration
npx astro add svelte # Svelte integration
npx astro add tailwind # Tailwind CSS
npx astro add vercel # Vercel adapter (enables SSR)
npx astro add netlify # Netlify adapter
npx astro add cloudflare # Cloudflare adapter
npx astro add node # Node.js adapter
npx astro add sitemap # Auto-generate sitemap.xml
npx astro add mdx # MDX support
astro add automatically installs the package, updates astro.config.mjs, and adds any required tsconfig changes.
Debugging Checklist
When something doesn't work:
- Component not interactive? — Check you have a
client:*directive. Without it, zero JS is sent. - Dynamic route 404? — In static mode,
getStaticPaths()must return the route. In server mode, you don't need it. - Collection not found? — Check config is in
src/content.config.ts(Astro 5) and runnpx astro sync. - Import errors? — Framework components (
.jsx,.vue,.svelte) need their integration installed viaastro add. window is not defined? — You're using a browser API in server-side code. Useclient:onlyor guard withtypeof window !== 'undefined'.- Type errors in .astro files? — Run
npx astro checkto see them. Runnpx astro syncto regenerate types.