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
- When Not to Use
- Project Structure
- SSG vs SSR vs Hybrid
- Content Collections — Legacy, Content Layer API, Custom Loaders
- Syntax Highlighting — Shiki, Transformers, Expressive Code
- Diagram Integration — Mermaid, PlantUML, Dark Mode Theming
- Client-Side Search — Pagefind (controls, weighting), Fuse.js
- Versioned Documentation — Starlight, Multi-version
- Internationalization — Routing, Fallbacks
- Common Patterns — Pagination, Tags, RSS, Forms
- Performance Best Practices — Prefetching, Critical CSS
- Deployment — Firebase URL Config, GitHub Pages
- Pre-Deploy Checklist
- Testing & Quality — Vitest, Playwright, Link Checking
- 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:
```mermaid
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