Astro 5.x Best Practices Guide (2024-2025)

· combray's blog


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
  2. Content Collections Best Practices
  3. TypeScript Configuration Recommendations
  4. Performance Optimization Techniques
  5. Security Best Practices
  6. Testing Approaches for Astro Projects
  7. 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

src/components/ - Recommended Convention

src/layouts/ - Layout Templates

src/content/ - Content Collections

public/ - Static Assets

File Naming Conventions #

Component Files:

Consistent Naming:

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:
 1{
 2  "compilerOptions": {
 3    "baseUrl": ".",
 4    "paths": {
 5      "@components/*": ["src/components/*"],
 6      "@layouts/*": ["src/layouts/*"],
 7      "@content/*": ["src/content/*"]
 8    }
 9  }
10}

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:

Setting Up Content Collections #

1. Create Configuration File

Create src/content.config.ts (or .js, .mjs):

 1import { defineCollection, z } from 'astro:content';
 2import { glob } from 'astro/loaders';
 3
 4const blog = defineCollection({
 5  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
 6  schema: z.object({
 7    title: z.string(),
 8    description: z.string(),
 9    publishDate: z.date(),
10    author: z.string(),
11    tags: z.array(z.string()).optional(),
12    draft: z.boolean().default(false),
13    image: z.object({
14      src: z.string(),
15      alt: z.string()
16    }).optional()
17  })
18});
19
20export const collections = { blog };

2. TypeScript Requirements

Ensure these settings in tsconfig.json:

1{
2  "compilerOptions": {
3    "strictNullChecks": true,
4    "allowJs": true
5  }
6}

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:

Use Zod Effectively

 1import { z } from 'astro:content';
 2
 3// Make fields optional explicitly
 4const schema = z.object({
 5  title: z.string(),                    // Required
 6  subtitle: z.string().optional(),      // Optional
 7  publishDate: z.date().transform((date) => new Date(date)), // Transform
 8  status: z.enum(['draft', 'published', 'archived']), // Enum
 9  readingTime: z.number().positive(),   // Validated number
10  tags: z.array(z.string()).default([]) // Default value
11});

Key Zod Patterns:

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:

 1import { defineCollection, z, reference } from 'astro:content';
 2
 3const authors = defineCollection({
 4  schema: z.object({
 5    name: z.string(),
 6    bio: z.string()
 7  })
 8});
 9
10const blog = defineCollection({
11  schema: z.object({
12    title: z.string(),
13    author: reference('authors'),        // Single reference
14    relatedPosts: z.array(reference('blog')).optional() // Multiple
15  })
16});

Querying Content Collections #

Type-Safe Queries

 1import { getCollection, getEntry } from 'astro:content';
 2
 3// Get all entries
 4const allPosts = await getCollection('blog');
 5
 6// Filter entries
 7const publishedPosts = await getCollection('blog', ({ data }) => {
 8  return data.draft !== true;
 9});
10
11// Get single entry
12const post = await getEntry('blog', 'my-first-post');

Typing Component Props

1---
2import type { CollectionEntry } from 'astro:content';
3
4interface Props {
5  post: CollectionEntry<'blog'>;
6}
7
8const { post } = Astro.props;
9---

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


3. TypeScript Configuration Recommendations #

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:

 1{
 2  "extends": "astro/tsconfigs/strict",
 3  "compilerOptions": {
 4    "baseUrl": ".",
 5    "paths": {
 6      "@components/*": ["src/components/*"],
 7      "@layouts/*": ["src/layouts/*"],
 8      "@utils/*": ["src/utils/*"]
 9    },
10    "verbatimModuleSyntax": true
11  }
12}

Key Compiler Options #

Essential Settings:

 1{
 2  "compilerOptions": {
 3    "strict": true,                    // Enable all strict checks
 4    "strictNullChecks": true,          // Required for content collections
 5    "allowJs": true,                   // Support .js files
 6    "verbatimModuleSyntax": true,      // Enforce type imports
 7    "isolatedModules": true,           // Ensure file-level transpilation
 8    "skipLibCheck": true,              // Speed up compilation
 9    "moduleResolution": "bundler",     // Modern module resolution
10    "jsx": "react-jsx"                 // For JSX support
11  }
12}

Verbatim Module Syntax

Enabled by default in Astro's presets, this setting enforces import type for types:

1// Correct
2import type { CollectionEntry } from 'astro:content';
3import { getCollection } from 'astro:content';
4
5// Error - type should use 'import type'
6import { CollectionEntry, getCollection } from 'astro:content';

