Loading the skills library…

Astro patterns with Clerk — middleware, SSR pages, island components, API routes, static vs SSR rendering. Triggers on: astro clerk, clerk astro middleware, astro protected page, clerk island component, astro API route auth, clerk astro SSR.
npx skills add clerk/skills --skill clerk-astro-patternsInstalls from the source repository, including any supporting files.
Downloads contain the instructions shown here. Use the install command for the complete skill.
SDK: @clerk/astro v4+. Requires Astro 5+.
| Task | Reference |
|---|---|
Configure middleware (bare clerkMiddleware(), checks per page) | references/middleware.md |
| Protect SSR pages | references/ssr-pages.md |
| Use Clerk in island components | references/island-components.md |
| Auth in API routes | references/api-routes.md |
| Use Clerk with React in Astro | references/astro-react.md |
Astro has two rendering modes per page: SSR and static prerender. Clerk works differently in each:
Astro.locals.auth() which is populated by the middlewareexport const prerender = true) — Clerk middleware skips them; use client-side hooks in islandsuseAuth() and other hooks from @clerk/astro/reactRequest → clerkMiddleware() → SSR page → Astro.locals.auth()
↓
Island (.client) → useAuth() hook
import { defineConfig } from 'astro/config'
import clerk from '@clerk/astro'
export default defineConfig({
integrations: [clerk()],
output: 'server',
})
import { clerkMiddleware } from '@clerk/astro/server'
export const onRequest = clerkMiddleware()
The middleware populates Astro.locals.auth(). Each page and API route checks for itself. createRouteMatcher was removed in @clerk/astro 4.0 (deprecated in 3.x). Don't add it. Existing uses must move to page and API-route checks before upgrading. See the Astro clerkMiddleware() reference.
---
const { userId, orgId } = Astro.locals.auth()
if (!userId) return Astro.redirect('/sign-in')
---
<h1>Dashboard</h1>
// src/pages/api/data.ts
import type { APIRoute } from 'astro'
export const GET: APIRoute = ({ locals }) => {
const { userId } = locals.auth()
if (!userId) return new Response('Unauthorized', { status: 401 })
return Response.json({ userId })
}
| Symptom | Cause | Fix |
|---|---|---|
Astro.locals.auth is undefined | Missing middleware | Add clerkMiddleware to src/middleware.ts |
| Auth works in dev but not production | output: 'static' globally | Set output: 'server' or hybrid for protected pages |
| Static page has no auth | Prerendered pages skip middleware | Use export const prerender = false or move to island |
| Island not reactive to sign-in | Missing client:load directive | Add client:load to the island component |
| What | Import From |
|---|---|
clerkMiddleware, clerkClient | @clerk/astro/server |
useAuth, UserButton (no useUser) | @clerk/astro/react |
$userStore, $authStore | @clerk/astro/client |
Astro components (<SignIn>, etc.) | @clerk/astro/components |
# .env
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
Astro uses PUBLIC_ prefix for client-exposed variables (not NEXT_PUBLIC_).
clerk-setup - Initial Clerk installclerk-custom-ui - Custom flows & appearanceclerk-orgs - B2B organizations---
name: clerk-astro-patterns
description: 'Astro patterns with Clerk — middleware, SSR pages, island components,
API routes, static vs SSR rendering. Triggers on: astro clerk, clerk astro middleware,
astro protected page, clerk island component, astro API route auth, clerk astro
SSR.'
license: MIT
allowed-tools: WebFetch
metadata:
author: clerk
version: 1.0.0
---
# Astro Patterns
SDK: `@clerk/astro` v4+. Requires Astro 5+.
## What Do You Need?
| Task | Reference |
|------|-----------|
| Configure middleware (bare `clerkMiddleware()`, checks per page) | references/middleware.md |
| Protect SSR pages | references/ssr-pages.md |
| Use Clerk in island components | references/island-components.md |
| Auth in API routes | references/api-routes.md |
| Use Clerk with React in Astro | references/astro-react.md |
## Mental Model
Astro has two rendering modes per page: **SSR** and **static prerender**. Clerk works differently in each:
- **SSR pages** — use `Astro.locals.auth()` which is populated by the middleware
- **Static pages** (`export const prerender = true`) — Clerk middleware skips them; use client-side hooks in islands
- **Islands** — React/Vue/Svelte components; use `useAuth()` and other hooks from `@clerk/astro/react`
```
Request → clerkMiddleware() → SSR page → Astro.locals.auth()
↓
Island (.client) → useAuth() hook
```
## Setup
### astro.config.mjs
```ts
import { defineConfig } from 'astro/config'
import clerk from '@clerk/astro'
export default defineConfig({
integrations: [clerk()],
output: 'server',
})
```
### src/middleware.ts
```ts
import { clerkMiddleware } from '@clerk/astro/server'
export const onRequest = clerkMiddleware()
```
The middleware populates `Astro.locals.auth()`. Each page and API route checks for itself. `createRouteMatcher` was removed in `@clerk/astro` 4.0 (deprecated in 3.x). Don't add it. Existing uses must move to page and API-route checks before upgrading. See the [Astro `clerkMiddleware()` reference](https://clerk.com/docs/reference/astro/clerk-middleware).
## SSR Page Auth
```astro
---
const { userId, orgId } = Astro.locals.auth()
if (!userId) return Astro.redirect('/sign-in')
---
<h1>Dashboard</h1>
```
## API Route Auth
```ts
// src/pages/api/data.ts
import type { APIRoute } from 'astro'
export const GET: APIRoute = ({ locals }) => {
const { userId } = locals.auth()
if (!userId) return new Response('Unauthorized', { status: 401 })
return Response.json({ userId })
}
```
## Common Pitfalls
| Symptom | Cause | Fix |
|---------|-------|-----|
| `Astro.locals.auth` is undefined | Missing middleware | Add `clerkMiddleware` to `src/middleware.ts` |
| Auth works in dev but not production | `output: 'static'` globally | Set `output: 'server'` or `hybrid` for protected pages |
| Static page has no auth | Prerendered pages skip middleware | Use `export const prerender = false` or move to island |
| Island not reactive to sign-in | Missing `client:load` directive | Add `client:load` to the island component |
## Import Map
| What | Import From |
|------|-------------|
| `clerkMiddleware`, `clerkClient` | `@clerk/astro/server` |
| `useAuth`, `UserButton` (no `useUser`) | `@clerk/astro/react` |
| `$userStore`, `$authStore` | `@clerk/astro/client` |
| Astro components (`<SignIn>`, etc.) | `@clerk/astro/components` |
## Env Variables
```
# .env
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
```
Astro uses `PUBLIC_` prefix for client-exposed variables (not `NEXT_PUBLIC_`).
## See Also
- `clerk-setup` - Initial Clerk install
- `clerk-custom-ui` - Custom flows & appearance
- `clerk-orgs` - B2B organizations
## Docs
[Astro SDK](https://clerk.com/docs/astro/getting-started/quickstart)