astro
Astro framework patterns for SSR/SSG, middleware, and TypeScript. Use when working with Astro pages, API routes, middleware configuration, or debugging rendering issues. Trigger phrases include "Astro", "prerender", "middleware", "SSR", "SSG", "static site".
$ 安裝
git clone https://github.com/wrsmith108/astro-claude-skill ~/.claude/skills/astro-claude-skill// tip: Run this command in your terminal to install the skill
name: astro description: Astro framework patterns for SSR/SSG, middleware, and TypeScript. Use when working with Astro pages, API routes, middleware configuration, or debugging rendering issues. Trigger phrases include "Astro", "prerender", "middleware", "SSR", "SSG", "static site".
Astro Framework Skill
Patterns and gotchas for Astro development, with focus on authentication, middleware, and rendering modes.
Rendering Modes
SSG vs SSR for Protected Content
Critical: In Astro 5.x with output: "static", pre-rendered pages bypass middleware completely.
User Request → Vercel CDN → Pre-rendered HTML (cached)
↳ Middleware SKIPPED for static content
| Rendering Mode | Middleware Runs? | Use Case |
|---|---|---|
SSG (prerender = true) | No - build time only | Public content, marketing |
SSR (prerender = false) | Yes - every request | Protected content, auth |
Protecting Content Pages
// src/pages/learn/[workshop]/[slug].astro
---
// REQUIRED: Forces middleware to run on every request
export const prerender = false;
const auth = Astro.locals.auth();
if (!auth?.userId) {
return Astro.redirect('/sign-in');
}
---
API Routes Always Need SSR
// src/pages/api/members/index.ts
// API routes needing locals.auth MUST disable prerender
export const prerender = false;
export const GET: APIRoute = async ({ locals }) => {
const auth = locals.auth?.();
if (!auth?.userId) {
return new Response("Unauthorized", { status: 401 });
}
// ...
};
Middleware Patterns
Basic Route Protection
// src/middleware.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/astro/server";
const isPublicRoute = createRouteMatcher([
"/",
"/sign-in(.*)",
"/sign-up(.*)",
"/api/webhooks/(.*)",
]);
export const onRequest = clerkMiddleware((auth, context) => {
const { userId } = auth();
if (isPublicRoute(context.request)) {
return; // Allow public routes
}
if (!userId) {
return auth().redirectToSignIn();
}
});
Member Status Validation
Check member status in addition to authentication:
import { checkMemberAccess, ACCESS_LEVELS } from "@lib/auth";
async function checkAccess(userId, context) {
const member = await memberQueries.findByClerkId(userId);
if (!member) {
return context.redirect("/pending-approval");
}
// Check status (expired, pending_approval, etc.)
const access = checkMemberAccess(member, ACCESS_LEVELS.MEMBER);
if (!access.hasAccess && access.redirectTo) {
return context.redirect(access.redirectTo);
}
return undefined; // Allow access
}
TypeScript Gotchas
Unused Variable in Template Strings
Astro's TypeScript preprocessor may report variables as "unused" even when used in template literals:
// ❌ TypeScript error: 'slug' is declared but never read
---
const slug = entries[0]?.slug.split("/").pop();
return Astro.redirect(`/learn/${workshop}/${slug}`);
---
// ✅ Inline the expression
---
return Astro.redirect(`/learn/${workshop}/${entries[0]?.slug.split("/").pop() ?? "default"}`);
---
Path Brackets in Shell Commands
File paths with brackets need quoting in shell:
# ❌ Shell interprets brackets as glob
git add src/pages/learn/[workshop]/index.astro
# ✅ Quote the path
git add 'src/pages/learn/[workshop]/index.astro'
Content Collections
Dynamic Routes with SSR
When using SSR, you can't use getStaticPaths(). Use direct lookups instead:
// ❌ SSR mode - getStaticPaths not available
export const prerender = false;
export function getStaticPaths() { ... } // Won't work
// ✅ Direct content lookup
export const prerender = false;
const { workshop, slug } = Astro.params;
const entry = await getEntry("workshops", `${workshop}/${slug}`);
if (!entry) {
return Astro.redirect("/404");
}
Cache Headers
Prevent Caching of Status Pages
Status pages (pending, expired, error) should never be cached:
---
// Prevent caching - status can change
Astro.response.headers.set("Cache-Control", "no-store, no-cache, must-revalidate");
Astro.response.headers.set("Pragma", "no-cache");
---
Configuration Reference
astro.config.mjs
export default defineConfig({
output: "static", // Default - SSG with per-page SSR opt-in
// OR
output: "server", // Full SSR - all pages server-rendered
adapter: vercel(), // Required for SSR on Vercel
});
Per-Page Rendering
// Force SSG (build-time)
export const prerender = true;
// Force SSR (request-time)
export const prerender = false;
Common Issues
Middleware Not Running
Symptoms: Auth checks bypassed, expired users see content
Cause: Page is pre-rendered (SSG default)
Fix: Add export const prerender = false;
404 on Dynamic Routes
Symptoms: /learn/workshop/module returns 404
Cause: Dynamic params not handled in SSR mode
Fix: Check Astro.params and handle missing values:
const { workshop, slug } = Astro.params;
if (!workshop || !slug) {
return Astro.redirect("/404");
}
Build Fails with Type Errors
Symptoms: ts(6133): variable is declared but never read
Cause: Astro preprocessor quirk with template strings
Fix: Inline expressions or suppress with // @ts-ignore
Last updated: December 2025
Repository
