Astro Operations

SkillCloud & infra

Once added, this skill gives your AI working knowledge of Astro, a framework for building websites. Your AI can then help you build Astro sites, organize their content, and choose how each page is rendered and deployed.

Available today. Use it from your connected AI after setup.

After adding it, describe your Astro project to your AI and ask for help with page structure, rendering choices, or deployment.

Then ask your AI: use the Astro Operations skill

What your AI can do with it

  • Build Astro sites using established framework patterns
  • Organize site content with content collections
  • Apply islands architecture and partial hydration to pages
  • Choose the right rendering strategy for each page: static, SSR, or hybrid
  • Add view transitions between pages
  • Prepare projects for deployment, including to Cloudflare with the right Astro adapter

What this skill tells your AI

The instructions your AI receives, as published by 0xdarkmatter/claude-mods in skills/astro-ops/SKILL.md and read by ahel’s review.

Facts verified as of 2026-07.

Comprehensive patterns for Astro framework development: islands architecture, content collections, rendering strategies, view transitions, and multi-platform deployment.

Rendering Strategy Decision Tree

Which rendering strategy?
│
├─ Is content mostly static (blog, docs, marketing)?
│  ├─ YES → Does it change less than daily?
│  │  ├─ YES → SSG (output: 'static')
│  │  │        Fastest TTFB, CDN-cacheable, zero runtime cost
│  │  └─ NO  → Hybrid (output: 'hybrid')
│  │           Default static + opt-in SSR per route
│  └─ NO  → Does every page need personalization?
│     ├─ YES → SSR (output: 'server')
│     │        Dynamic per-request, auth-aware, real-time data
│     └─ NO  → Hybrid (output: 'hybrid')
│              Static shell + server islands for dynamic parts
│
├─ Does the app need real-time interactivity (dashboard, SPA)?
│  ├─ YES → Is it a full SPA with client-side routing?
│  │  ├─ YES → Consider React/Vue SPA instead, or Astro + client:only
│  │  └─ NO  → Hybrid + islands architecture
│  │           Interactive islands in static pages
│  └─ NO  → SSG (output: 'static')
│
├─ Build time concerns (>10k pages)?
│  ├─ YES → Hybrid with on-demand rendering
│  │        Prerender popular pages, SSR the long tail
│  └─ NO  → SSG handles it fine
│
└─ Need edge computing (low latency globally)?
   ├─ YES → SSR + Cloudflare/Vercel Edge adapter
   └─ NO  → SSR + Node adapter or SSG

Configuration

// astro.config.mjs
import { defineConfig } from 'astro/config';

// SSG (default) - all pages prerendered at build time
export default defineConfig({
  output: 'static',
});

// SSR - all pages rendered on request
export default defineConfig({
  output: 'server',
  adapter: cloudflare(), // or vercel(), netlify(), node()
});

// Hybrid - static default, opt-in SSR per page
export default defineConfig({
  output: 'hybrid',
  adapter: cloudflare(),
});
---
// In hybrid mode, opt OUT of prerendering for specific pages:
export const prerender = false;
// In SSR mode, opt IN to prerendering:
export const prerender = true;
---

Islands Architecture Quick Reference

DirectiveHydrates WhenJS ShippedUse Case
client:loadImmediately on page loadFull bundleAbove-fold interactive (nav, hero CTA)
client:idleAfter page is idle (requestIdleCallback)Full bundleBelow-fold interactive (comment form, chat)
client:visibleWhen scrolled into viewportFull bundleFar-down-page (footer widget, carousel)
client:mediaWhen media query matchesFull bundleMobile-only nav, responsive components
client:only="react"Immediately, skip SSR entirelyFull bundleComponents that can't SSR (canvas, WebGL)
(none)Never - static HTML onlyZero JSStatic content, cards, headers
---
import NavBar from '../components/NavBar.tsx';
import CommentForm from '../components/CommentForm.tsx';
import ImageCarousel from '../components/ImageCarousel.svelte';
import MobileMenu from '../components/MobileMenu.vue';
import ThreeScene from '../components/ThreeScene.tsx';
---

<!-- Loads immediately - critical interactivity -->
<NavBar client:load />

<!-- Loads after page is idle - non-critical -->
<CommentForm client:idle />

<!-- Loads when scrolled into view - lazy -->
<ImageCarousel client:visible />

<!-- Loads only on mobile -->
<MobileMenu client:media="(max-width: 768px)" />

<!-- Client-only, no SSR (WebGL can't run on server) -->
<ThreeScene client:only="react" />

Content Collections Quick Start

Define Schema

// src/content.config.ts (Astro 5) or src/content/config.ts (Astro 4)
import { defineCollection, z, reference } 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(),
    description: z.string().max(160),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    heroImage: z.string().optional(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
    author: reference('authors'), // Reference another collection
  }),
});