Multiple JSX Framework Configuration #

When using multiple frameworks (React, Preact, Solid), configure per-file JSX:

tsconfig.json:

1{
2  "compilerOptions": {
3    "jsx": "react-jsx",
4    "jsxImportSource": "react"  // Default framework
5  }
6}

Per-File Override:

1/** @jsxImportSource preact */
2import { h } from 'preact';
3
4export function PreactComponent() {
5  return <div>Preact Component</div>;
6}

Path Aliases Best Practices #

Organize imports with clear aliases:

 1{
 2  "compilerOptions": {
 3    "baseUrl": ".",
 4    "paths": {
 5      "@/*": ["src/*"],
 6      "@components/*": ["src/components/*"],
 7      "@layouts/*": ["src/layouts/*"],
 8      "@lib/*": ["src/lib/*"],
 9      "@utils/*": ["src/utils/*"],
10      "@content/*": ["src/content/*"],
11      "@assets/*": ["src/assets/*"]
12    }
13  }
14}

Usage:

1// Instead of: import { formatDate } from '../../../utils/date';
2import { formatDate } from '@utils/date';

Type Generation and Validation #

Automatic Type Generation

Astro automatically generates types for:

Manual Sync

Force type regeneration:

1npx astro sync

Do this when:

Editor Integration #

VS Code Configuration

Install the official Astro extension for:

Why tsconfig.json Matters

Even if not writing TypeScript, tsconfig.json enables:


4. Performance Optimization Techniques #

Astro 5.0 Built-in Improvements #

Content Layer Performance

Astro 5.0 delivers significant build performance improvements:

Server Islands Architecture

Server Islands extend the Islands Architecture to the server, enabling:

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

1# Use latest LTS Node.js version
2node --version  # Should be v20+ or v22+
3
4# Keep Astro updated
5npm update astro

Expected improvement: ~30% faster builds

2. Increase Memory Allocation

1# In package.json scripts
2{
3  "scripts": {
4    "build": "NODE_OPTIONS='--max-old-space-size=8192' astro build"
5  }
6}

Benefits:

3. Configure Build Concurrency

1// astro.config.mjs
2export default defineConfig({
3  build: {
4    concurrency: 4  // Adjust based on CPU cores
5  }
6});

Optimal settings:

4. Implement API Caching

Problem: Individual API calls per page during build significantly increase build times.

Solution: Cache API responses in getStaticPaths():

 1// Bad: API call in component (runs for each page)
 2---
 3const data = await fetch('https://api.example.com/data');
 4---
 5
 6// Good: Cache in getStaticPaths (runs once)
 7export async function getStaticPaths() {
 8  const data = await fetch('https://api.example.com/data');
 9  const items = await data.json();
10
11  return items.map(item => ({
12    params: { slug: item.slug },
13    props: { item }  // Pass as props
14  }));
15}

5. Move Large Assets to CDN

1// astro.config.mjs
2export default defineConfig({
3  build: {
4    assets: 'assets'  // Customize asset directory
5  }
6});

Benefits:

Image Optimization #

Use Modern Formats

---
import { Image } from 'astro:assets';
import heroImage from '@assets/hero.jpg';
---

<Image
  src={heroImage}
  alt="Hero image"
  format="webp"           // or 'avif'
  quality={80}
  loading="lazy"
  widths={[400, 800, 1200]}
  sizes="(max-width: 800px) 100vw, 800px"
/>

Best practices:

Image Component Benefits

Astro's <Image> component automatically:

CSS Optimization #

Automatic Optimizations

Astro handles CSS optimization by default:

Best Practices

---
// Component-scoped styles (recommended)
---
<style>
  h1 {
    color: var(--primary-color);
  }
</style>

<!-- Or global styles when needed -->
<style is:global>
  :root {
    --primary-color: #3b82f6;
  }
</style>

Code Splitting

Astro automatically splits code:

Incremental Content Caching #

Enable for large content sites:

1// astro.config.mjs
2export default defineConfig({
3  experimental: {
4    contentCollectionCache: true
5  }
6});

Benefits:

Runtime Performance #

Minimize JavaScript

Astro's HTML-first philosophy:

View Transitions

Smooth page transitions without JavaScript overhead:

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

Prefetching

<a href="/about" data-astro-prefetch>About</a>

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:

1// astro.config.mjs
2export default defineConfig({
3  experimental: {
4    security: {
5      csp: true
6    }
7  }
8});

Benefits:

Hash-Based CSP Implementation

Astro uses hash-based CSP instead of nonces:

