Skip to content
astro logo

Astro

astro

Builds content-focused websites with Astro using islands architecture, content collections, and multi-framework support. Use when creating static sites, blogs, documentation, marketing pages, or content-heavy applications with minimal JavaScript.

SKILL.md

Full skill instructions

Astro

Content-focused web framework with islands architecture for building fast static and server-rendered websites with minimal JavaScript.

Quick Start

Create new project:

npm create astro@latest my-site
cd my-site
npm run dev

Essential file structure:

src/
  pages/              # File-based routing
    index.astro       # Home page (/)
    about.astro       # /​about
    blog/
      [slug].astro    # /​blog/:slug
  components/         # Reusable components
  layouts/            # Page layouts
  content/            # Content collections
    blog/             # Blog posts collection
  styles/             # Global styles
public/               # Static assets
astro.config.mjs      # Astro configuration

Astro Components

Basic Syntax

---
// Component Script (runs at build time)
import Header from '../​components/​Header.astro';
import Button from '../​components/​Button.tsx';

interface Props {
  title: string;
  description?: string;
}

const { title, description = 'Default description' } = Astro.props;

// Fetch data at build time
const response = await fetch('https://api.example.com/​data');
const data = await response.json();
---

<!-- Component Template -->
<html lang="en">
  <head>
    <title>{title}</​title>
    <meta name="description" content={description} />
  </​head>
  <body>
    <Header />
    <main>
      <h1>{title}</​h1>
      <ul>
        {data.items.map((item) => (
          <li>{item.name}</​li>
        ))}
      </​ul>
      <!-- Interactive island -->
      <Button client:load>Click me</​Button>
    </​main>
  </​body>
</​html>

<style>
  /​* Scoped CSS by default */
  h1 {
    color: navy;
    font-size: 2rem;
  }
</​style>

Props and Types

---
interface Props {
  title: string;
  tags: string[];
  publishDate: Date;
  featured?: boolean;
}

const { title, tags, publishDate, featured = false } = Astro.props;
---

<article class:list={['post', { featured }]}>
  <h2>{title}</​h2>
  <time datetime={publishDate.toISOString()}>
    {publishDate.toLocaleDateString()}
  </​time>
  <ul>
    {tags.map((tag) => <li>{tag}</​li>)}
  </​ul>
</​article>

Slots

---
// Card.astro
interface Props {
  title: string;
}
const { title } = Astro.props;
---

<div class="card">
  <header>
    <slot name="header">{title}</​slot>
  </​header>
  <main>
    <slot />  <!-- Default slot -->
  </​main>
  <footer>
    <slot name="footer">Default footer</​slot>
  </​footer>
</​div>

Using slots:

<Card title="My Card">
  <h3 slot="header">Custom Header</​h3>
  <p>Main content goes here</​p>
  <button slot="footer">Action</​button>
</​Card>

Islands Architecture

Client Directives

Components are static by default. Add client:* directives for interactivity:

DirectiveWhen JavaScript Loads
client:loadImmediately on page load
client:idleWhen browser becomes idle
client:visibleWhen component enters viewport
client:mediaWhen media query matches
client:onlySkip SSR, client render only
---
import Counter from '../​components/​Counter.tsx';
import Newsletter from '../​components/​Newsletter.vue';
import Comments from '../​components/​Comments.svelte';
---

<!-- Load immediately (above fold, critical) -->
<Counter client:load />

<!-- Load when idle (non-critical) -->
<Newsletter client:idle />

<!-- Load when visible (below fold) -->
<Comments client:visible />

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

<!-- React-only, no SSR -->
<ReactChart client:only="react" />

Framework Integrations

# Add React
npx astro add react

# Add Vue
npx astro add vue

# Add Svelte
npx astro add svelte

# Add SolidJS
npx astro add solid

Using multiple frameworks:

---
import ReactComponent from '../​components/​ReactComponent.tsx';
import VueComponent from '../​components/​VueComponent.vue';
import SvelteComponent from '../​components/​SvelteComponent.svelte';
---

<ReactComponent client:load />
<VueComponent client:visible />
<SvelteComponent client:idle />

File-Based Routing

Static Routes

src/​pages/
  index.astro          # /
  about.astro          # /​about
  contact.astro        # /​contact
  blog/
    index.astro        # /​blog
    first-post.astro   # /​blog/​first-post

Dynamic Routes

---
// src/​pages/​blog/[slug].astro
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await post.render();
---

<article>
  <h1>{post.data.title}</​h1>
  <Content />
</​article>

Rest Parameters

---
// src/​pages/​docs/[...slug].astro
// Matches /​docs, /​docs/​intro, /​docs/​guides/​getting-started

export function getStaticPaths() {
  return [
    { params: { slug: undefined } },  // /​docs
    { params: { slug: 'intro' } },     // /​docs/​intro
    { params: { slug: 'guides/​start' } }, // /​docs/​guides/​start
  ];
}

const { slug } = Astro.params;
---

Content Collections

Define Collections

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