const authors = defineCollection({
  loader: glob({ pattern: '**/*.json', base: './src/content/authors' }),
  schema: z.object({
    name: z.string(),
    avatar: z.string(),
    bio: z.string(),
    socials: z.object({
      twitter: z.string().optional(),
      github: z.string().optional(),
    }).optional(),
  }),
});

export const collections = { blog, authors };

Query Collections

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

// Get all non-draft blog posts, sorted by date
const posts = (await getCollection('blog', ({ data }) => !data.draft))
  .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

// Get a single entry
const post = await getEntry('blog', 'my-first-post');

// Resolve a reference
const author = await getEntry(post.data.author);

// Render content
const { Content, headings } = await post.render();
---

<Content />

Content Collections vs External CMS

CriterionContent CollectionsExternal CMS (Payload, etc.)
Content typeLocal markdown/MDX, docs, blogsRelational data models
AuthorsDevelopers (version-controlled)Editors (admin UI, multi-user auth)
ValidationType-safe via Zod at build timeCMS-side schemas + API contracts
Update cadenceDeploys with the siteIndependent of deployments
API needsNone (build-time queries)REST/GraphQL for other consumers
WorkflowGit PRs, simple reviewEditorial workflows, drafts, roles

Rule of thumb: start with Content Collections; reach for a CMS only when non-developers need to publish without a deploy, or when content is genuinely relational.

Project Structure Reference

project-root/
├── astro.config.mjs          # Astro configuration
├── tsconfig.json              # TypeScript config (extends astro/tsconfigs)
├── package.json
├── public/                    # Static assets (copied as-is)
│   ├── favicon.svg
│   ├── robots.txt
│   └── og-image.png
├── src/
│   ├── pages/                 # File-based routing
│   │   ├── index.astro        # → /
│   │   ├── about.astro        # → /about
│   │   ├── blog/
│   │   │   ├── index.astro    # → /blog
│   │   │   └── [slug].astro   # → /blog/:slug (dynamic)
│   │   ├── api/
│   │   │   └── search.ts      # → /api/search (API endpoint)
│   │   └── [...slug].astro    # → catch-all/404
│   ├── layouts/
│   │   ├── BaseLayout.astro   # HTML shell, <head>, global styles
│   │   └── BlogPost.astro     # Blog post layout
│   ├── components/
│   │   ├── Header.astro       # Static Astro component
│   │   ├── Footer.astro
│   │   ├── NavBar.tsx         # React island
│   │   └── Counter.svelte     # Svelte island
│   ├── content/               # Content collections source files
│   │   ├── blog/
│   │   │   ├── post-one.md
│   │   │   └── post-two.mdx
│   │   └── authors/
│   │       └── jane.json
│   ├── content.config.ts      # Collection schemas (Astro 5)
│   ├── middleware.ts           # Request/response middleware
│   ├── styles/
│   │   └── global.css
│   └── lib/                   # Shared utilities
│       ├── utils.ts
│       └── constants.ts
└── .env                       # Environment variables

View Transitions Quick Reference

---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
---

<html>
  <head>
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>

Transition Directives

<!-- Persist element across pages (keeps state, avoids re-render) -->
<audio transition:persist id="player">
  <source src="/music.mp3" />
</audio>

<!-- Named transition for animation pairing -->
<img transition:name="hero" src={post.heroImage} />

<!-- Custom animation -->
<div transition:animate="slide">Content</div>
<div transition:animate="fade">Content</div>
<div transition:animate="none">No animation</div>

<!-- Persist with name (for multiple persistent elements) -->
<video transition:persist="media-player" />

Lifecycle Events

<script>
  document.addEventListener('astro:before-preparation', (e) => {
    // Before new page is fetched - cancel navigation, show loading
  });

  document.addEventListener('astro:after-preparation', (e) => {
    // New page fetched, before swap
  });

  document.addEventListener('astro:before-swap', (e) => {
    // Customize DOM swap behavior
  });

  document.addEventListener('astro:after-swap', () => {
    // DOM updated - reinitialize scripts
  });

  document.addEventListener('astro:page-load', () => {
    // Page fully loaded (fires on initial + every navigation)
    // Use this instead of DOMContentLoaded with View Transitions
  });
</script>

Back/Forward Handling

// astro.config.mjs
export default defineConfig({
  prefetch: {
    prefetchAll: true,         // Prefetch all links on hover
    defaultStrategy: 'hover',  // 'hover' | 'tap' | 'viewport' | 'load'
  },
});
<!-- Per-link prefetch control -->
<a href="/about" data-astro-prefetch>Prefetch on hover (default)</a>
<a href="/blog" data-astro-prefetch="viewport">Prefetch when visible</a>
<a href="/contact" data-astro-prefetch="load">Prefetch immediately</a>
<a href="/external" data-astro-prefetch="false">No prefetch</a>