CSP via Meta Tag

For static sites and SPAs, Astro uses:

1<meta http-equiv="content-security-policy" content="...">

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:

Environment Variable Security #

Type-Safe Environment Variables (astro:env)

 1// astro.config.mjs
 2import { defineConfig, envField } from 'astro/config';
 3
 4export default defineConfig({
 5  env: {
 6    schema: {
 7      // Public client variable
 8      PUBLIC_API_URL: envField.string({
 9        context: "client",
10        access: "public",
11        default: "https://api.example.com"
12      }),
13
14      // Public server variable
15      BUILD_TIME: envField.string({
16        context: "server",
17        access: "public"
18      }),
19
20      // Secret server variable
21      API_SECRET: envField.string({
22        context: "server",
23        access: "secret"
24      }),
25
26      // Optional with validation
27      DB_URL: envField.string({
28        context: "server",
29        access: "secret",
30        optional: true
31      })
32    },
33    validateSecrets: true  // Validate on start
34  }
35});

Security Rules:

Usage:

1// Server-side only
2import { API_SECRET } from 'astro:env/server';
3
4// Client and server
5import { PUBLIC_API_URL } from 'astro:env/client';

Security Best Practices Checklist #

Environment Variables:

Content Security:

Dependency Security:

1# Audit dependencies regularly
2npm audit
3
4# Fix vulnerabilities
5npm audit fix
6
7# Check for outdated packages
8npm outdated

Headers and Configuration:

 1// astro.config.mjs
 2export default defineConfig({
 3  server: {
 4    headers: {
 5      'X-Frame-Options': 'DENY',
 6      'X-Content-Type-Options': 'nosniff',
 7      'Referrer-Policy': 'strict-origin-when-cross-origin',
 8      'Permissions-Policy': 'geolocation=(), microphone=()'
 9    }
10  }
11});

Static Site Generation Security #

Build-Time Security:

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:

Recommended tools:

Vitest Setup for Unit Testing #

Installation:

1npm install -D vitest

Configuration (vitest.config.ts):

1import { getViteConfig } from 'astro/config';
2
3export default getViteConfig({
4  test: {
5    globals: true,
6    environment: 'happy-dom'  // or 'jsdom'
7  }
8});

Key Points:

Custom Configuration (Astro 4.8+):

1export default getViteConfig(
2  { test: { /* ... */ } },
3  {
4    site: 'https://example.com',
5    trailingSlash: 'always'
6  }
7);

Component Testing with Container API #

Astro 4.9+ Container API

The Container API enables native Astro component testing:

 1import { experimental_AstroContainer as AstroContainer } from 'astro/container';
 2import { expect, test } from 'vitest';
 3import Card from '../src/components/Card.astro';
 4
 5test('Card component renders correctly', async () => {
 6  const container = await AstroContainer.create();
 7  const result = await container.renderToString(Card, {
 8    props: {
 9      title: 'Test Card',
10      description: 'Test description'
11    }
12  });
13
14  expect(result).toContain('Test Card');
15  expect(result).toContain('Test description');
16});

Testing with Slots:

 1test('Card with slot content', async () => {
 2  const container = await AstroContainer.create();
 3  const result = await container.renderToString(Card, {
 4    slots: {
 5      default: '<p>Slot content</p>'
 6    }
 7  });
 8
 9  expect(result).toContain('Slot content');
10});

Environment Configuration:

1// At top of test file
2/// @vitest-environment happy-dom
3
4import { expect, test } from 'vitest';

Vitest 2.0 Browser Mode #

Enhanced Component Testing:

Vitest 2.0 introduced Browser Mode:

Configuration:

1export default {
2  test: {
3    browser: {
4      enabled: true,
5      name: 'chromium',
6      provider: 'playwright'
7    }
8  }
9};

Compatibility Considerations #

Astro 5 + Vitest Issues:

Recent compatibility notes:

Solution:

1npm install -D vitest@latest

Playwright Setup for E2E Testing #

Installation:

1npm init playwright@latest

Configuration (playwright.config.ts):

 1import { defineConfig } from '@playwright/test';
 2
 3export default defineConfig({
 4  testDir: './tests',
 5  baseURL: 'http://localhost:4321',
 6
 7  webServer: {
 8    command: 'npm run dev',
 9    url: 'http://localhost:4321',
10    reuseExistingServer: !process.env.CI
11  },
12
13  use: {
14    trace: 'on-first-retry'
15  },
16
17  projects: [
18    {
19      name: 'chromium',
20      use: { browserName: 'chromium' }
21    },
22    {
23      name: 'firefox',
24      use: { browserName: 'firefox' }
25    },
26    {
27      name: 'webkit',
28      use: { browserName: 'webkit' }
29    }
30  ]
31});

