spillwavesolutions/publishing-astro-websites-agentic-skill · Archived

publishing-astro-websites

Comprehensive guidance for building and deploying static websites with the Astro framework. This skill should be used when asked to "create astro site", "deploy astro to firebase", "set up content collections", "add mermaid diagrams to astro", "configure astro i18n", "build static blog", or "astro markdown setup". Covers SSG fundamentals, Content Collections, Markdown/MDX, partial hydration, islands architecture, and deployment to Netlify, Vercel, GitHub Pages, or GCP/Firebase.

First seen Feb 11, 2026

Installation

$ npx skills add spillwavesolutions/publishing-astro-websites-agentic-skill --skill publishing-astro-websites

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 31
License MIT
Default branch main
Open issues 3
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.0.0
LicenseMIT
Declared agents claude-code
More metadata
version
1.0.0
category
web-development
author
Claude Code Skills
triggers
["astro","astro website","astro static site","astro content collections","astro deployment","astro firebase","astro mermaid","starlight","build astro site"]
tags
["astro","static-site-generation","markdown","deployment"]

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 29,789 B
  • docs SUMMARY.md 515 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 52 installs

SKILL.md

Publishing Astro Websites

Build fast, content-driven static websites with Astro's zero-runtime SSG approach, partial hydration, and extensive Markdown support.

