skills/migueldialpad/skills/astro-framework

astro-framework

SKILL.md

Astro Framework

Astro is a web framework for building content-driven websites. Ships zero JavaScript by default using Islands Architecture.

Quick Start

# Create new project (choose your package manager)
npm create astro@latest
pnpm create astro@latest
bun create astro@latest

# Add integrations
npx astro add react tailwind
bunx astro add react tailwind

Package Manager Commands

Task npm bun
Create project npm create astro@latest bun create astro@latest
Dev server npm run dev bun dev
Build npm run build bun run build
Preview npm run preview bun preview
Add integration npx astro add <name> bunx astro add <name>
Check types npx astro check bunx astro check

Project Structure

src/
├── pages/          # File-based routing (REQUIRED)
├── components/     # Reusable .astro components
├── layouts/        # Page templates with <slot/>
├── content/        # Content collections
├── styles/         # CSS/SCSS files
├── assets/         # Optimized images
public/             # Static assets (unprocessed)
astro.config.mjs    # Configuration
content.config.ts   # Collection schemas

Component Anatomy

---
// Component Script (runs on server, never sent to client)
import Header from './Header.astro';
import { getCollection } from 'astro:content';

interface Props {
  title: string;
  count?: number;
}

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

<!-- Component Template -->
<Header />
<h1>{title}</h1>
<ul>
  {posts.map(post => <li>{post.data.title}</li>)}
</ul>

<style>
  /* Scoped by default */
  h1 { color: blue; }
</style>

<style is:global>
  /* Global styles */
</style>

Pages & Routing

File-based routing from src/pages/:

File Route
index.astro /
about.astro /about
blog/[slug].astro /blog/:slug
[...path].astro catch-all

Dynamic Routes (SSG)

---
// src/pages/posts/[slug].astro
export function getStaticPaths() {
  return [
    { params: { slug: 'post-1' }, props: { title: 'First' } },
    { params: { slug: 'post-2' }, props: { title: 'Second' } },
  ];
}

const { slug } = Astro.params;
const { title } = Astro.props;
---
<h1>{title}</h1>

Layouts

---
// src/layouts/BaseLayout.astro
interface Props {
  title: string;
}
const { title } = Astro.props;
---
<html lang="en">
  <head>
    <title>{title}</title>
  </head>
  <body>
    <slot /> <!-- Child content injected here -->
  </body>
</html>

Usage:

---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="Home">
  <h1>Welcome</h1>
</BaseLayout>

Content Collections

Define in src/content.config.ts:

import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';

const blog = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
  schema: z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
    draft: z.boolean().optional(),
  }),
});

export const collections = { blog };

Query collections:

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

const posts = await getCollection('blog', ({ data }) => !data.draft);
const post = await getEntry('blog', 'my-post');
const { Content } = await render(post);
---
<Content />

UI Framework Islands

Add framework support:

# npm
npx astro add react    # React 19 support
npx astro add vue      # Vue 3
npx astro add svelte   # Svelte 5
npx astro add solid    # SolidJS
npx astro add preact   # Preact

# bun
bunx astro add react vue svelte  # Can add multiple at once

Use client:* directives for hydration:

---
import Counter from './Counter.jsx';
---
<!-- Static by default (no JS) -->
<Counter />

<!-- Hydrated on page load -->
<Counter client:load />

<!-- Hydrated when visible -->
<Counter client:visible />

<!-- Hydrated on idle -->
<Counter client:idle />

<!-- Hydrated on media query -->
<Counter client:media="(max-width: 768px)" />

Server-Side Rendering (SSR)

Enable on-demand rendering:

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

export default defineConfig({
  output: 'server', // or 'static' (default)
  adapter: node({ mode: 'standalone' }),
});

Per-page opt-in/out:

---
export const prerender = false; // Render on-demand
// export const prerender = true; // Pre-render at build
---

Access request data:

---
const cookie = Astro.cookies.get('session');
const url = Astro.url;
const headers = Astro.request.headers;
---

Server Islands

Defer component rendering until content is ready:

---
import Avatar from './Avatar.astro';
---
<Avatar server:defer>
  <div slot="fallback">Loading...</div>
</Avatar>

Actions

Type-safe backend functions in src/actions/index.ts:

import { defineAction, ActionError } from 'astro:actions';
import { z } from 'astro/zod';

export const server = {
  subscribe: defineAction({
    accept: 'form',
    input: z.object({
      email: z.string().email(),
    }),
    handler: async ({ email }, ctx) => {
      if (!ctx.cookies.has('session')) {
        throw new ActionError({ code: 'UNAUTHORIZED' });
      }
      // Process subscription
      return { success: true };
    },
  }),
};

Call from client:

<script>
import { actions } from 'astro:actions';

const { data, error } = await actions.subscribe({ email: 'test@example.com' });
</script>

View Transitions

Enable SPA-like navigation:

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

<!-- Animate elements -->
<div transition:animate="slide">
<img transition:name="hero" transition:persist />

Images

---
import { Image, Picture } from 'astro:assets';
import myImage from '../assets/hero.png';
---
<Image src={myImage} alt="Hero" />
<Image src="/public-image.jpg" alt="Public" width={800} height={600} />
<Picture src={myImage} formats={['avif', 'webp']} alt="Hero" />

Styling

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

<style is:global>
  /* Global styles */
</style>

<style define:vars={{ color: 'blue' }}>
  h1 { color: var(--color); }
</style>

Tailwind CSS v4

npx astro add tailwind
bunx astro add tailwind

MDX Support

npx astro add mdx
bunx astro add mdx

Then use .mdx files in content collections or pages:

---
title: My Post
---
import MyComponent from '../components/MyComponent.astro';

# Hello {props.title}

<MyComponent client:load />

Configuration

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

export default defineConfig({
  site: 'https://example.com',
  base: '/docs',
  output: 'static', // 'static' | 'server'
  trailingSlash: 'always', // 'always' | 'never' | 'ignore'
  integrations: [],
  vite: {},
});

Common Patterns

API Endpoints

// src/pages/api/users.ts
import type { APIRoute } from 'astro';

export const GET: APIRoute = async ({ params, request }) => {
  return new Response(JSON.stringify({ users: [] }), {
    headers: { 'Content-Type': 'application/json' },
  });
};

export const POST: APIRoute = async ({ request }) => {
  const data = await request.json();
  return new Response(JSON.stringify({ success: true }));
};

Data Fetching

---
const response = await fetch('https://api.example.com/data');
const data = await response.json();
---

Error Pages

Create src/pages/404.astro and src/pages/500.astro.

Redirects

// astro.config.mjs
export default defineConfig({
  redirects: {
    '/old': '/new',
    '/blog/[...slug]': '/articles/[...slug]',
  },
});

Common Integrations

# All-in-one: React + Tailwind + MDX
bunx astro add react tailwind mdx

# SSR adapters
bunx astro add node       # Node.js standalone
bunx astro add vercel     # Vercel
bunx astro add netlify    # Netlify
bunx astro add cloudflare # Cloudflare Workers

Key References

Additional Resources

Weekly Installs
3
First Seen
2 days ago
Installed on
opencode3
gemini-cli3
claude-code3
codex3
cursor3
github-copilot2