Example E2E Test:

 1import { test, expect } from '@playwright/test';
 2
 3test('homepage loads correctly', async ({ page }) => {
 4  await page.goto('/');
 5
 6  // Check page title
 7  await expect(page).toHaveTitle(/My Astro Site/);
 8
 9  // Check heading
10  const heading = page.locator('h1');
11  await expect(heading).toHaveText('Welcome to Astro');
12
13  // Test navigation
14  await page.click('a[href="/about"]');
15  await expect(page).toHaveURL(/about/);
16});
17
18test('blog posts load', async ({ page }) => {
19  await page.goto('/blog');
20
21  const articles = page.locator('article');
22  await expect(articles).toHaveCount(3);
23});

Running Tests:

1# Run all tests
2npx playwright test
3
4# Run in UI mode
5npx playwright test --ui
6
7# View test report
8npx playwright show-report

Testing Best Practices #

1. Test Production Builds

1# Build first
2npm run build
3
4# Test against preview
5npm run preview
6npx playwright test

Production builds may differ from development.

2. Test Real Deployments

1// playwright.config.ts for production
2export default defineConfig({
3  baseURL: process.env.PRODUCTION_URL || 'http://localhost:4321'
4});

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:

1import { render, screen } from '@testing-library/react';
2import { expect, test } from 'vitest';
3import ReactButton from '../src/components/ReactButton';
4
5test('React button renders', () => {
6  render(<ReactButton label="Click me" />);
7  expect(screen.getByRole('button')).toHaveTextContent('Click me');
8});

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:

 1name: Test
 2
 3on: [push, pull_request]
 4
 5jobs:
 6  test:
 7    runs-on: ubuntu-latest
 8    steps:
 9      - uses: actions/checkout@v4
10      - uses: actions/setup-node@v4
11        with:
12          node-version: '20'
13
14      - name: Install dependencies
15        run: npm ci
16
17      - name: Run unit tests
18        run: npm run test:unit
19
20      - name: Install Playwright
21        run: npx playwright install --with-deps
22
23      - name: Build site
24        run: npm run build
25
26      - name: Run E2E tests
27        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:

 1---
 2// src/pages/blog/[slug].astro
 3export async function getStaticPaths() {
 4  const posts = await getCollection('blog');
 5
 6  return posts.map(post => ({
 7    params: { slug: post.slug },
 8    props: { post }
 9  }));
10}
11
12const { post } = Astro.props;
13const { Content } = await post.render();
14---
15
16<h1>{post.data.title}</h1>
17<Content />

Key Rules:

Advanced Patterns #

Multiple Parameters:

 1// src/pages/[category]/[year]/[slug].astro
 2export async function getStaticPaths() {
 3  const posts = await getCollection('blog');
 4  const categories = ['tech', 'design', 'business'];
 5  const years = ['2023', '2024', '2025'];
 6
 7  return categories.flatMap(category =>
 8    years.flatMap(year =>
 9      posts
10        .filter(p => p.data.category === category && p.data.year === year)
11        .map(post => ({
12          params: { category, year, slug: post.slug },
13          props: { post }
14        }))
15    )
16  );
17}

Generates routes like: /tech/2024/my-post

Pagination Pattern:

 1---
 2import { getCollection } from 'astro:content';
 3
 4export async function getStaticPaths({ paginate }) {
 5  const posts = await getCollection('blog');
 6  const sortedPosts = posts.sort(
 7    (a, b) => b.data.publishDate - a.data.publishDate
 8  );
 9
10  return paginate(sortedPosts, { pageSize: 10 });
11}
12
13const { page } = Astro.props;
14---
15
16{page.data.map(post => (
17  <article>
18    <h2>{post.data.title}</h2>
19  </article>
20))}
21
22<!-- Pagination controls -->
23{page.url.prev && <a href={page.url.prev}>Previous</a>}
24{page.url.next && <a href={page.url.next}>Next</a>}

API Data at Build Time:

 1export async function getStaticPaths() {
 2  // Fetch once at build time
 3  const response = await fetch('https://api.example.com/products');
 4  const products = await response.json();
 5
 6  return products.map(product => ({
 7    params: { id: product.id },
 8    props: { product }  // Pass data as props
 9  }));
10}

Performance Optimization for getStaticPaths #