const blog = defineCollection({
  loader: glob({ pattern: '**/​*.md', base: './​src/​content/​blog' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    heroImage: z.string().optional(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});

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

export const collections = { blog, authors };

Query Collections

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

// Get all published posts
const allPosts = await getCollection('blog', ({ data }) => {
  return data.draft !== true;
});

// Sort by date
const sortedPosts = allPosts.sort(
  (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);

// Get single entry
const featuredPost = await getEntry('blog', 'featured-post');
---

<ul>
  {sortedPosts.map((post) => (
    <li>
      <a href={`/​blog/​${post.slug}`}>{post.data.title}</​a>
    </​li>
  ))}
</​ul>

Render Content

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

const post = await getEntry('blog', 'my-post');
const { Content, headings } = await post.render();
---

<article>
  <h1>{post.data.title}</​h1>
  <nav>
    <h2>Table of Contents</​h2>
    <ul>
      {headings.map((h) => (
        <li>
          <a href={`#${h.slug}`}>{h.text}</​a>
        </​li>
      ))}
    </​ul>
  </​nav>
  <Content />
</​article>

Layouts

Basic Layout

---
// src/​layouts/​BaseLayout.astro
interface Props {
  title: string;
  description?: string;
}

const { title, description = 'My Astro site' } = Astro.props;
---

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <meta name="description" content={description} />
    <title>{title}</​title>
  </​head>
  <body>
    <header>
      <nav><!-- Navigation --></​nav>
    </​header>
    <main>
      <slot />
    </​main>
    <footer><!-- Footer --></​footer>
  </​body>
</​html>

Using layouts:

---
import BaseLayout from '../​layouts/​BaseLayout.astro';
---

<BaseLayout title="Home">
  <h1>Welcome!</​h1>
  <p>This is the home page.</​p>
</​BaseLayout>

Markdown Layout

---
// src/​layouts/​BlogPost.astro
import BaseLayout from './​BaseLayout.astro';
import { type CollectionEntry } from 'astro:content';

interface Props {
  post: CollectionEntry<'blog'>;
}

const { post } = Astro.props;
const { title, pubDate, heroImage } = post.data;
---

<BaseLayout title={title}>
  <article>
    {heroImage && <img src={heroImage} alt="" />}
    <h1>{title}</​h1>
    <time datetime={pubDate.toISOString()}>
      {pubDate.toLocaleDateString()}
    </​time>
    <slot />
  </​article>
</​BaseLayout>

Server-Side Rendering

Enable SSR

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

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

Server Endpoints

// src/​pages/​api/​posts.json.ts
import type { APIRoute } from 'astro';

export const GET: APIRoute = async ({ request }) => {
  const posts = await getPosts();
  return new Response(JSON.stringify(posts), {
    headers: { 'Content-Type': 'application/​json' },
  });
};

export const POST: APIRoute = async ({ request }) => {
  const data = await request.json();
  const post = await createPost(data);
  return new Response(JSON.stringify(post), {
    status: 201,
    headers: { 'Content-Type': 'application/​json' },
  });
};

Hybrid Rendering

// astro.config.mjs
export default defineConfig({
  output: 'hybrid', // Static by default, opt-in to SSR
});
---
// This page renders on each request
export const prerender = false;

const user = await getUser(Astro.cookies.get('session'));
---

Styling

Scoped Styles

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

Global Styles

<style is:global>
  /​* Applies globally */
  body {
    font-family: sans-serif;
  }
</​style>

CSS Variables

---
const { color = 'blue' } = Astro.props;
---

<div class="box">Content</​div>

<style define:vars={{ color }}>
  .box {
    background-color: var(--color);
  }
</​style>

Tailwind CSS

npx astro add tailwind
<div class="flex items-center justify-between p-4 bg-blue-500">
  <h1 class="text-2xl font-bold text-white">Hello</​h1>
</​div>

View Transitions

---
import { ViewTransitions } from 'astro:transitions';
---

<html>
  <head>
    <ViewTransitions />
  </​head>
  <body>
    <header transition:persist>
      <!-- Persists across page navigations -->
    </​header>
    <main transition:animate="slide">
      <slot />
    </​main>
  </​body>
</​html>

Custom transitions:

<div transition:name="hero" transition:animate="fade">
  <img src={heroImage} alt="" />
</​div>

Image Optimization

---
import { Image } from 'astro:assets';
import heroImage from '../​assets/​hero.png';
---

<!-- Optimized image -->
<Image src={heroImage} alt="Hero" />

<!-- With dimensions -->
<Image src={heroImage} alt="Hero" width={800} height={600} />

<!-- Remote image -->
<Image
  src="https://example.com/​image.jpg"
  alt="Remote"
  width={400}
  height={300}
/>

Environment Variables

# .env
PUBLIC_API_URL=https://api.example.com
SECRET_KEY=abc123
---
// Server-side (secret)
const secret = import.meta.env.SECRET_KEY;

// Client-side (public)
const apiUrl = import.meta.env.PUBLIC_API_URL;
---

Best Practices

  1. Default to static - Only add interactivity where needed
  2. Use content collections - For any structured content
  3. Lazy load islands - Use client:visible for below-fold content
  4. Colocate styles - Use scoped styles in components
  5. Optimize images - Use astro:assets for automatic optimization

Common Mistakes

MistakeFix
Adding client:* everywhereOnly for truly interactive components
Large client bundlesSplit into smaller islands
Not using content collectionsFor blogs, docs, use collections
Fetching in client componentsFetch in Astro component script
Ignoring getStaticPathsRequired for dynamic routes

Reference Files

Templates

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View