# Astro 5.x Best Practices Guide (2024-2025)
**Report Date:** November 30, 2025
**Framework Version:** Astro 5.x
**Research Focus:** Current best practices for production-ready Astro development
---
## Table of Contents
1. [Project Structure and Organization Patterns](#project-structure-and-organization-patterns)
2. [Content Collections Best Practices](#content-collections-best-practices)
3. [TypeScript Configuration Recommendations](#typescript-configuration-recommendations)
4. [Performance Optimization Techniques](#performance-optimization-techniques)
5. [Security Best Practices](#security-best-practices)
6. [Testing Approaches for Astro Projects](#testing-approaches-for-astro-projects)
7. [Static Site Generation Patterns](#static-site-generation-patterns)
---
## 1. Project Structure and Organization Patterns
### Standard Directory Layout
Astro projects follow an opinionated folder structure that balances convention with flexibility:
```
project-root/
├── src/
│ ├── pages/ # Required - defines routes
│ ├── components/ # Reusable components (Astro, React, Vue, etc.)
│ ├── layouts/ # Page layout templates
│ ├── styles/ # CSS/Sass files
│ ├── content/ # Content collections (Markdown, MDX, etc.)
│ └── middleware/ # Request/response processing
├── public/ # Static assets (copied as-is)
├── astro.config.mjs # Astro configuration
├── tsconfig.json # TypeScript configuration
└── package.json # Dependencies and scripts
```
### Key Directory Guidelines
**`src/pages/` - Required Directory**
- The only mandatory directory in Astro projects
- Files here automatically become routes based on file-based routing
- Without it, your site will have no pages or routes
- Supports `.astro`, `.md`, `.mdx`, and framework component files
**`src/components/` - Recommended Convention**
- Store reusable UI components (Astro and framework components)
- Optional but highly recommended for organization
- Can be organized by feature, type, or framework
**`src/layouts/` - Layout Templates**
- Define shared UI structures across multiple pages
- Common layouts: BaseLayout, BlogPostLayout, DocumentationLayout
- Conventional but not mandatory
**`src/content/` - Content Collections**
- Organize collections with each subdirectory representing a named collection
- While Astro 5's Content Layer API allows collections anywhere, this structure maintains clarity
- Example: `src/content/blog/`, `src/content/docs/`
**`public/` - Static Assets**
- Files served untouched during builds
- Not bundled or optimized by Astro
- Ideal for: `robots.txt`, `favicon.ico`, manifests, pre-optimized images
- Referenced in HTML with root-relative paths: `/favicon.ico`
### File Naming Conventions
**Component Files:**
- Use `.astro` for reusable components and page layouts
- Only `.astro` files can handle Astro API calls like `getStaticPaths()`
- Framework components (`.jsx`, `.vue`, `.svelte`) for interactive elements
**Consistent Naming:**
- Use lowercase with dashes instead of spaces: `blog-post.astro`, `user-profile.tsx`
- Makes content easier to find and organize
- Improves cross-platform compatibility
### Project Organization Recommendations
1. **Modular Development:** Organize components by feature or domain
2. **Code Splitting:** Astro handles this by default for optimal loading
3. **Import Aliases:** Configure path aliases in `tsconfig.json`:
```json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"],
"@content/*": ["src/content/*"]
}
}
}
```
---
## 2. Content Collections Best Practices
### Content Layer API (Astro 5.0)
Astro 5.0 introduces the Content Layer API, a fundamental improvement for content management:
- **Unified, type-safe API** for defining, loading, and accessing content
- **Works with any source:** local files, APIs, databases, CMS platforms
- **Performance gains:** 5x faster builds for Markdown, 2x faster for MDX
- **Memory efficiency:** 25-50% reduction in memory usage
### Setting Up Content Collections
**1. Create Configuration File**
Create `src/content.config.ts` (or `.js`, `.mjs`):
```typescript
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(),
publishDate: z.date(),
author: z.string(),
tags: z.array(z.string()).optional(),
draft: z.boolean().default(false),
image: z.object({
src: z.string(),
alt: z.string()
}).optional()
})
});
export const collections = { blog };
```
**2. TypeScript Requirements**
Ensure these settings in `tsconfig.json`:
```json
{
"compilerOptions": {
"strictNullChecks": true,
"allowJs": true
}
}
```
Recommended: Use `"strict": true` or extend `astro/tsconfigs/strict` for full type safety.
### Schema Validation Best Practices
**Always Define Schemas**
While optional, schemas are highly recommended because they:
- Enforce consistent frontmatter structure
- Provide automatic TypeScript typings
- Enable property autocompletion in editors
- Catch validation errors during builds
**Use Zod Effectively**
```typescript
import { z } from 'astro:content';
// Make fields optional explicitly
const schema = z.object({
title: z.string(), // Required
subtitle: z.string().optional(), // Optional
publishDate: z.date().transform((date) => new Date(date)), // Transform
status: z.enum(['draft', 'published', 'archived']), // Enum
readingTime: z.number().positive(), // Validated number
tags: z.array(z.string()).default([]) // Default value
});
```
**Key Zod Patterns:**
- Everything is required by default - use `.optional()` explicitly
- Use `.transform()` for data conversion (strings to dates, etc.)
- Use `.default()` to provide fallback values
- Use `.enum()` for constrained string values
### Collection Organization
**Separate Collections by Content Type**
If content represents different structures, use separate collections:
```
src/content/
├── blog/ # Blog posts
├── docs/ # Documentation
├── authors/ # Author profiles
└── projects/ # Project showcases
```
**Cross-Collection References**
Use the `reference()` function for relationships:
```typescript
import { defineCollection, z, reference } from 'astro:content';
const authors = defineCollection({
schema: z.object({
name: z.string(),
bio: z.string()
})
});
const blog = defineCollection({
schema: z.object({
title: z.string(),
author: reference('authors'), // Single reference
relatedPosts: z.array(reference('blog')).optional() // Multiple
})
});
```
### Querying Content Collections
**Type-Safe Queries**
```typescript
import { getCollection, getEntry } from 'astro:content';
// Get all entries
const allPosts = await getCollection('blog');
// Filter entries
const publishedPosts = await getCollection('blog', ({ data }) => {
return data.draft !== true;
});
// Get single entry
const post = await getEntry('blog', 'my-first-post');
```
**Typing Component Props**
```typescript
---
import type { CollectionEntry } from 'astro:content';
interface Props {
post: CollectionEntry<'blog'>;
}
const { post } = Astro.props;
---
```
### Content Collection Maintenance
**Development Workflow**
1. **Restart dev server** or use sync command (`s + enter`) after schema changes
2. **Add `.astro` to `.gitignore`** - contains generated types
3. **Use consistent naming** for file slugs to ensure predictable URLs
**Production Considerations**
- Filter draft content in production builds
- Leverage incremental content caching for faster builds
- Use loaders for remote content sources (CMS, APIs)
---
## 3. TypeScript Configuration Recommendations
### Recommended Strictness Levels
Astro provides three TypeScript configuration presets:
1. **`base`** - Minimal TypeScript support, permissive
2. **`strict`** - Recommended for TypeScript projects
3. **`strictest`** - Maximum type safety
**Recommendation:** Use `strict` or `strictest` for production projects.
### Strict Configuration
**Extending Astro's Strict Preset:**
```json
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"],
"@utils/*": ["src/utils/*"]
},
"verbatimModuleSyntax": true
}
}
```
### Key Compiler Options
**Essential Settings:**
```json
{
"compilerOptions": {
"strict": true, // Enable all strict checks
"strictNullChecks": true, // Required for content collections
"allowJs": true, // Support .js files
"verbatimModuleSyntax": true, // Enforce type imports
"isolatedModules": true, // Ensure file-level transpilation
"skipLibCheck": true, // Speed up compilation
"moduleResolution": "bundler", // Modern module resolution
"jsx": "react-jsx" // For JSX support
}
}
```
**Verbatim Module Syntax**
Enabled by default in Astro's presets, this setting enforces `import type` for types:
```typescript
// Correct
import type { CollectionEntry } from 'astro:content';
import { getCollection } from 'astro:content';
// Error - type should use 'import type'
import { CollectionEntry, getCollection } from 'astro:content';
```
### Multiple JSX Framework Configuration
When using multiple frameworks (React, Preact, Solid), configure per-file JSX:
**tsconfig.json:**
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react" // Default framework
}
}
```
**Per-File Override:**
```tsx
/** @jsxImportSource preact */
import { h } from 'preact';
export function PreactComponent() {
return
Preact Component
;
}
```
### Path Aliases Best Practices
**Organize imports with clear aliases:**
```json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"],
"@lib/*": ["src/lib/*"],
"@utils/*": ["src/utils/*"],
"@content/*": ["src/content/*"],
"@assets/*": ["src/assets/*"]
}
}
}
```
**Usage:**
```typescript
// Instead of: import { formatDate } from '../../../utils/date';
import { formatDate } from '@utils/date';
```
### Type Generation and Validation
**Automatic Type Generation**
Astro automatically generates types for:
- Content collections schemas
- Environment variables (via `astro:env`)
- Route parameters
**Manual Sync**
Force type regeneration:
```bash
npx astro sync
```
Do this when:
- Adding new content collections
- Modifying collection schemas
- After pulling changes from version control
### Editor Integration
**VS Code Configuration**
Install the official Astro extension for:
- Syntax highlighting for `.astro` files
- TypeScript IntelliSense
- Code formatting
- Error checking
- Debugging support
**Why tsconfig.json Matters**
Even if not writing TypeScript, `tsconfig.json` enables:
- NPM package imports in the editor
- Framework component type checking
- Better autocompletion
- Import path resolution
---
## 4. Performance Optimization Techniques
### Astro 5.0 Built-in Improvements
**Content Layer Performance**
Astro 5.0 delivers significant build performance improvements:
- **5x faster** for Markdown pages on content-heavy sites
- **2x faster** for MDX content
- **25-50% reduction** in memory usage
**Server Islands Architecture**
Server Islands extend the Islands Architecture to the server, enabling:
- Static HTML caching on Edge CDNs
- Independent loading of dynamic components
- Custom fallback content during island loading
- Individual cache lifetime per island
### Build Optimization Strategies
Real-world optimization can improve build speed from 35 pages/second to 127 pages/second (3.6x improvement):
**1. Upgrade Node.js and Astro**
```bash
# Use latest LTS Node.js version
node --version # Should be v20+ or v22+
# Keep Astro updated
npm update astro
```
Expected improvement: ~30% faster builds
**2. Increase Memory Allocation**
```bash
# In package.json scripts
{
"scripts": {
"build": "NODE_OPTIONS='--max-old-space-size=8192' astro build"
}
}
```
Benefits:
- Reduced garbage collection pauses
- Eliminated memory-related crashes
- Faster processing of large sites
**3. Configure Build Concurrency**
```javascript
// astro.config.mjs
export default defineConfig({
build: {
concurrency: 4 // Adjust based on CPU cores
}
});
```
Optimal settings:
- 4 for most systems
- Too high causes resource contention
- Too low underutilizes CPU
**4. Implement API Caching**
Problem: Individual API calls per page during build significantly increase build times.
Solution: Cache API responses in `getStaticPaths()`:
```typescript
// Bad: API call in component (runs for each page)
---
const data = await fetch('https://api.example.com/data');
---
// Good: Cache in getStaticPaths (runs once)
export async function getStaticPaths() {
const data = await fetch('https://api.example.com/data');
const items = await data.json();
return items.map(item => ({
params: { slug: item.slug },
props: { item } // Pass as props
}));
}
```
**5. Move Large Assets to CDN**
```javascript
// astro.config.mjs
export default defineConfig({
build: {
assets: 'assets' // Customize asset directory
}
});
```
Benefits:
- Reduced build time (no processing of large files)
- Faster page loads
- Lower hosting costs
### Image Optimization
**Use Modern Formats**
```astro
---
import { Image } from 'astro:assets';
import heroImage from '@assets/hero.jpg';
---
```
Best practices:
- **WebP/AVIF:** Smaller file sizes, better quality
- **Responsive widths:** Load appropriate sizes per screen
- **Lazy loading:** Load images only when in viewport
- **Quality settings:** 80-85 is often sufficient
**Image Component Benefits**
Astro's `` component automatically:
- Optimizes images during build
- Generates multiple sizes
- Serves modern formats with fallbacks
- Adds proper width/height attributes (prevents layout shift)
### CSS Optimization
**Automatic Optimizations**
Astro handles CSS optimization by default:
- Minification
- Purification (removes unused styles)
- Critical CSS extraction
- Scoped styles per component
**Best Practices**
```astro
---
// Component-scoped styles (recommended)
---
```
**Code Splitting**
Astro automatically splits code:
- Each page gets only its required JavaScript
- Components are bundled efficiently
- Shared dependencies are extracted
### Incremental Content Caching
Enable for large content sites:
```javascript
// astro.config.mjs
export default defineConfig({
experimental: {
contentCollectionCache: true
}
});
```
Benefits:
- Reuses unchanged content entries
- Dramatically speeds up incremental builds
- Tracks changes via internal build manifest
### Runtime Performance
**Minimize JavaScript**
Astro's HTML-first philosophy:
- Zero JavaScript by default
- Add interactivity only where needed
- Framework components are islands of interactivity
**View Transitions**
Smooth page transitions without JavaScript overhead:
```astro
---
import { ViewTransitions } from 'astro:transitions';
---
```
**Prefetching**
```astro
About
```
---
## 5. Security Best Practices
### Content Security Policy (CSP)
**Astro 5.9 CSP Support**
Astro 5.9 introduces experimental built-in CSP support - the framework's most upvoted feature request:
```javascript
// astro.config.mjs
export default defineConfig({
experimental: {
security: {
csp: true
}
}
});
```
**Benefits:**
- Ditch `unsafe-inline` workarounds
- Works with all adapters and runtimes
- Supports static sites, serverless, Node.js, edge runtimes
- Compatible with all frontend libraries
**Hash-Based CSP Implementation**
Astro uses hash-based CSP instead of nonces:
- Generates hashes for every script and stylesheet
- More complex but supports more use cases
- Works with static sites (nonce headers don't)
**CSP via Meta Tag**
For static sites and SPAs, Astro uses:
```html
```
This approach works everywhere, not just server-rendered pages.
### Static Site Security Advantages
**Inherent Security Benefits:**
1. **Reduced Attack Surface:** Pre-built HTML with minimal JavaScript
2. **No Server-Side Processing:** Can't exploit server vulnerabilities
3. **XSS Protection:** Static content can't inject malicious scripts
4. **No Database:** No SQL injection risks
5. **Faster Patches:** Rebuild and redeploy quickly
**Default Security Posture**
Astro renders entire pages to static HTML by default:
- Removes all JavaScript from final build (unless explicitly added)
- No runtime code execution vulnerabilities
- Combines performance with security
### Environment Variable Security
**Type-Safe Environment Variables (astro:env)**
```typescript
// astro.config.mjs
import { defineConfig, envField } from 'astro/config';
export default defineConfig({
env: {
schema: {
// Public client variable
PUBLIC_API_URL: envField.string({
context: "client",
access: "public",
default: "https://api.example.com"
}),
// Public server variable
BUILD_TIME: envField.string({
context: "server",
access: "public"
}),
// Secret server variable
API_SECRET: envField.string({
context: "server",
access: "secret"
}),
// Optional with validation
DB_URL: envField.string({
context: "server",
access: "secret",
optional: true
})
},
validateSecrets: true // Validate on start
}
});
```
**Security Rules:**
- **Client variables:** Available in browser (public data only)
- **Server variables:** Server-side only, not in client bundle
- **Secrets:** Never exposed to client, validated at runtime
- **No client secrets:** Framework prevents this (no safe way to send)
**Usage:**
```typescript
// Server-side only
import { API_SECRET } from 'astro:env/server';
// Client and server
import { PUBLIC_API_URL } from 'astro:env/client';
```
### Security Best Practices Checklist
**Environment Variables:**
- ✅ Never hardcode sensitive information
- ✅ Use `.env` files, add to `.gitignore`
- ✅ Use `astro:env` for type safety
- ✅ Rotate secrets regularly
- ✅ Apply least privilege principle
- ✅ Use different secrets for dev/staging/production
**Content Security:**
- ✅ Enable CSP in production
- ✅ Validate user-generated content
- ✅ Sanitize Markdown/MDX inputs
- ✅ Use trusted content sources
**Dependency Security:**
```bash
# Audit dependencies regularly
npm audit
# Fix vulnerabilities
npm audit fix
# Check for outdated packages
npm outdated
```
**Headers and Configuration:**
```javascript
// astro.config.mjs
export default defineConfig({
server: {
headers: {
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'geolocation=(), microphone=()'
}
}
});
```
### Static Site Generation Security
**Build-Time Security:**
- Environment variables evaluated at build time
- Can't change after deployment
- Predictable, immutable output
**Best Practices:**
1. **Validate inputs** at build time
2. **Use CSP** to lock down allowed resources
3. **Implement ISR** carefully - understand caching implications
4. **Audit third-party scripts** before including
5. **Monitor dependencies** for vulnerabilities
---
## 6. Testing Approaches for Astro Projects
### Testing Strategy Overview
Astro supports comprehensive testing approaches:
- **Unit tests:** Test individual functions and utilities
- **Component tests:** Test Astro and framework components
- **End-to-end tests:** Test complete user flows
Recommended tools:
- **Vitest:** Unit and component testing
- **Playwright:** End-to-end testing
- **Cypress:** Alternative E2E option
### Vitest Setup for Unit Testing
**Installation:**
```bash
npm install -D vitest
```
**Configuration (vitest.config.ts):**
```typescript
import { getViteConfig } from 'astro/config';
export default getViteConfig({
test: {
globals: true,
environment: 'happy-dom' // or 'jsdom'
}
});
```
**Key Points:**
- Use `getViteConfig()` to integrate with Astro's settings
- Auto-detects Astro config by default
- Choose DOM library: `happy-dom` (faster) or `jsdom` (more compatible)
**Custom Configuration (Astro 4.8+):**
```typescript
export default getViteConfig(
{ test: { /* ... */ } },
{
site: 'https://example.com',
trailingSlash: 'always'
}
);
```
### Component Testing with Container API
**Astro 4.9+ Container API**
The Container API enables native Astro component testing:
```typescript
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import { expect, test } from 'vitest';
import Card from '../src/components/Card.astro';
test('Card component renders correctly', async () => {
const container = await AstroContainer.create();
const result = await container.renderToString(Card, {
props: {
title: 'Test Card',
description: 'Test description'
}
});
expect(result).toContain('Test Card');
expect(result).toContain('Test description');
});
```
**Testing with Slots:**
```typescript
test('Card with slot content', async () => {
const container = await AstroContainer.create();
const result = await container.renderToString(Card, {
slots: {
default: 'Slot content
'
}
});
expect(result).toContain('Slot content');
});
```
**Environment Configuration:**
```typescript
// At top of test file
/// @vitest-environment happy-dom
import { expect, test } from 'vitest';
```
### Vitest 2.0 Browser Mode
**Enhanced Component Testing:**
Vitest 2.0 introduced Browser Mode:
- Built on Playwright
- Renders components in iframe with real browser events
- More accurate than JSDOM
- Less error-prone than snapshot testing
**Configuration:**
```typescript
export default {
test: {
browser: {
enabled: true,
name: 'chromium',
provider: 'playwright'
}
}
};
```
### Compatibility Considerations
**Astro 5 + Vitest Issues:**
Recent compatibility notes:
- Vitest reverted Vite 6 support in v2.1.7
- Update to Vitest 3.0.5+ for Astro 5 compatibility
- "test does not exist" errors indicate version conflicts
**Solution:**
```bash
npm install -D vitest@latest
```
### Playwright Setup for E2E Testing
**Installation:**
```bash
npm init playwright@latest
```
**Configuration (playwright.config.ts):**
```typescript
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
baseURL: 'http://localhost:4321',
webServer: {
command: 'npm run dev',
url: 'http://localhost:4321',
reuseExistingServer: !process.env.CI
},
use: {
trace: 'on-first-retry'
},
projects: [
{
name: 'chromium',
use: { browserName: 'chromium' }
},
{
name: 'firefox',
use: { browserName: 'firefox' }
},
{
name: 'webkit',
use: { browserName: 'webkit' }
}
]
});
```
**Example E2E Test:**
```typescript
import { test, expect } from '@playwright/test';
test('homepage loads correctly', async ({ page }) => {
await page.goto('/');
// Check page title
await expect(page).toHaveTitle(/My Astro Site/);
// Check heading
const heading = page.locator('h1');
await expect(heading).toHaveText('Welcome to Astro');
// Test navigation
await page.click('a[href="/about"]');
await expect(page).toHaveURL(/about/);
});
test('blog posts load', async ({ page }) => {
await page.goto('/blog');
const articles = page.locator('article');
await expect(articles).toHaveCount(3);
});
```
**Running Tests:**
```bash
# Run all tests
npx playwright test
# Run in UI mode
npx playwright test --ui
# View test report
npx playwright show-report
```
### Testing Best Practices
**1. Test Production Builds**
```bash
# Build first
npm run build
# Test against preview
npm run preview
npx playwright test
```
Production builds may differ from development.
**2. Test Real Deployments**
```typescript
// playwright.config.ts for production
export default defineConfig({
baseURL: process.env.PRODUCTION_URL || 'http://localhost:4321'
});
```
**3. Organize Tests**
```
tests/
├── unit/ # Vitest unit tests
│ ├── utils.test.ts
│ └── helpers.test.ts
├── component/ # Vitest component tests
│ ├── Card.test.ts
│ └── Navigation.test.ts
└── e2e/ # Playwright E2E tests
├── homepage.spec.ts
└── blog.spec.ts
```
**4. Test Framework Components**
For React/Vue/Svelte components, use framework-specific testing libraries:
```typescript
import { render, screen } from '@testing-library/react';
import { expect, test } from 'vitest';
import ReactButton from '../src/components/ReactButton';
test('React button renders', () => {
render();
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
```
**5. Client-Side Testing**
For now, test client-side Astro component code with E2E tests (Playwright/Cypress) rather than unit tests.
### Continuous Integration
**GitHub Actions Example:**
```yaml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit
- name: Install Playwright
run: npx playwright install --with-deps
- name: Build site
run: npm run build
- name: Run E2E tests
run: npm run test:e2e
```
---
## 7. Static Site Generation Patterns
### getStaticPaths() for Dynamic Routes
**Core Concept**
In static mode, all routes must be determined at build time. Dynamic routes require `getStaticPaths()` to generate paths.
**Basic Pattern:**
```typescript
---
// src/pages/blog/[slug].astro
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();
---
{post.data.title}
```
**Key Rules:**
- Returns array of objects with `params` property
- Each object generates one route
- Executes in isolated scope (can't reference parent scope except imports)
- Runs once at build time
### Advanced Patterns
**Multiple Parameters:**
```typescript
// src/pages/[category]/[year]/[slug].astro
export async function getStaticPaths() {
const posts = await getCollection('blog');
const categories = ['tech', 'design', 'business'];
const years = ['2023', '2024', '2025'];
return categories.flatMap(category =>
years.flatMap(year =>
posts
.filter(p => p.data.category === category && p.data.year === year)
.map(post => ({
params: { category, year, slug: post.slug },
props: { post }
}))
)
);
}
```
Generates routes like: `/tech/2024/my-post`
**Pagination Pattern:**
```typescript
---
import { getCollection } from 'astro:content';
export async function getStaticPaths({ paginate }) {
const posts = await getCollection('blog');
const sortedPosts = posts.sort(
(a, b) => b.data.publishDate - a.data.publishDate
);
return paginate(sortedPosts, { pageSize: 10 });
}
const { page } = Astro.props;
---
{page.data.map(post => (
{post.data.title}
))}
{page.url.prev && Previous}
{page.url.next && Next}
```
**API Data at Build Time:**
```typescript
export async function getStaticPaths() {
// Fetch once at build time
const response = await fetch('https://api.example.com/products');
const products = await response.json();
return products.map(product => ({
params: { id: product.id },
props: { product } // Pass data as props
}));
}
```
### Performance Optimization for getStaticPaths
**1. Limit Data Fetching**
```typescript
// Bad: Fetching full product data for each page
export async function getStaticPaths() {
const products = await fetchAllProductsWithFullDetails();
return products.map(p => ({ params: { id: p.id }, props: { p } }));
}
// Good: Fetch only IDs, get details in component
export async function getStaticPaths() {
const ids = await fetchProductIds(); // Lighter query
return ids.map(id => ({ params: { id } }));
}
// In component
const { id } = Astro.params;
const product = await fetchProduct(id); // Cached/memoized
```
**2. Implement Caching**
```typescript
// utils/cache.ts
const cache = new Map();
export async function cachedFetch(url: string) {
if (cache.has(url)) {
return cache.get(url);
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, data);
return data;
}
// Use in getStaticPaths
export async function getStaticPaths() {
const data = await cachedFetch('https://api.example.com/data');
// ...
}
```
**3. Content Collections over getStaticPaths**
When possible, use Content Collections instead of `getStaticPaths()`:
```typescript
// Preferred: Content Collections
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post }
}));
}
```
Benefits:
- Type-safe schemas
- 5x faster builds
- Better caching
- Automatic optimization
### Hybrid Rendering (Astro 5.0)
**Simplified Configuration**
Astro 5.0 merged `hybrid` and `static` modes:
```javascript
// astro.config.mjs
export default defineConfig({
output: 'static', // Default
adapter: node() // For SSR routes
});
```
**Selective SSR:**
```typescript
// src/pages/api/dynamic.ts
export const prerender = false; // This route uses SSR
export async function GET() {
const data = await fetchLiveData();
return new Response(JSON.stringify(data));
}
```
**Selective Static:**
```typescript
// In SSR mode
export const prerender = true; // This route is pre-rendered
```
### Server Islands for Static Sites
**Use Case:**
Combine static HTML with dynamic, server-rendered components:
```astro
---
// src/pages/product/[id].astro (static)
import DynamicPricing from '@components/DynamicPricing.astro';
import Reviews from '@components/Reviews.astro';
---
{product.name}
{product.description}
Loading price...
Loading reviews...
```
**Benefits:**
- Static page cached on CDN (fast delivery)
- Dynamic data loaded independently
- Slower islands don't block fast ones
- Fallback content shows immediately
**When to Use:**
✅ E-commerce sites (static products, dynamic pricing/inventory)
✅ Content sites with personalization
✅ Dashboards with mostly static layout
❌ Avoid when: high ratio of dynamic to static content
### Incremental Static Regeneration (ISR)
**Middleware Pattern:**
```typescript
// src/middleware.ts
const cache = new Map();
const REVALIDATE_TIME = 60 * 1000; // 1 minute
export async function onRequest({ request, next }, locals) {
const url = new URL(request.url);
const cacheKey = url.pathname;
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < REVALIDATE_TIME) {
return cached.response;
}
const response = await next();
cache.set(cacheKey, {
response: response.clone(),
timestamp: Date.now()
});
return response;
}
```
**CDN-Level ISR:**
```javascript
// astro.config.mjs
export default defineConfig({
adapter: netlify(),
output: 'static'
});
// In page component
---
export const prerender = true;
// Set cache headers
Astro.response.headers.set('Cache-Control', 's-maxage=60, stale-while-revalidate=86400');
---
```
**Stale-While-Revalidate:**
- Serves stale content immediately
- Revalidates in background
- User never waits for fresh content
- Ideal for frequently updated but not real-time content
### Static Site Best Practices
**1. Build Strategy**
```javascript
// astro.config.mjs
export default defineConfig({
build: {
format: 'directory', // URLs without .html
inlineStylesheets: 'auto', // Inline small CSS
assetsPrefix: 'https://cdn.example.com' // CDN for assets
}
});
```
**2. Route Organization**
```
src/pages/
├── index.astro # /
├── about.astro # /about
├── blog/
│ ├── index.astro # /blog
│ └── [slug].astro # /blog/[slug]
├── docs/
│ └── [...slug].astro # /docs/any/path
└── api/
└── search.json.ts # /api/search.json
```
**3. Content Organization**
Use Content Collections for type-safety:
- Better organization
- Schema validation
- Automatic type generation
- Performance optimization
**4. Monitoring and Validation**
```bash
# Check for broken links
npm run build
npx broken-link-checker http://localhost:4321
# Lighthouse CI
npm install -g @lhci/cli
lhci autorun
```
---
## Conclusion
Astro 5.x represents a mature, production-ready framework for building high-performance static sites with modern developer experience. Key takeaways:
**Performance First:**
- HTML-first architecture
- Zero JavaScript by default
- Built-in optimizations
- Incremental adoption of interactivity
**Type Safety:**
- Full TypeScript support
- Automatic type generation
- Content Collections schemas
- Environment variable validation
**Developer Experience:**
- Intuitive project structure
- Multiple testing approaches
- Framework-agnostic
- Excellent documentation
**Production Ready:**
- Built-in security features
- Multiple deployment targets
- Performance monitoring
- Scalability patterns
By following these best practices, teams can build fast, secure, and maintainable websites with Astro 5.x.
---
## Sources
### Official Documentation
- [Astro 5.0 Release](https://astro.build/blog/astro-5/)
- [Project Structure - Astro Docs](https://docs.astro.build/en/basics/project-structure/)
- [Content Collections - Astro Docs](https://docs.astro.build/en/guides/content-collections/)
- [TypeScript - Astro Docs](https://docs.astro.build/en/guides/typescript/)
- [Testing - Astro Docs](https://docs.astro.build/en/guides/testing/)
- [Routing - Astro Docs](https://docs.astro.build/en/guides/routing/)
- [Environment Variables - Astro Docs](https://docs.astro.build/en/guides/environment-variables/)
- [Environment Variables API Reference](https://docs.astro.build/en/reference/modules/astro-env/)
- [Server Islands - Astro Docs](https://docs.astro.build/en/guides/server-islands/)
- [Astro 5.9 Release](https://astro.build/blog/astro-590/)
### Tutorials and Guides
- [What is Astro? A Step-by-Step Tutorial for Beginners in 2025](https://themefisher.com/astro-js-introduction)
- [Astro Web Framework Guide 2025](https://apatero.com/blog/astro-web-framework-complete-guide-2025)
- [How to Use Astro Content Collections](https://astrocourse.dev/blog/how-to-use-content-collections/)
- [Getting Started with Content Collections in Astro — SitePoint](https://www.sitepoint.com/getting-started-with-content-collections-in-astro/)
- [Understanding Astro's getStaticPaths function](https://kristianfreeman.com/understanding-astros-getstaticpaths-function)
- [Type-safe environment variables in Astro 5.0](https://bryanlrobinson.com/blog/type-safe-environment-variables-in-astro-5-0/)
### Performance Optimization
- [Boosting Web Performance with Astro JS](https://dev.to/benajaero/boosting-web-performance-how-we-supercharged-our-agencys-site-with-astro-js-image-speed-optimization-techniques-18mf)
- [Astro Build Speed Optimization: From 35 to 127 Pages/Second](https://www.bitdoze.com/astro-ssg-build-optimization/)
- [How We Cut Astro Build Time from 30 Minutes to 5 Minutes](https://medium.com/@mohdkhan.mk99/how-we-cut-astro-build-time-from-30-minutes-to-5-minutes-83-faster-115349727060)
- [How to optimize images in Astro: A step-by-step guide](https://uploadcare.com/blog/how-to-optimize-images-in-astro/)
- [Production-Ready Astro Middleware](https://www.lorenstew.art/blog/production-ready-astro-middleware)
- [How to Implement ISR in Astro](https://logsnag.com/blog/implementing-isr-in-astro)
- [How to do advanced caching and ISR with Astro](https://developers.netlify.com/guides/how-to-do-advanced-caching-and-isr-with-astro/)
### Security
- [Astro 5.9: Content Security Policy Support](https://www.piyushmehta.com/blog/astro-v5-9-content-security-policy)
- [Complete Guide to Astro SEO Optimization](https://astrojs.dev/articles/astro-seo-optimization/)
### Testing
- [How to set up unit tests for Astro components](https://angelika.me/2025/02/01/astro-component-unit-tests/)
- [Testing dynamic astro endpoints](https://allpointsburnes.com/blog/2024-01-23-testing-dynamic-astro-endpoints/)
### Server Islands
- [Server Islands - The Future of Astro](https://astro.build/blog/future-of-astro-server-islands/)
- [Astro 4.12: Server Islands](https://astro.build/blog/astro-4120/)
- [How Astro's server islands deliver progressive rendering](https://developers.netlify.com/guides/how-astros-server-islands-deliver-progressive-rendering-for-your-sites/)
### Community Resources
- [2024 Year in Review](https://astro.build/blog/year-in-review-2024/)
- [The Rise of Astro.js in 2025](https://dev.to/fahim_shahrier_4a003786e0/the-rise-of-astrojs-in-2025-m4k)
- [Starting 2025 With Astro](https://randomgeekery.org/post/2024/12/starting-2025-with-astro/)
- [AstroWind Theme](https://github.com/arthelokyo/astrowind)