1. Limit Data Fetching

 1// Bad: Fetching full product data for each page
 2export async function getStaticPaths() {
 3  const products = await fetchAllProductsWithFullDetails();
 4  return products.map(p => ({ params: { id: p.id }, props: { p } }));
 5}
 6
 7// Good: Fetch only IDs, get details in component
 8export async function getStaticPaths() {
 9  const ids = await fetchProductIds();  // Lighter query
10  return ids.map(id => ({ params: { id } }));
11}
12
13// In component
14const { id } = Astro.params;
15const product = await fetchProduct(id);  // Cached/memoized

2. Implement Caching

 1// utils/cache.ts
 2const cache = new Map();
 3
 4export async function cachedFetch(url: string) {
 5  if (cache.has(url)) {
 6    return cache.get(url);
 7  }
 8
 9  const response = await fetch(url);
10  const data = await response.json();
11  cache.set(url, data);
12
13  return data;
14}
15
16// Use in getStaticPaths
17export async function getStaticPaths() {
18  const data = await cachedFetch('https://api.example.com/data');
19  // ...
20}

3. Content Collections over getStaticPaths

When possible, use Content Collections instead of getStaticPaths():

 1// Preferred: Content Collections
 2import { getCollection } from 'astro:content';
 3
 4export async function getStaticPaths() {
 5  const posts = await getCollection('blog');
 6  return posts.map(post => ({
 7    params: { slug: post.slug },
 8    props: { post }
 9  }));
10}

Benefits:

Hybrid Rendering (Astro 5.0) #

Simplified Configuration

Astro 5.0 merged hybrid and static modes:

1// astro.config.mjs
2export default defineConfig({
3  output: 'static',  // Default
4  adapter: node()    // For SSR routes
5});

Selective SSR:

1// src/pages/api/dynamic.ts
2export const prerender = false;  // This route uses SSR
3
4export async function GET() {
5  const data = await fetchLiveData();
6  return new Response(JSON.stringify(data));
7}

Selective Static:

1// In SSR mode
2export const prerender = true;  // This route is pre-rendered

Server Islands for Static Sites #

Use Case:

Combine static HTML with dynamic, server-rendered components:

---
// src/pages/product/[id].astro (static)
import DynamicPricing from '@components/DynamicPricing.astro';
import Reviews from '@components/Reviews.astro';
---

<!-- Static content -->
<h1>{product.name}</h1>
<p>{product.description}</p>

<!-- Dynamic island -->
<DynamicPricing server:defer productId={product.id}>
  <div slot="fallback">Loading price...</div>
</DynamicPricing>

<!-- Another island -->
<Reviews server:defer productId={product.id}>
  <div slot="fallback">Loading reviews...</div>
</Reviews>

Benefits:

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:

 1// src/middleware.ts
 2const cache = new Map();
 3const REVALIDATE_TIME = 60 * 1000; // 1 minute
 4
 5export async function onRequest({ request, next }, locals) {
 6  const url = new URL(request.url);
 7  const cacheKey = url.pathname;
 8
 9  const cached = cache.get(cacheKey);
10
11  if (cached && Date.now() - cached.timestamp < REVALIDATE_TIME) {
12    return cached.response;
13  }
14
15  const response = await next();
16
17  cache.set(cacheKey, {
18    response: response.clone(),
19    timestamp: Date.now()
20  });
21
22  return response;
23}

CDN-Level ISR:

 1// astro.config.mjs
 2export default defineConfig({
 3  adapter: netlify(),
 4  output: 'static'
 5});
 6
 7// In page component
 8---
 9export const prerender = true;
10
11// Set cache headers
12Astro.response.headers.set('Cache-Control', 's-maxage=60, stale-while-revalidate=86400');
13---

Stale-While-Revalidate:

Static Site Best Practices #

1. Build Strategy

1// astro.config.mjs
2export default defineConfig({
3  build: {
4    format: 'directory',      // URLs without .html
5    inlineStylesheets: 'auto', // Inline small CSS
6    assetsPrefix: 'https://cdn.example.com'  // CDN for assets
7  }
8});

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:

4. Monitoring and Validation

1# Check for broken links
2npm run build
3npx broken-link-checker http://localhost:4321
4
5# Lighthouse CI
6npm install -g @lhci/cli
7lhci 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:

Type Safety:

Developer Experience:

Production Ready:

By following these best practices, teams can build fast, secure, and maintainable websites with Astro 5.x.


Sources #

Official Documentation #

Tutorials and Guides #

Performance Optimization #

Security #

Testing #

Server Islands #

Community Resources #

last updated: