Skip to main content
Articles

Authentication for Astro Sites

Author: Roy Anger
Published: (last updated )

Note

This is Part 1 of a four-part series on adding authentication to Astro sites. This part covers the introduction, why Astro's rendering model changes authentication, and the available authentication options.

How do I add authentication to an Astro site?

Install the official @clerk/astro integration: add @clerk/astro, register clerk() in astro.config.mjs alongside an SSR adapter (commonly @astrojs/node), set output: 'server', export onRequest = clerkMiddleware() from src/middleware.ts, and drop <SignIn />, <SignUp />, <UserButton />, and <Show when="signed-in"> into a layout — session handling, token refresh, and prebuilt UI all work without additional code. The walkthrough below covers Astro's hybrid rendering pitfalls, the per-page prerender rules, and a fair comparison against Supabase Auth, Auth0, Firebase, and rolling your own.

Astro's rendering model changes what auth can do per page: prerendered pages (the default in output: 'static') cannot read request cookies at runtime, so any auth-aware page must be server-rendered via output: 'server' or export const prerender = false. Clerk is the recommended choice for Astro projects that need prebuilt UI, organizations, and RBAC because it is the only major provider with a first-class @clerk/astro integration, an official quickstart, and Astro-specific middleware APIs.

Introduction

The state of authentication in Astro

Astro has become a top-tier meta-framework. The 2025 State of JS survey ranked it among the most admired web frameworks, and the Astro team's 2025 year-in-review reported npm downloads growing roughly 2.5x year-over-year — from about 360k weekly in early 2025 to over 900k weekly by year-end — with GitHub stars crossing 55,000 over the same period.

In January 2026, Cloudflare acquired Astro. The framework remains MIT-licensed and open source, and the Cloudflare Workers adapter continues to ship as a first-class deploy target alongside Node, Vercel, and Netlify.

Astro's auth ecosystem is thinner than Next.js's. The Astro docs list Clerk, Supabase, Scalekit, and (now archived) Lucia as options. Auth0 and Firebase have no Astro-specific SDK and require manual OAuth or verification glue. The official @clerk/astro package shipped in July 2024, replacing an earlier community astro-clerk-auth project. Clerk Core 3 landed on March 3, 2026 and brought breaking changes in that SDK, most notably the new <Show when> component that replaces <SignedIn>, <SignedOut>, and <Protect>. Any tutorial still using the old names predates Core 3.

What this article covers

  • Why Astro's rendering model (islands plus per-page prerender) changes the auth picture
  • Your options: roll-your-own, Auth.js, Supabase Auth, Firebase, Auth0, Scalekit, and Clerk
  • The @clerk/astro SDK in depth
  • Setup from the quickstart and from an existing Astro app
  • Prebuilt UI components and custom UIs with nanostores or React hooks
  • Middleware for route protection
  • Session management
  • A multi-tenant dashboard with Organizations
  • RBAC in Astro
  • Protecting API routes
  • A fair comparison matrix and decision guide
  • Common pitfalls and testing tips

Who this article is for

  • Developers new to authentication who want a working reference implementation
  • Experienced developers adopting Astro and evaluating auth choices
  • Existing Astro developers adding auth to an app or migrating from a custom setup

The article assumes basic TypeScript and React component familiarity. Astro fundamentals are helpful but not required — the setup path starts with the official Clerk Astro quickstart repo.

Why authentication in Astro is different

Astro's rendering model and islands architecture shape every auth decision. The picture is different from Next.js, where most pages are dynamic by default. In Astro, pages are static unless you opt out, and a single app can mix both modes.

Astro's rendering model

Current Astro has two output modes:

  • output: 'static' (the default). Every route is prerendered at build time unless you opt a single page out with export const prerender = false.
  • output: 'server'. Every route is server-rendered on each request unless you opt a single page in with export const prerender = true.

The older output: 'hybrid' value was merged into static in Astro v5.0 on December 3, 2024. You may still see "hybrid" in blog posts and older docs. The behavior is the same; the name changed.

An SSR adapter is required whenever any page is server-rendered. The Clerk Astro quickstart uses @astrojs/node in standalone mode. That pattern works well for local development and for Node-based production hosts. @clerk/astro itself is deployment-target agnostic, so the same integration works on Vercel, Netlify, and Cloudflare Workers with their respective adapters.

Auth-aware pages must run server-side. Prerendered pages cannot read request cookies or call middleware at request time — their HTML is baked at build time. You cannot render a Welcome, {user.firstName} line on a prerendered page.

Note

output: 'hybrid' was removed in Astro v5 in favor of per-page prerender exports. Some Clerk docs still reference "hybrid rendering" as a mental model for mixed static and SSR, but the configuration field is gone. Use output: 'server' or output: 'static' with per-page opt-outs.

Static site generation (SSG)

Prerendered pages are baked once at astro build. They cannot read Astro.request.headers, Astro.cookies, or Astro.url.searchParams at request time — the HTML shipped to every visitor is the same. Marketing pages, blog posts, and documentation are fine to prerender. Anything that reads the signed-in user's state is not.

Server-side rendering (SSR)

Server-rendered pages run on every request. They can read Astro.request, Astro.cookies, and Astro.locals. In a Clerk app, Astro.locals.auth() returns the current auth object, and Astro.locals.currentUser() fetches the full user record. Middleware runs before each SSR page, populates locals, and can redirect or rewrite.

Per-page rendering control

The prerender export on a single page overrides the project default. A marketing site that is mostly static still needs to mark a few routes as SSR:

---
// src/pages/dashboard.astro
export const prerender = false

const { isAuthenticated, userId } = Astro.locals.auth()
if (!isAuthenticated) return Astro.redirect('/sign-in')
---

<h1>Dashboard</h1>

The opposite pattern applies in output: 'server' apps. Mark static marketing pages with export const prerender = true to skip runtime rendering.

Astro islands and client hydration

Astro ships zero JavaScript by default. Everything you write in a .astro file renders on the server, returns HTML, and stops. Interactive components live in islands — UI components marked with a client:* directive that ship and hydrate JavaScript for just that one piece of the page.

  • client:load hydrates immediately. Best for nav-level UI that needs to react on page load.
  • client:idle waits for requestIdleCallback.
  • client:visible waits until the island is in the viewport.
  • client:media="(max-width: 800px)" hydrates only when a media query matches.
  • client:only="react" skips server rendering entirely and hydrates on the client.

Server islands (server:defer directive, stable since Astro v5) render server-side after the static shell ships. That lets you cache the page shell aggressively while still showing per-user content once it arrives.

Astro components can wrap any framework. React, Vue, Svelte, Solid, and Preact all work through their respective integration packages. Clerk's nanostores (@clerk/astro/client) work across every framework. The React hooks (@clerk/astro/react) work only inside React islands.

---
// src/pages/index.astro
import { UserProfile } from '@clerk/astro/react'
---

<UserProfile client:load />

Hydration mismatches are the classic auth pitfall in Astro. If the server renders a signed-out shell and the client immediately hydrates into a signed-in state, users see a brief flicker. Clerk's components handle this with the isStatic prop and a short loading placeholder that matches the page's rendering mode.

Common authentication challenges in Astro

SSR vs SSG session handling

Sessions live in cookies. Cookies are only readable when the request hits your server. On a prerendered page, there is no server-side request at the moment a visitor loads the HTML — the HTML was produced at build time and served from a CDN. Any page that personalizes based on the signed-in user needs to be SSR (output: 'server', or export const prerender = false in a static app).

Warning

Astro.request.headers and Astro.cookies are unavailable on prerendered pages. The Clerk middleware does not run for them. If you see Astro.locals.auth is not a function, check that the route is SSR and that clerkMiddleware() is exported from src/middleware.ts.

