Common Patterns
Middleware
Section titled “Middleware”Intercept requests before page rendering:
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (context, next) => { const { locals, request, redirect, url } = context;
// Auth check const token = request.headers.get('authorization'); if (!token && url.pathname.startsWith('/dashboard')) { return redirect('/login'); }
// Set data for pages via locals locals.user = await verifyToken(token);
return next();});Access in pages via Astro.locals:
---const { user } = Astro.locals;---<p>Welcome, {user.name}!</p>Actions
Section titled “Actions”Type-safe server functions callable from any page:
import { defineAction } from 'astro:actions';import { z } from 'astro:schema';
export const server = { subscribe: defineAction({ accept: 'form', input: z.object({ email: z.string().email(), name: z.string().min(2), }), handler: async ({ email, name }) => { await db.subscribers.create({ email, name }); return { success: true, message: `Welcome, ${name}!` }; }, }),};Call from a page:
---import { actions } from 'astro:actions';
if (Astro.request.method === 'POST') { const result = await actions.subscribe(Astro.request);}---
<form method="POST" action={actions.subscribe}> <input name="email" type="email" required /> <input name="name" required /> <button type="submit">Subscribe</button></form>API Endpoints
Section titled “API Endpoints”import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ request, url }) => { const limit = url.searchParams.get('limit') ?? '10'; const users = await db.users.findMany({ take: Number(limit) });
return new Response(JSON.stringify(users), { status: 200, headers: { 'Content-Type': 'application/json' }, });};
export const POST: APIRoute = async ({ request }) => { const body = await request.json();
if (!body.email) { return new Response(JSON.stringify({ error: 'Email required' }), { status: 400, }); }
const user = await db.users.create({ data: body }); return new Response(JSON.stringify(user), { status: 201 });};Sessions
Section titled “Sessions”Store user data across requests:
export default defineConfig({ session: { driver: 'fs' },});---const session = await Astro.session;
// Readconst cart = await session.get('cart') ?? [];
// Writeawait session.set('cart', [...cart, newItem]);
// Destroyawait session.destroy();---View Transitions
Section titled “View Transitions”Smooth page transitions without full reload:
---// In your layoutimport { ViewTransitions } from 'astro:transitions';---
<head> <ViewTransitions /></head>Persist elements across navigations:
<audio id="player" transition:persist> <source src="/music.mp3" /></audio>Image Optimization
Section titled “Image Optimization”---import { Image, getImage } from 'astro:assets';import heroImg from '../assets/hero.jpg';---
<!-- Component (recommended) --><Image src={heroImg} alt="Hero" width={800} />
<!-- Programmatic -->{async () => { const optimized = await getImage({ src: heroImg, width: 400 }); return <img src={optimized.src} {...optimized.attributes} />;}}RSS Feed
Section titled “RSS Feed”import rss from '@astrojs/rss';import { getCollection } from 'astro:content';
export async function GET(context) { const posts = await getCollection('blog'); return rss({ title: 'My Blog', description: 'A blog about stuff', site: context.site, items: posts.map(post => ({ title: post.data.title, pubDate: post.data.pubDate, description: post.data.description, link: `/blog/${post.id}/`, })), });}Environment Variables
Section titled “Environment Variables”PUBLIC_API_URL=https://api.example.comSECRET_KEY=super-secret---// Server-side (all vars available)const secret = import.meta.env.SECRET_KEY;
// Client-side (only PUBLIC_ prefix)const apiUrl = import.meta.env.PUBLIC_API_URL;
// Built-inconst isDev = import.meta.env.DEV;const isProd = import.meta.env.PROD;const mode = import.meta.env.MODE;---Dynamic Imports
Section titled “Dynamic Imports”---// Conditional component loadingconst Component = (await import(`../components/${name}.astro`)).default;---
<Component />Error Pages
Section titled “Error Pages”<html> <body> <h1>Page Not Found</h1> <a href="/">Go home</a> </body></html>---// src/pages/500.astro (SSR only)---<html> <body> <h1>Server Error</h1> </body></html>Redirects
Section titled “Redirects”export default defineConfig({ redirects: { '/old-page': '/new-page', '/blog/[...slug]': '/articles/[...slug]', },});Or in middleware:
if (url.pathname === '/old') { return redirect('/new', 301);}Azure Static Web Apps Deployment
Section titled “Azure Static Web Apps Deployment”name: Deploy to Azure Static Web Apps
on: push: branches: [main]
jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://npm.pkg.github.com' scope: '@your-org'
- run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GH_PACKAGES_TOKEN }}
- run: npm run build
- uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} action: 'upload' app_location: '/' output_location: 'dist' skip_app_build: true # Already built aboveKey points:
skip_app_build: true— use when you’ve already runnpm run build(needed for private registry auth)registry-url+scope+NODE_AUTH_TOKEN— required for GitHub Packages private dependenciesoutput_location: 'dist'— Astro’s default build output directory
staticwebapp.config.json
Section titled “staticwebapp.config.json”Optional configuration for Azure SWA routing and headers:
{ "navigationFallback": { "rewrite": "/index.html" }, "globalHeaders": { "X-Frame-Options": "DENY", "X-Content-Type-Options": "nosniff" }}