Contents

  • [Quick Start](#quick-start)
  • [When Not to Use](#when-not-to-use)
  • [Project Structure](#project-structure)
  • [SSG vs SSR vs Hybrid](#ssg-vs-ssr-vs-hybrid)
  • [Content Collections](#content-collections) — Legacy, Content Layer API, Custom Loaders
  • [Syntax Highlighting](#syntax-highlighting) — Shiki, Transformers, Expressive Code
  • [Diagram Integration](#diagram-integration) — Mermaid, PlantUML, Dark Mode Theming
  • [Client-Side Search](#client-side-search) — Pagefind (controls, weighting), Fuse.js
  • [Versioned Documentation](#versioned-documentation) — Starlight, Multi-version
  • [Internationalization](#internationalization-i18n) — Routing, Fallbacks
  • [Common Patterns](#common-patterns) — Pagination, Tags, RSS, Forms
  • [Performance Best Practices](#performance-best-practices) — Prefetching, Critical CSS
  • [Deployment](#deployment) — Firebase URL Config, GitHub Pages
  • [Pre-Deploy Checklist](#pre-deploy-checklist)
  • [Testing & Quality](#testing--quality) — Vitest, Playwright, Link Checking
  • [Troubleshooting](#troubleshooting)

Quick Start

# Create new project (use Blog template for Markdown sites)
npm create astro@latest

# Development
npm run dev          # Local server at http://localhost:4321
npm run build        # Generate static files in dist/
npm run preview      # Preview production build

When Not to Use

This skill focuses on static site generation (SSG). Consider other approaches for:

  • Real-time data applications - Use SSR mode with database connections
  • User authentication flows - Requires server-side session handling
  • E-commerce with dynamic inventory - Use hybrid mode or full SSR
  • Single-page applications (SPAs) - Consider React/Vue frameworks directly

For hybrid SSG+SSR patterns, see Astro's adapter documentation.

Project Structure

src/
  components/     # Astro, React, Vue, Svelte components
  content/        # Content Collections (Markdown/MDX)
    config.ts     # Collection schemas
    docs/         # Example collection
  layouts/        # Page wrappers with slots
  pages/          # File-based routing
public/           # Static assets (images, fonts, favicons)
astro.config.mjs  # Framework configuration

SSG vs SSR vs Hybrid

Mode When Pages Render Use Case
SSG (default) Build time Blogs, docs, marketing sites
SSR Each request Dynamic data, personalization
Hybrid Mix of both Static pages + dynamic endpoints

For pure static sites, use default output: 'static' - no adapter needed.

Content Collections

Legacy Pattern (Astro 4.x)

Define schemas in src/content/config.ts:

import { defineCollection, z } from "astro:content";

export const collections = {
  docs: defineCollection({
    schema: z.object({
      title: z.string(),
      description: z.string().optional(),
      tags: z.array(z.string()).optional(),
      order: z.number().optional(),
      draft: z.boolean().default(false)
    })
  })
};

Content Layer API (Astro 5.0+)

New pattern with glob() loader - up to 75% faster builds for large sites:

// src/content.config.ts (note: different filename)
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';

const blog = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/data/blog' }),
  schema: ({ image }) => z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
    draft: z.boolean().default(false),
    cover: image(),  // Validates image exists
    author: reference('authors'),  // Cross-collection reference
  })
});

export const collections = { blog };

Advanced Schema Patterns

schema: ({ image }) => z.object({
  cover: image(),                    // Validates image in src/
  category: z.enum(['tech', 'news']),
  author: reference('authors'),      // Cross-collection ref
  relatedPosts: z.array(reference('blog')).optional(),
})

Custom Loaders (Remote Content)

Fetch content from external APIs (GitHub releases, CMS, etc.):

// src/loaders/github-releases.ts
import type { Loader } from 'astro/loaders';

export function githubReleasesLoader(repo: string): Loader {
  return {
    name: 'github-releases',
    load: async ({ store, logger }) => {
      logger.info(`Fetching releases for ${repo}`);
      const response = await fetch(`https://api.github.com/repos/${repo}/releases`);
      const releases = await response.json();

      for (const release of releases) {
        store.set({
          id: release.tag_name,
          data: {
            version: release.tag_name,
            published_at: release.published_at,
            body: release.body  // Markdown release notes
          }
        });
      }
    }
  };
}

Register in content.config.ts:

import { githubReleasesLoader } from './loaders/github-releases';

const releases = defineCollection({
  loader: githubReleasesLoader('owner/repo'),
  schema: z.object({
    version: z.string(),
    published_at: z.string(),
    body: z.string(),
  })
});

Query and render collections:

---
import { getCollection } from "astro:content";

export async function getStaticPaths() {
  const docs = await getCollection("docs");
  return docs.map(doc => ({
    params: { slug: doc.slug },
    props: { doc }
  }));
}

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

<article>
  <h1>{doc.data.title}</h1>
  <Content />
</article>

Syntax Highlighting

Basic Shiki Configuration

import { defineConfig } from "astro/config";

export default defineConfig({
  markdown: {
    shikiConfig: {
      theme: "github-dark",
      wrap: true
    }
  }
});

Dual Light/Dark Theme

shikiConfig: {
  themes: {
    light: 'github-light',
    dark: 'github-dark',
  },
}

Add CSS to switch themes:

@media (prefers-color-scheme: dark) {
  .astro-code, .astro-code span {
    color: var(--shiki-dark) !important;
    background-color: var(--shiki-dark-bg) !important;
  }
}

Line Highlighting and Transformers

```typescript {2,4}
const a = 1;
const b = 2;  // highlighted
const c = 3;
console.log(a + b + c);  // highlighted

**Shiki Transformers (Astro 4.14+):**

import { transformerNotationFocus, transformerNotationDiff } from '@shikijs/transformers';

shikiConfig: { transformers: [transformerNotationFocus(), transformerNotationDiff()], }


Use notation comments in code:
- `// [!code focus]` - Focus this line
- `// [!code ++]` - Mark as addition (green)
- `// [!code --]` - Mark as deletion (red)

### Expressive Code (Recommended for Docs)

Rich code blocks with copy buttons, filenames, diff highlighting:

npm install astro-expressive-code

import expressiveCode from 'astro-expressive-code';

export default defineConfig({ integrations: [expressiveCode()], });


Features: Copy button, file tabs, line markers, terminal frames, text markers.

## Diagram Integration

### Mermaid (Recommended)

Install the Astro integration:

npm install astro-mermaid mermaid

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

export default defineConfig({ integrations: [mermaid({ theme: 'default' })] });


Use in Markdown:
graph TD;
    A-->B;
    B-->C;

Features: Client-side rendering, automatic theme switching, offline capable, no Playwright required.

**Alternative (build-time static SVG):** Use `rehype-mermaid` with Playwright for pre-rendered diagrams (`npx playwright install --with-deps` required).

**Dark Mode Theming Strategies:**

1. **CSS Variables** - Let browser resolve colors at runtime:

// mermaid config mermaid.initialize({ theme: 'base', themeVariables: { primaryColor: 'var(--diagram-primary)', lineColor: 'var(--diagram-line)' } });


2. **Picture Element** - Generate both themes, swap with media query:

<picture> <source srcset="/diagrams/flow-dark.svg" media="(prefers-color-scheme: dark)"> <img src="/diagrams/flow-light.svg" alt="Flow diagram"> </picture>


3. **Inline SVG** - Target SVG classes with CSS (risk: style collisions):

.dark .mermaid-svg .node rect { fill: var(--bg-dark); }


### PlantUML

npx astro add plantuml


Use in Markdown:
@startuml
Alice -> Bob: Hello
Bob --> Alice: Hi!
@enduml

## Client-Side Search

### Pagefind (Recommended for Large Sites)

Zero-config static search that indexes at build time:

npm install pagefind


Add to `package.json`:

{ "scripts": { "build": "astro build && npx pagefind --site dist", "postbuild": "pagefind --site dist" } }


Use in components:

<link href="/pagefind/pagefind-ui.css" rel="stylesheet" /> <script src="/pagefind/pagefind-ui.js" type="text/javascript"></script>

<div id="search"></div> <script> window.addEventListener('DOMContentLoaded', () => { new PagefindUI({ element: '#search', showSubResults: true }); }); </script>


Features: No external service, works offline, automatic indexing, small bundle (~10KB).

**Granular Indexing Control:**

<!-- Only index main content, not headers/sidebars --> <main data-pagefind-body> <h1 data-pagefind-meta="title">Page Title</h1> <p data-pagefind-weight="10">Important intro text</p>

<!-- Exclude from search snippets --> <nav data-pagefind-ignore> <a href="/related">Related Posts</a> </nav> </main>


| Attribute | Purpose |
|-----------|---------|
| `data-pagefind-body` | Limit indexing to this element only |
| `data-pagefind-ignore` | Exclude element from index |
| `data-pagefind-meta="key"` | Define metadata field |
| `data-pagefind-weight="10"` | Boost relevance (default: 1) |

### Pagefind vs Fuse.js

| Feature | Pagefind | Fuse.js |
|---------|----------|---------|
| Architecture | Pre-built binary chunks | Runtime in-memory |
| Bandwidth | Low (loads only needed chunks) | High (downloads full index) |
| Scalability | 10,000+ pages | < 500 pages |
| Multilingual | Native stemming | Manual config |
| Use Case | Global site search | Small list filtering |

### Fuse.js (Lightweight Alternative)

For smaller sites with custom UI needs:

import { getCollection } from "astro:content"; const posts = await getCollection('blog'); const searchIndex = JSON.stringify(posts.map(post => ({ title: post.data.title, slug: post.slug, body: post.body.slice(0, 500) })));


<input type="search" id="search" placeholder="Search..." /> <ul id="results"></ul>

<script define:vars={{ searchIndex }}> import Fuse from 'fuse.js';

const fuse = new Fuse(JSON.parse(searchIndex), { keys: ['title', 'body'], threshold: 0.3 });

document.getElementById('search').addEventListener('input', (e) => { const results = fuse.search(e.target.value); const resultsEl = document.getElementById('results');

// Clear previous results safely resultsEl.replaceChildren();

// Build results using safe DOM methods results.forEach(r => { const li = document.createElement('li'); const link = document.createElement('a'); link.href = /blog/${r.item.slug}; link.textContent = r.item.title; li.appendChild(link); resultsEl.appendChild(li); }); }); </script>


For enterprise needs, consider Algolia (hosted search API).

## Versioned Documentation

### Starlight (Recommended for Docs Sites)

Purpose-built documentation framework on Astro:

npm create astro@latest -- --template starlight


Key features: Built-in search (Pagefind), i18n, sidebar navigation, dark mode, component overrides.

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

export default defineConfig({ integrations: [ starlight({ title: 'My Docs', sidebar: [ { label: 'Guides', autogenerate: { directory: 'guides' } }, { label: 'Reference', autogenerate: { directory: 'reference' } }, ], }), ], });


### Multi-Version Docs Pattern

Use folder-based structure:

src/content/docs/ v1/ getting-started.md api-reference.md v2/ getting-started.md api-reference.md new-feature.md


For Starlight versioning, use `starlight-utils` plugin:

npm install starlight-utils

import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; import starlightUtils from 'starlight-utils';

export default defineConfig({ integrations: [ starlight({ plugins: [starlightUtils({ multiSidebar: { switcherStyle: 'dropdown' } })], sidebar: [ { label: 'v2', items: [{ label: 'Guides', autogenerate: { directory: 'v2' } }] }, { label: 'v1', items: [{ label: 'Guides', autogenerate: { directory: 'v1' } }] }, ], }), ], });


## Internationalization (i18n)

Configure in `astro.config.mjs`:

export default defineConfig({ i18n: { defaultLocale: "en", locales: ["en", "fr", "es"], routing: { prefixDefaultLocale: false } } });


Structure content by locale:

src/content/docs/ en/ getting-started.md fr/ getting-started.md


Detect locale in components:

const locale = Astro.currentLocale || 'en';



### Fallback for Missing Translations

Show English content with a banner when translations don't exist:

// src/pages/[lang]/[...slug].astro import { getCollection, getEntry } from "astro:content";

const languages = ['en', 'es', 'fr']; const defaultLang = 'en';

export async function getStaticPaths() { const englishDocs = await getCollection('docs', ({ id }) => id.startsWith('en/')); const paths = [];

for (const doc of englishDocs) { const slug = doc.id.replace(/^en\//, '');

for (const lang of languages) { const localizedId = ${lang}/${slug}; const localizedDoc = await getEntry('docs', localizedId);

paths.push({ params: { lang, slug }, props: { entry: localizedDoc || doc, // Fallback to English isFallback: !localizedDoc } }); } } return paths; }

const { entry, isFallback } = Astro.props; const { Content } = await entry.render();


{isFallback && ( <div class="translation-notice"> This page is not yet available in your language. </div> )} <Content />


## Common Patterns

### Paginated Listings

For sites with 50+ posts, split into pages:

// src/pages/blog/page/[page].astro import { getCollection } from "astro:content";

const POSTSPERPAGE = 10;

export async function getStaticPaths() { const allPosts = await getCollection("blog"); const totalPages = Math.ceil(allPosts.length / POSTSPERPAGE);

return Array.from({ length: totalPages }, (_, i) => ({ params: { page: String(i + 1) }, })); }

const { page } = Astro.params; const pageNum = parseInt(page); const allPosts = await getCollection("blog"); const sortedPosts = allPosts.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime() );

const start = (pageNum - 1) * POSTSPERPAGE; const posts = sortedPosts.slice(start, start + POSTSPERPAGE); const totalPages = Math.ceil(allPosts.length / POSTSPERPAGE);


{posts.map(post => <article>{post.data.title}</article>)}

<nav> {pageNum > 1 && <a href={/blog/page/${pageNum - 1}}>← Previous</a>} <span>Page {pageNum} of {totalPages}</span> {pageNum < totalPages && <a href={/blog/page/${pageNum + 1}}>Next →</a>} </nav>


### Tag/Category Archives

Generate a page for each tag:

// src/pages/tags/[tag].astro import { getCollection } from "astro:content";

export async function getStaticPaths() { const allPosts = await getCollection("blog"); const tags = new Set(); allPosts.forEach(post => post.data.tags?.forEach(tag => tags.add(tag)));

return Array.from(tags).map(tag => ({ params: { tag }, props: { tag }, })); }

const { tag } = Astro.props; const allPosts = await getCollection("blog"); const postsWithTag = allPosts.filter(post => post.data.tags?.includes(tag));


<h1>Posts tagged: {tag}</h1> {postsWithTag.map(post => <a href={/blog/${post.slug}}>{post.data.title}</a>)}


### RSS Feed

// src/pages/rss.xml.js import rss from "@astrojs/rss"; import { getCollection } from "astro:content";

export async function GET(context) { const blog = await getCollection("blog"); return rss({ title: "My Blog", description: "A blog about Astro", site: context.site, items: blog.map(post => ({ title: post.data.title, description: post.data.description, pubDate: post.data.pubDate, link: /blog/${post.slug}/, })), }); }


### Static Forms

For SSG sites, use third-party form handlers:

**Formspree (Easiest):**

<form action="https://formspree.io/f/YOURFORMID" method="POST"> <input type="text" name="name" required /> <input type="email" name="email" required /> <textarea name="message" required></textarea> <button type="submit">Send</button> </form>


**Netlify Forms:**

<form name="contact" method="POST" data-netlify="true"> <input type="hidden" name="form-name" value="contact" /> <input type="text" name="name" required /> <button type="submit">Send</button> </form>


### JSON-LD Structured Data

Add rich snippets for SEO:

const jsonLd = { "@context": "https://schema.org";, "@type": "BlogPosting", headline: entry.data.title, description: entry.data.description, datePublished: entry.data.pubDate?.toISOString(), author: { "@type": "Person", name: entry.data.author }, };


<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />


### Dark Mode Toggle

<button id="theme-toggle">🌙</button>

<script> const toggle = document.getElementById("theme-toggle"); const html = document.documentElement;

// Load saved preference or detect system preference const savedTheme = localStorage.getItem("theme"); const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;

if (savedTheme === "dark" || (!savedTheme && prefersDark)) { html.classList.add("dark"); }

toggle?.addEventListener("click", () => { html.classList.toggle("dark"); localStorage.setItem("theme", html.classList.contains("dark") ? "dark" : "light"); }); </script>


With Tailwind, enable `darkMode: "class"` in config.

## Performance Best Practices

1. **Partial Hydration**: Use `client:*` directives only where needed
   - `client:load` - Hydrate immediately
   - `client:idle` - Hydrate when browser is idle
   - `client:visible` - Hydrate when in viewport
   - `client:media` - Hydrate on media query match
   - `client:only="react"` - Skip server render, client-only

2. **Image Optimization**: Use Astro's `<Image />` component

3. **Keep Static Where Possible**: Islands architecture means most content remains static HTML

4. **Asset Fingerprinting**: Automatic in production builds

5. **Prefetching**: Auto-load links before user clicks

// astro.config.mjs export default defineConfig({ prefetch: { prefetchAll: true, // Prefetch all links defaultStrategy: 'viewport' // When links enter viewport } });


Options: `'tap'` (on hover/focus), `'viewport'` (when visible), `'load'` (on page load).

6. **Critical CSS**: Inline above-the-fold CSS with astro-critters

npm install astro-critters

import critters from 'astro-critters';

export default defineConfig({ integrations: [critters()] });


Extracts critical CSS and inlines it, deferring the rest for faster first paint.

## Deployment

### Deployment Workflow

1. **Build**: Run `npm run build` and verify `dist/` output
2. **Preview**: Test with `npm run preview` at localhost:4321
3. **Configure**: Set `site`, `base`, and `trailingSlash` in astro.config.mjs
4. **Platform Setup**: Initialize hosting (e.g., `firebase init hosting`)
5. **Deploy**: Push to platform (`firebase deploy`, `vercel`, or git push)
6. **Verify**: Check live URL, test 404 page, validate assets load

### Quick Deploy Commands

Build for production

npm run build

Preview before deploy

npm run preview


### Platform-Specific

**Netlify/Vercel/Cloudflare Pages:**
Connect Git repository - auto-deploys on push.

**GitHub Pages:**

// astro.config.mjs export default defineConfig({ site: 'https://username.github.io';, base: '/repo-name' });


**Firebase Hosting:**

npm install -g firebase-tools firebase login firebase init hosting # Set public to 'dist' npm run build firebase deploy


`firebase.json` (recommended configuration):

{ "hosting": { "public": "dist", "ignore": ["firebase.json", "**/.*"], "cleanUrls": true, "trailingSlash": false, "headers": [ { "source": "/_astro/**", "headers": [{"key": "Cache-Control", "value": "public, max-age=31536000, immutable"}] } ] } }


**Align Astro config** to prevent redirect loops:

// astro.config.mjs - match Firebase settings export default defineConfig({ trailingSlash: 'never', // Must match Firebase trailingSlash: false build: { format: 'directory' // Default - generates /about/index.html } });


| Firebase Setting | Astro Setting | Result |
|------------------|---------------|--------|
| `trailingSlash: false` | `trailingSlash: 'never'` | `/about` (no slash) |
| `trailingSlash: true` | `trailingSlash: 'always'` | `/about/` (with slash) |
| Mismatch | Mismatch | Redirect loops! |

### Common Deployment Gotchas

| Issue | Solution |
|-------|----------|
| Trailing slash problems | Set `trailingSlash: 'always'` or `'never'` |
| Assets not loading on subpath | Configure `base` in astro.config.mjs |
| 404 not working | Create custom `404.astro` page |
| Build fails on deploy | Check Node version matches local |

## Pre-Deploy Checklist

Before deploying, verify:

- [ ] `npm run build` completes without errors
- [ ] `npm run preview` shows site correctly at localhost:4321
- [ ] All Content Collection schemas validate (`astro check`)
- [ ] Images use `<Image />` component or are in `public/`
- [ ] SEO metadata present on all pages (title, description, og:*)
- [ ] 404.astro page exists and renders correctly
- [ ] `base` path configured if deploying to subdirectory
- [ ] Environment variables set on deployment platform
- [ ] `trailingSlash` setting matches hosting platform expectations
- [ ] RSS feed working (`/rss.xml`)
- [ ] Sitemap generated (`/sitemap-index.xml`)
- [ ] Lighthouse score > 90
- [ ] Component tests pass (`npm run test`)
- [ ] E2E tests pass (`npm run test:e2e`)
- [ ] Link checker finds no broken links (`npx linkinator dist`)

## Testing & Quality

### Static Analysis

Type checking and validation

npx astro check

Linting (with ESLint)

npm install -D eslint npx eslint .

Preview production build

npm run build && npm run preview


**Build-time validation** happens automatically with Content Collections - schema errors fail the build.

### Component Testing with Vitest

npm install -D vitest @vitest/ui @astrojs/testing

// vitest.config.ts import { getViteConfig } from 'astro/config';

export default getViteConfig({ test: { include: ['src/**/*.test.ts'], }, });

// src/components/Button.test.ts import { experimental_AstroContainer as AstroContainer } from 'astro/container'; import { expect, test } from 'vitest'; import Button from './Button.astro';

test('Button renders with text', async () => { const container = await AstroContainer.create(); const result = await container.renderToString(Button, { props: { text: 'Click me' } }); expect(result).toContain('Click me'); });


### E2E Testing with Playwright

npm install -D @playwright/test npx playwright install

// tests/homepage.spec.ts import { test, expect } from '@playwright/test';

test('homepage loads correctly', async ({ page }) => { await page.goto('/'); await expect(page).toHaveTitle(/My Site/); await expect(page.locator('h1')).toBeVisible(); });

test('navigation works', async ({ page }) => { await page.goto('/'); await page.click('a[href="/about"]'); await expect(page).toHaveURL(/about/); });

// package.json { "scripts": { "test": "vitest", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui" } }


### Link Checking

Validate internal links don't break:

npm install -D linkinator npx linkinator dist --recurse


Add to CI:

.github/workflows/links.yml


## .astro File Anatomy

// Frontmatter: JavaScript/TypeScript runs at build time import Layout from '../layouts/Layout.astro'; import { getCollection } from 'astro:content';

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


<!-- Template: HTML with JSX expressions --> <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 to this component / h1 { color: navy; } </style>

<script> // Client-side JavaScript console.log('Runs in browser'); </script>


## File-Based Routing

| File | Route |
|------|-------|
| `src/pages/index.astro` | `/` |
| `src/pages/about.astro` | `/about` |
| `src/pages/blog/index.astro` | `/blog` |
| `src/pages/blog/[slug].astro` | `/blog/:slug` (dynamic) |
| `src/pages/[...path].astro` | Catch-all |

Dynamic routes require `getStaticPaths()` for SSG:

export function getStaticPaths() { return [ { params: { slug: 'post-1' } }, { params: { slug: 'post-2' } } ]; }



## SEO Essentials

### Manual Approach

const { title, description, image } = Astro.props; const canonicalURL = new URL(Astro.url.pathname, Astro.site);


<head> <title>{title}</title> <meta name="description" content={description} /> <link rel="canonical" href={canonicalURL} />

<!-- Open Graph --> <meta property="og:title" content={title} /> <meta property="og:description" content={description} /> <meta property="og:image" content={image} /> <meta property="og:type" content="website" />

<!-- Twitter --> <meta name="twitter:card" content="summarylargeimage" /> </head>


### astro-seo (Simplified)

npm install astro-seo


import { SEO } from 'astro-seo';


<head> <SEO title="Page Title" description="Page description" openGraph={{ basic: { title: "OG Title", type: "website", image: "/og-image.png", } }} twitter={{ creator: "@handle" }} /> </head>


Handles meta tags, Open Graph, Twitter Cards, and canonical URLs automatically.

## Essential Integrations

Add integrations

npx astro add react # React components npx astro add tailwind # Tailwind CSS npx astro add mdx # MDX support npx astro add sitemap # Auto-generate sitemap

RSS feed

npm install @astrojs/rss


## Troubleshooting

**"Works locally but breaks on deploy"**
- Check environment variables are set on host
- Verify `base` path configuration
- Ensure Node version matches (v18+ recommended)

**Dynamic routes missing pages**
- Verify `getStaticPaths()` returns all needed paths
- Check for typos in params

**Content Collection schema errors**
- Run `astro check` for validation details
- Ensure frontmatter matches Zod schema exactly

**Assets not loading**
- Use `import` for processed assets
- Use `public/` for unprocessed static files

## References

For detailed guides on specific topics, see:
- `references/markdown-deep-dive.md` - Advanced Markdown/MDX patterns
- `references/deployment-platforms.md` - Platform-specific deployment details

## Key Resources

- [Astro Documentation](https://docs.astro.build/)
- [Content Collections](https://docs.astro.build/en/guides/content-collections/)
- [Markdown in Astro](https://docs.astro.build/en/guides/markdown-content/)
- [Deploy Astro](https://docs.astro.build/en/guides/deploy/)
- [Starlight Docs Theme](https://starlight.astro.build/)