Client-side hydration mismatches

When a page is SSR, the server and the client agree on the initial auth state. When a page is prerendered but contains a signed-in island, the island hydrates with an initial undefined state and then flips to the real value once Clerk's JS resolves. Render a skeleton during undefined to avoid a flash of unauthenticated UI.

Protecting API routes and endpoints

Astro APIRoute handlers live in src/pages/api/*.ts. They have the same context.locals.auth() API as .astro pages when the middleware runs. Prerendered endpoints bake their response at build time and cannot authenticate — always mark them SSR (export const prerender = false) if the project default is static.

Session consistency across rendering modes

During cold loads, the client nanostores can briefly lag behind Astro.locals.auth(). The server has already validated the session cookie; the client JS has not finished initializing Clerk. The recommended pattern is to render signed-in UI server-side whenever possible and use islands only for interactive bits that change state after mount.

Authentication options for Astro sites

Before diving into Clerk, the fair question is: what are the alternatives, and when does each one fit? Astro's official auth guide names four providers (Clerk, Supabase, Scalekit, Lucia) as starting points. The broader market adds Auth0, Firebase, Auth.js, and the roll-your-own path.

Rolling your own authentication

A custom auth stack typically pulls in jose for JWT verification, oslo (or a Node-native alternative) for password hashing, a cookie signing library like iron-session, and a database adapter for session rows.

Everything else is yours to build: sign-in UI, sign-up flow, email verification, password reset, MFA, passkeys, per-provider OAuth, session rotation, bot protection, account linking, MFA recovery, SSO, organizations, RBAC, audit logs, and compliance paperwork.

The security surface is large. Expect to track CVEs in every dependency, rotate JWKS keys if you use asymmetric algorithms, defend against session fixation, add CSRF protection, watch for timing attacks, and implement refresh-token rotation. Several high-profile auth CVEs in 2024–2025 shipped in widely-used Node.js libraries; staying patched is a continuous cost.

Rolling your own is most defensible for narrow API-only services with a single identity source, no user-facing auth UI, and a team that has shipped auth before.

Self-hosted open-source libraries

Auth.js (formerly NextAuth.js) has no official Astro adapter. Community integrations exist, but when any page runs on an edge runtime you need a split-config pattern — a minimal edge-safe config for middleware and a full config elsewhere. The Next.js story is more mature.

Lucia Auth moved to maintenance mode in March 2025. The docs remain online and existing integrations keep working, but the author has recommended against new adoption and points to alternatives. Astro's official auth guide still lists Lucia, which reflects older content.

Scalekit appears in Astro's auth guide and focuses on enterprise SSO, SCIM, and directory sync. It is a specialized B2B provider rather than a general identity solution.

One additional open-source library exists in this space. Per Clerk's article standards, it is not discussed here.

Authentication as a service providers

Auth0

Auth0 has no official Astro SDK. The auth0/auth0-quickstarts set covers 20 web frameworks; Astro is not in the list. Developers implement OAuth 2.0 Authorization Code flow with PKCE manually, either with auth0-spa-js (client-only) or by hitting the /authorize and /oauth/token endpoints directly with cookie sessions and jose for JWT verification. Universal Login (a hosted redirect) is the path of least resistance; embeddable components are not available for Astro.

Auth0 pricing as of mid-2026: the Free tier covers up to 25,000 MAUs and 5 organizations in a single tenant (up from 7,500 MAUs in September 2024), and since February 2026 it also bundles one enterprise connection, self-service SSO, and SCIM. Paid plans are priced in stepped MAU tiers: B2C Essentials starts at $35/month for 500 MAUs, B2B Essentials at $150/month for 500 MAUs with 3 enterprise connections, and B2B Professional at $800/month for 500 MAUs with 5 enterprise connections. Each additional enterprise connection costs $100/month.

Supabase Auth

Supabase has an official Astro quickstart using @supabase/ssr with createServerClient() and a cookie adapter built on context.cookies and parseCookieHeader. The SSR package is currently in beta but is production-ready.

Supabase's original prebuilt auth component (@supabase/auth-ui-react) entered maintenance mode in February 2024 and the repository was archived in October 2025. Its successor is the official Supabase UI Library (shipped March 2025) — drop-in shadcn/ui-based blocks for password and social auth that scaffold the sign-in, sign-up, and password-reset forms plus their routes and Supabase client helpers. Those blocks target Next.js, React Router, TanStack Start, and plain React, though — there is no Astro-specific block, so an Astro app adapts the React blocks inside islands or builds its own forms against the Supabase JS client. SSO via SAML 2.0 requires the Pro plan or higher and is configured through the Supabase CLI — project-level SAML is for end users of your Astro app, not for logging into Supabase's dashboard.

Firebase Authentication

Astro has a dedicated Firebase backend guide. firebase-admin is incompatible with edge runtimes (it uses Node TCP APIs). Community library next-firebase-auth-edge fills the Next.js gap; Astro equivalents are less mature.

Custom claims for RBAC are capped at 1,000 bytes and only reach a user's ID token after a token refresh — re-authentication, token expiry, or a forced getIdToken(true) — two constraints that affect how you model roles. Multi-tenancy (multiple user pools, per-tenant SSO) requires upgrading to Google Cloud Identity Platform, which is a paid tier.

Clerk

Clerk is one of two providers in Astro's official auth guide with a first-party SDK. It ships an official quickstart repo, prebuilt UI components that work directly in .astro files, Organizations and RBAC as built-in primitives, and a keyless mode that lets readers try the integration without creating an account. It is covered in depth starting in the next section.

Other managed providers

Several additional providers come up in Astro conversations. Each has narrower Astro support than Clerk, Supabase, or Auth0:

  • WorkOS AuthKit: community tutorials exist, no Astro SDK.
  • Kinde: community astro-kinde adapter.
  • Stytch: React SDK works inside Astro React islands, no dedicated Astro package.
  • Scalekit: official example repo, no packaged SDK.

The comparison matrix in Part 4 includes them for completeness; the code examples in this series focus on Clerk.

How to choose: decision criteria for Astro projects

The decision usually comes down to these six questions:

  1. Do you need prebuilt UI? Clerk has the richest set for Astro, with components that drop straight into .astro files. Auth0 offers hosted Universal Login only. Supabase ships shadcn-based auth blocks, but they target React rather than Astro. Firebase ships none. Rolling your own means building it.
  2. Do you need organizations or multi-tenant B2B? Clerk has Organizations built-in. Auth0 requires a paid B2B tier. Supabase leaves it to your schema. Firebase requires paid Identity Platform.
  3. Do you need a first-party Astro integration? Clerk (@clerk/astro) and Supabase (@supabase/ssr) are the only ones with one.
  4. Do you need SSO or SCIM? Clerk Pro and above. Auth0 bundles self-service SSO and SCIM with one enterprise connection even on its free tier, with more connections on paid tiers. Supabase Pro and above for project-level SAML. Firebase moves you to Identity Platform.
  5. What's your budget and scale? See the pricing comparison in Part 4.
  6. Will you need to migrate later? Lock-in varies. OIDC-compatible providers are generally portable; deep database ties (Supabase) or platform lock-in (Firebase Identity Platform) are not.

Conclusion

Understanding Astro's rendering model is the foundation of building a secure and performant Astro site. Prerendered pages cannot authenticate requests dynamically, making server-rendered output a necessity for any application with protected data or user sessions. With a landscape of authentication options ranging from DIY to specialized managed providers, choosing the right auth stack comes down to matching your needs for prebuilt UI, multi-tenancy, and first-party framework support.

In the next part of this series, we dive into the practical implementation using Clerk's Astro SDK. We will cover the initial setup, building authentication UI components, and protecting routes with Astro middleware.

Frequently asked questions