How to Use Clerk with Astro: Complete Guide
Step-by-step guide to integrating Clerk authentication with your Astro website. Installation, configuration, and best practices.
Clerk is a complete user management platform with pre-built UI components, social login, multi-factor auth, and organization management. It has an official Astro integration that makes adding authentication to your site surprisingly straightforward. Instead of building login forms and session management from scratch, you drop in Clerk's components and focus on your actual product.
This guide was checked against the official Clerk Astro quickstart and the current Astro docs on 2026-05-29. Versions current at that date: @clerk/astro 3.3.2 (peer dependency astro ^4.15.0 or ^5 or ^6), Astro 6.4.2, and @astrojs/node 10.1.2 (peer dependency astro ^6.3.0). Pin whatever @latest resolves to when you install.
Prerequisites
- Node.js 22.12.0 or newer (this is the engine Astro 6 requires; older Astro 4/5 projects ran on Node 18, but the current line needs 22.12+)
- An Astro project (
npm create astro@latest) - A Clerk account (the Free plan covers up to 50,000 monthly retained users at clerk.com, increased from the old 10,000 limit)
Installation
Clerk needs on-demand (server) rendering, so the official quickstart installs the Clerk SDK together with a server adapter. The Node adapter is the simplest for self-hosting:
npm install @clerk/astro@latest @astrojs/node@latest
If you deploy to Netlify, Vercel, or Cloudflare instead, install that adapter rather than @astrojs/node. Use your project's package manager if it is not npm (pnpm, yarn, or bun).
Then wire both into your Astro config. The integration goes in integrations, and the adapter plus output: "server" turns on server rendering:
// astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
import clerk from "@clerk/astro";
export default defineConfig({
integrations: [clerk()],
adapter: node({ mode: "standalone" }),
output: "server",
});
A note on output modes. Astro v5 removed output: "hybrid" and merged it into output: "static" (now the default), so any older guide that tells you to set output: "hybrid" is out of date. With the default static mode you can still opt individual pages into server rendering with export const prerender = false. Because Clerk's middleware needs to run on requests, the Clerk quickstart sets output: "server" so every route is server-rendered by default. If you only have a few protected pages, you can leave the default static output and add export const prerender = false to just those pages instead, as long as your adapter is configured.
Configuration
Get your API keys from the Clerk dashboard. Go to your application, then API Keys.
Add them to your .env file (the Clerk quickstart names the file .env.local; either works as long as Astro loads it):
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxx
CLERK_SECRET_KEY=sk_test_xxxxxxxxxxxxx
PUBLIC_CLERK_SIGN_IN_URL=/sign-in
PUBLIC_CLERK_SIGN_UP_URL=/sign-up
The publishable key must use the PUBLIC_ prefix so Astro exposes it to the client. The secret key has no prefix and must never reach the browser.
Basic Usage
Clerk provides pre-built UI components for sign-in, sign-up, and user management. The components imported from @clerk/astro/components are Astro components, so you use them directly with no client:* hydration directive. Here is a sign-in page:
---
// src/pages/sign-in.astro
import { SignIn } from "@clerk/astro/components";
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Sign In">
<main class="flex items-center justify-center min-h-screen">
<SignIn />
</main>
</BaseLayout>
And a sign-up page:
---
// src/pages/sign-up.astro
import { SignUp } from "@clerk/astro/components";
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Sign Up">
<main class="flex items-center justify-center min-h-screen">
<SignUp />
</main>
</BaseLayout>
Do not add client:load to these. Astro's own framework component directives (client:load, client:visible, and so on) apply to React, Vue, Svelte, and similar UI-framework components. The @clerk/astro/components exports are first-class Astro components, so they render and hydrate without a directive. Clerk does also ship React versions through @clerk/astro/react for projects that mount Clerk inside a React island; those, being React components, would use a client:* directive. For a plain Astro site, prefer the @clerk/astro/components set.
Protecting Routes
First, register Clerk's middleware. The minimal setup is one line in src/middleware.ts:
// src/middleware.ts
import { clerkMiddleware } from "@clerk/astro/server";
export const onRequest = clerkMiddleware();
To actually gate routes, pass a callback and match the protected paths. The current Clerk docs read auth with isAuthenticated and bounce unauthenticated visitors with redirectToSignIn():
// src/middleware.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/astro/server";
const isProtectedRoute = createRouteMatcher(["/dashboard(.*)", "/settings(.*)"]);
export const onRequest = clerkMiddleware((auth, context) => {
const { isAuthenticated, redirectToSignIn } = auth();
if (!isAuthenticated && isProtectedRoute(context.request)) {
return redirectToSignIn();
}
});
If you are upgrading an older project, note that the previous pattern destructured userId and called context.redirect("/sign-in") by hand. The isAuthenticated boolean plus redirectToSignIn() is the shape the docs use now, and redirectToSignIn() sends users to your configured sign-in URL and returns them afterward.
Accessing User Data
In any server-rendered page, read the current auth state from Astro.locals.auth(). To fetch the full user record, use Clerk's backend client through clerkClient rather than hand-rolling a fetch to the Clerk API. The client handles the base URL, headers, and your secret key for you:
---
// src/pages/dashboard.astro
export const prerender = false;
import { clerkClient } from "@clerk/astro/server";
import BaseLayout from "../layouts/BaseLayout.astro";
const { isAuthenticated, userId } = Astro.locals.auth();
if (!isAuthenticated) {
return Astro.redirect("/sign-in");
}
const user = await clerkClient(Astro).users.getUser(userId);
---
<BaseLayout title="Dashboard">
<main class="max-w-4xl mx-auto py-12">
<h1>Welcome, {user.firstName}!</h1>
<p>Email: {user.emailAddresses[0]?.emailAddress}</p>
</main>
</BaseLayout>
Note that the backend client returns camelCase fields (firstName, emailAddresses, emailAddress), unlike the snake_case JSON you would get from a raw REST call. Pass the page or middleware context to clerkClient(...) so it can read request-scoped config.
Adding User Profile Management
Clerk has a pre-built user profile component:
---
// src/pages/settings.astro
export const prerender = false;
import { UserProfile } from "@clerk/astro/components";
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Settings">
<main class="max-w-4xl mx-auto py-12">
<UserProfile />
</main>
</BaseLayout>
This renders a full profile management UI where users can update their name, email, password, connected accounts, and active sessions.
Navigation with Auth State
Show different navigation items based on whether the user is signed in:
---
// src/components/Header.astro
import { SignedIn, SignedOut, UserButton } from "@clerk/astro/components";
---
<header class="flex items-center justify-between p-4">
<a href="/" class="text-xl font-bold">My Site</a>
<nav class="flex items-center gap-4">
<SignedIn>
<a href="/dashboard">Dashboard</a>
<UserButton />
</SignedIn>
<SignedOut>
<a href="/sign-in">Sign In</a>
<a href="/sign-up" class="bg-blue-600 text-white px-4 py-2 rounded">
Sign Up
</a>
</SignedOut>
</nav>
</header>
SignedIn renders its children only while a user is signed in, and SignedOut only while signed out, so this header swaps between the two states automatically. There is also a <SignedIn> and <SignedOut> style helper named <Show> (for example <Show when="signed-in">) plus SignInButton and SignUpButton if you prefer Clerk's own buttons over plain anchors.
Production Tips
Use the
@clerk/astro/componentsset, not a hydration directive. These are Astro components and work withoutclient:load. Reach for the React versions under@clerk/astro/react(which do take aclient:*directive) only when you are mounting Clerk inside an existing React island.
Configure social login providers in the Clerk dashboard. Adding Google, GitHub, or Apple sign-in takes about two minutes per provider. No code changes needed.
Set up webhooks for user events. Clerk can send webhooks when users sign up, update their profile, or delete their account. Use these to sync user data with your database.
Customize the Clerk appearance. Pass a custom theme to match your brand colors and typography. Clerk components support deep customization through their appearance prop.
Use organizations for team features. Clerk's organization management lets users create teams, invite members, and assign roles. All built-in, no custom code needed.
Alternatives to Consider
- Lucia if you want a lightweight, self-hosted auth library with full control over the implementation. More work, but no vendor lock-in.
- Supabase Auth if you are already using Supabase for your database. Auth is built in and free.
- Auth0 if you need enterprise features like SSO, compliance certifications, and advanced security policies.
Common Errors and Fixes
Auth state never shows or middleware never runs. Clerk depends on on-demand rendering. If you left the project on the default static output with no adapter, the middleware cannot execute on requests. Add an adapter (@astrojs/node for self-hosting, or your platform's adapter) and either set output: "server" or mark the relevant pages with export const prerender = false. Astro requires an adapter for any on-demand rendering.
Build fails after upgrading with an error about output: "hybrid". Astro v5 removed hybrid and folded it into static. Delete the output: "hybrid" line. The default static mode already lets individual pages opt into server rendering with export const prerender = false.
The publishable key is undefined in the browser. Astro only exposes environment variables prefixed with PUBLIC_ to client code. The key must be named PUBLIC_CLERK_PUBLISHABLE_KEY. The secret key stays unprefixed (CLERK_SECRET_KEY) and server-only; never expose it.
Astro.locals.auth() is undefined or throws. This means the Clerk middleware did not run for that request. Confirm src/middleware.ts exports onRequest = clerkMiddleware() (the file must be src/middleware.ts, or middleware.ts at the project root if you have no src/), and that the page is rendered on demand rather than prerendered.
firstName comes back as undefined when you expected first_name. The clerkClient(...).users.getUser() backend client returns camelCase fields. Use user.firstName and user.emailAddresses[0]?.emailAddress. The snake_case shape only appears if you call the raw REST API directly, which you should not need to do.
Node version errors on install or build. Astro 6 requires Node 22.12.0 or newer. If your CI or local machine is on Node 18 or 20, the install or build can fail. Upgrade Node, or pin to an older Astro line if you must stay on an older runtime.
Official Docs and Examples
- Clerk Astro quickstart: https://clerk.com/docs/quickstarts/astro
- Clerk Astro SDK overview: https://clerk.com/docs/references/astro/overview
- Clerk
clerkMiddlewarereference (route protection): https://clerk.com/docs/references/astro/clerk-middleware - Official Clerk Astro example repository: https://github.com/clerk/clerk-astro-quickstart
- Astro on-demand rendering and adapters guide: https://docs.astro.build/en/guides/on-demand-rendering/
Wrapping Up
Clerk is the fastest way to add production-ready authentication to an Astro site. The pre-built components handle the UI, the middleware handles route protection, and the dashboard handles configuration. You can go from zero to fully authenticated app in under an hour.
Sources
All versions and facts below were checked on 2026-05-29.
@clerk/astroversion 3.3.2 and its Astro peer dependency: https://registry.npmjs.org/@clerk/astro/latest- Astro version 6.4.2 and Node engine requirement (>=22.12.0): https://registry.npmjs.org/astro/latest
@astrojs/nodeversion 10.1.2 and its Astro peer dependency: https://registry.npmjs.org/@astrojs/node/latest- Install command,
.envkey names,astro.config.mjswith adapter plusoutput: "server", one-line middleware, and that components need noclient:*directive: https://clerk.com/docs/quickstarts/astro Astro.locals.auth()andclerkClient(context).users.getUser(userId)for server-side auth and user data: https://clerk.com/docs/references/astro/overview- Route protection with
createRouteMatcher,isAuthenticated, andredirectToSignIn(): https://clerk.com/docs/references/astro/clerk-middleware SignedIn,SignedOut,UserButton,SignInButton,SignUpButton,UserProfilecomponent names and behavior: https://clerk.com/docs/astro/reference/components/overview- Default
output: "static",output: "server", and that an adapter is required for on-demand rendering: https://docs.astro.build/en/guides/on-demand-rendering/ - Removal of
output: "hybrid"in Astro v5 (merged intostatic): https://docs.astro.build/en/guides/upgrade-to/v5/ - Free plan limit of 50,000 monthly retained users: https://clerk.com/pricing
- Official Clerk Astro example/starter repository: https://github.com/clerk/clerk-astro-quickstart
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.