Deployment Decision Tree

Where to deploy?
│
├─ Need edge computing + Cloudflare ecosystem (KV, D1, R2)?
│  └─ Cloudflare Pages/Workers
│     Adapter: @astrojs/cloudflare
│     Best for: Global edge, Workers bindings, cost-effective
│
├─ Need serverless + Vercel ecosystem (ISR, analytics)?
│  └─ Vercel
│     Adapter: @astrojs/vercel
│     Best for: Next.js migration, image optimization, ISR
│
├─ Need serverless + Netlify ecosystem (forms, identity)?
│  └─ Netlify
│     Adapter: @astrojs/netlify
│     Best for: JAMstack, built-in forms, split testing
│
├─ Need full server control (Docker, custom runtime)?
│  └─ Node.js (standalone or Express/Fastify)
│     Adapter: @astrojs/node
│     Best for: Self-hosted, WebSocket, long-running processes
│
└─ Pure static site (no SSR needed)?
   └─ Any static host (GitHub Pages, S3, Cloudflare Pages)
      No adapter needed, output: 'static'
      Best for: Blogs, docs, marketing sites

Adapter Installation

# Cloudflare
npx astro add cloudflare

# Vercel
npx astro add vercel

# Netlify
npx astro add netlify

# Node.js
npx astro add node

Common Gotchas

GotchaWhyFix
Hydration mismatch errorsServer HTML differs from client render (dates, random IDs, browser APIs)Use client:only for browser-dependent components, or ensure deterministic rendering
import.meta.env undefined in clientOnly PUBLIC_ prefixed vars are exposed to client-side codeRename to PUBLIC_MY_VAR or pass via props from server
Dynamic routes 404 in SSGgetStaticPaths() not returning all possible paramsEnsure getStaticPaths() returns every valid path, or switch to hybrid/SSR
Images not optimizingUsing <img> instead of Astro's <Image /> componentImport from astro:assets: import { Image } from 'astro:assets' and use local imports for src
SSR fails without adapteroutput: 'server' or 'hybrid' requires a deployment adapterInstall adapter: npx astro add cloudflare (or vercel, netlify, node)
MDX components not renderingCustom components not passed to MDX contentPass components via <Content components={{ MyComponent }} /> or use astro.config.mjs MDX config
Content collection schema changes not reflectedType generation is cached, stale .astro typesRun astro sync to regenerate types, restart dev server
client:* on Astro componentsClient directives only work on framework components (React, Vue, Svelte)Astro components are static-only; extract interactive parts to a framework component
document / window is not definedServer-side code cannot access browser globalsGuard with if (typeof window !== 'undefined') or move to client:only
Styles leaking between componentsUsing global CSS instead of scoped stylesUse <style> (scoped by default in .astro) or <style is:global> intentionally
View Transitions break scriptsDOMContentLoaded only fires once with View TransitionsUse astro:page-load event instead, which fires on every navigation
Env vars missing in production.env not loaded or platform env vars not configuredUse envField in astro.config.mjs for validation; set vars in platform dashboard

Production Security Checklist

For every production deployment, address:

  • CSP headers - configure a restrictive Content-Security-Policy (see middleware patterns in references/deployment.md)
  • Remote image restrictions - enforce explicit image.domains / remotePatterns allow-lists; never derive image URLs from user input (SSRF risk)
  • Host header validation - verify the request host matches expected domains in middleware (SSR/hybrid only)
  • Secrets management - on Cloudflare, use Workers Bindings (wrangler secret put), not env vars baked into code; elsewhere use platform secret stores
  • HTTPS only - ensure all external resources (scripts, images, fonts) load over HTTPS
  • Input validation - sanitize all user input in SSR contexts (query params, form bodies, cookies)

Reference Files

FileContentsLines
references/content-collections.mdSchema patterns, Zod types, querying, MDX, content layer API, migrations~500
references/islands-rendering.mdIslands deep dive, client directives, framework integration, server islands~550
references/deployment.mdCloudflare/Vercel/Netlify/Node adapters, env vars, optimization~500

See Also

  • typescript-ops - TypeScript patterns used throughout Astro projects
  • tailwind-ops - Tailwind CSS integration with Astro (@astrojs/tailwind)
  • javascript-ops - Core JS patterns for client-side island code
  • container-orchestration - Docker patterns for self-hosted Astro (Node adapter)
  • Astro Documentation
  • Astro Integration Guide

Signals

GitHub stars
36
Forks
5
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
astro-ops
Source
github.com/0xdarkmatter/claude-mods