# Authentication for Astro Sites - Part 2

> Part 2 of 4. Start with [Authentication for Astro Sites](https://clerk.com/articles/authentication-for-astro-sites.md).

> This is Part 2 of a four-part series on adding authentication to Astro sites. This part focuses entirely on the practical implementation: the Clerk Astro SDK overview, the initial setup guide, building authentication UI, and protecting routes with Astro middleware.

## The Clerk Astro SDK: an overview

Before walking through setup, a quick map of what `@clerk/astro` provides and how its pieces fit together.

### What the @clerk/astro integration provides

The package exposes several entry points, each scoped to a different runtime:

- `@clerk/astro` — the Astro integration itself. Registers hooks, injects the `clerk()` integration into the build, and wires types.
- `@clerk/astro/components` — Astro-native UI components (`<SignIn />`, `<SignUp />`, `<UserButton />`, `<UserProfile />`, `<OrganizationSwitcher />`, `<OrganizationProfile />`, `<OrganizationList />`, `<CreateOrganization />`, `<Show />`, and the button components).
- `@clerk/astro/react` — React versions of the Clerk components plus the `useAuth` hook for use inside React islands. Other reactive state (user, organization, session, sign-in/up resources) comes from the nanostores in `@clerk/astro/client`.
- `@clerk/astro/client` — framework-agnostic nanostores (`$authStore`, `$userStore`, `$organizationStore`, `$clerkStore`, `$sessionStore`, `$sessionListStore`, `$signInStore`, `$signUpStore`).
- `@clerk/astro/server` — backend APIs: `clerkMiddleware`, `clerkClient`, `createRouteMatcher`.
- `@clerk/astro/webhooks` — `verifyWebhook()` for handling Clerk [webhooks](https://clerk.com/glossary.md#webhook).
- `@clerk/astro/types` — TypeScript types including `ClerkAuthorization` for type-safe role and permission checks.

The current version is `@clerk/astro` 3.0.16 (pinned by the official quickstart; [3.4.9 is the latest on npm](https://www.npmjs.com/package/@clerk/astro)). Minimum requirements: [Node.js 20.9.0 or higher, Astro 4.15.0 or higher (supports v4, v5, and v6)](https://www.npmjs.com/package/@clerk/astro). [Astro v6](https://astro.build/blog/astro-6/) itself [requires Node 22.12 or higher](https://docs.astro.build/en/guides/upgrade-to/v6/).

`@clerk/astro` replaces the earlier community package `astro-clerk-auth`. See the [migration guide](https://clerk.com/docs/guides/development/migrating/astro-community-sdk.md) for the upgrade path.

### Supported Astro features

#### SSR and SSG compatibility

The SDK supports both rendering modes. Control components (`<Show />`) accept an `isStatic` prop to indicate when the enclosing page is prerendered. When `isStatic={true}`, the component reads auth state from the client nanostores after hydration. When `isStatic={false}` (the default), it reads from `Astro.locals` on the server.

Set it according to the page's rendering mode:

- Project is `output: 'server'` and the page does not opt out: default is correct, omit the prop.
- Project is `output: 'server'` and the page is marked `export const prerender = true`: pass `isStatic={true}`.
- Project is `output: 'static'` and the page is marked `export const prerender = false`: default is correct.
- Project is `output: 'static'` and the page is prerendered: pass `isStatic={true}`.

#### Middleware support

`clerkMiddleware()` returns an Astro middleware function in the canonical shape. Export it as `onRequest` from `src/middleware.ts`, or compose it with other middleware using `sequence()` from `astro:middleware`.

#### Server islands and view transitions

`server:defer` server islands work with Clerk's server-rendered components. Mount them normally and the lazy server fetch will have access to `Astro.locals.auth()`.

The historical issue where Clerk components failed to load until soft navigation occurred in older versions of `@clerk/astro` was fixed in v2.17.2, so it is no longer a concern on v3.x. Any script that reinitializes on navigation should listen to the `astro:page-load` event rather than `DOMContentLoaded` — module scripts run only once per full page load, not on `ClientRouter` transitions.

### Prebuilt components and hooks

The Astro components live in `@clerk/astro/components` and render server-side:

- `<SignIn />`, `<SignUp />` — full sign-in and sign-up UIs with email, OAuth, passkeys, and MFA out of the box.
- `<UserButton />` — avatar dropdown with profile, sessions, and sign-out.
- `<UserProfile />` — full profile management UI.
- `<OrganizationSwitcher />`, `<OrganizationProfile />`, `<OrganizationList />`, `<CreateOrganization />` — Organizations UI.
- `<Show />` — conditional rendering based on auth state, role, permission, plan, or feature.
- `<SignInButton />`, `<SignUpButton />`, `<SignOutButton />` — unstyled trigger buttons.

The React subpath (`@clerk/astro/react`) exposes the same components in React form plus the `useAuth` hook. Use it inside React islands. For anything beyond `useAuth` (user, organization, session, sign-in/up resources), subscribe to the matching nanostore from `@clerk/astro/client`.

The nanostores let any framework island read the same state: `$authStore` for auth primitives, `$userStore` for the full user object, `$clerkStore` for the raw Clerk instance, `$sessionStore` for session details, `$organizationStore` for the active org. Loading states are `undefined`, signed-out states are `null`, and signed-in states are resolved objects.

### TypeScript support

Add the Clerk types to your `env.d.ts` so `Astro.locals.auth()` and `Astro.locals.currentUser()` are typed, and so custom roles and permissions are checked at compile time:

```ts
/// <reference path="../.astro/types.d.ts" />
/// <reference types="@clerk/astro/env" />

declare global {
  interface ClerkAuthorization {
    role: 'org:admin' | 'org:member' | 'org:billing_manager'
    permission: 'org:billing:manage' | 'org:posts:publish'
  }
}

export {}
```

> Augmenting `ClerkAuthorization` makes `has({ role })` and `<Show when={{ role }}>` refuse to compile with an invalid role string. If you add a new role later, the type error points to every guard that needs to be updated.

The types live on `@clerk/astro/types` in Core 3. Earlier versions exposed them through the main entry point.

## Getting started: adding Clerk to an Astro site

Two paths: start from the official quickstart repo (fastest), or add Clerk to an existing Astro app. Both end in the same place.

### Prerequisites

- [ ] Node.js 20.9 or higher (22 or higher for Astro v6)
- [ ] A Clerk account, or skip the keys for now and use keyless mode
- [ ] An Astro project with an SSR adapter (the quickstart uses `@astrojs/node`)

### Keyless mode (optional, for trial)

Clerk Core 3 introduced keyless mode. You can add `@clerk/astro` without any API keys and get a temporary sandbox instance — useful for quick tutorials and evaluation. Once you create a Clerk account, copy the publishable and secret keys into `.env` to migrate.

Keyless mode is not a production path. Use it to kick the tires, then wire real keys.

### Option 1: starting from the Clerk Astro quickstart

The quickstart is the fastest way to see a working Clerk + Astro app. It includes the integration, middleware, sign-in and sign-up pages, a protected dashboard, and the `<UserButton />` in the header.

#### Cloning the quickstart repo

```bash
git clone https://github.com/clerk/clerk-astro-quickstart
cd clerk-astro-quickstart
```

The repo [pins `astro ^5.17.1`, `@clerk/astro 3.0.16`, and `@astrojs/node ^9.5.2`](https://github.com/clerk/clerk-astro-quickstart/blob/main/package.json). It uses `output: 'server'` with the Node standalone adapter.

#### Installing dependencies

```bash
npm install
```

pnpm and yarn both work. The quickstart includes a `pnpm-lock.yaml` by default.

#### Configuring environment variables

Copy the example file and fill in your keys from the Clerk Dashboard:

```env
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
```

The `PUBLIC_*` prefix is required by Astro so the publishable key is bundled to the client. `CLERK_SECRET_KEY` stays server-only.

> API keys live at `dashboard.clerk.com/~/api-keys`. A new Clerk application starts with a development instance whose keys begin with `pk_test_` and `sk_test_`. Production keys (`pk_live_` and `sk_live_`) are generated when you create a production instance.

### Option 2: adding Clerk to an existing Astro project

For an existing project, the setup is three files plus environment variables.

#### Installing @clerk/astro

```bash
npm install @clerk/astro
```

If the project does not already have an SSR adapter, add one. For Node:

```bash
npx astro add node
```

Vercel, Netlify, and Cloudflare adapters are equivalent — pick whichever matches your deploy target.

#### Updating astro.config.mjs

Register the `clerk()` integration alongside the SSR adapter and set `output: 'server'`:

```ts
// 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',
})
```

#### Creating src/middleware.ts

The middleware is what populates `Astro.locals.auth()` on every request. Create `src/middleware.ts`:

```ts
// src/middleware.ts
import { clerkMiddleware } from '@clerk/astro/server'

export const onRequest = clerkMiddleware()
```

#### Setting environment variables

Add a `.env` file at the project root:

```env
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
```

> Never commit `CLERK_SECRET_KEY`. Add `.env` to `.gitignore`. Only `PUBLIC_*` env vars are bundled to the client; secrets stay on the server. Importing `@clerk/astro/server` into a React island will fail at build — the server SDK is not for client code.

#### Setting output mode for authentication

`output: 'server'` is the default recommendation for auth-heavy apps. Most routes are SSR. A few static pages (marketing, docs) can be opted out with `export const prerender = true`.

`output: 'static'` makes sense when the reverse is true — the site is mostly marketing or content, and only a few routes need auth. Mark those pages with `export const prerender = false` and the middleware will run for them.

The dashboard example in this article assumes `output: 'server'`.

### Running the app locally

```bash
npm run dev
```

Open `http://localhost:4321`. The quickstart ships with `<SignInButton />` and `<UserButton />` on the index page, plus `/sign-in` and `/sign-up` routes backed by `<SignIn />` and `<SignUp />`. Sign up, then watch `<UserButton />` appear in the header once authenticated.

`@clerk/astro` works with any Astro-supported deploy target — Node, Vercel, Netlify, and Cloudflare Workers. This article focuses on the integration itself. For host-specific notes (adapter config, edge-runtime caveats, Netlify preview key handling), see [Deploy an Astro app to production](https://clerk.com/docs/guides/development/deployment/astro.md).

## Building authentication UI in Astro

Two paths for building UI: prebuilt Clerk components (the default recommendation) or custom UI driven by the nanostores and React hooks.

### Using Clerk's prebuilt components

The prebuilt components handle every auth flow out of the box. Drop them into your layouts; they render the sign-in form, profile UI, or organization switcher without any extra configuration.

#### SignIn component

Create a catch-all route at `/sign-in/[...sign-in].astro` so Clerk can handle its internal navigation (verification steps, MFA challenges, passkey flows):

```astro
---
// src/pages/sign-in/[...sign-in].astro
import { SignIn } from '@clerk/astro/components'
import Layout from '../../layouts/Layout.astro'
---

<Layout title="Sign in">
  <SignIn path="/sign-in" />
</Layout>
```

Passing `path` is all you need — setting `path` makes Clerk use path-based routing automatically, so you don't add `routing="path"` separately. The `path` value must match where the route is mounted (`/sign-in`).

Notable props:

- `appearance` — overrides theme, variables, and element classes (see [Customizing Clerk components](#customizing-clerk-components) below).
- `signInFallbackRedirectUrl` — where to go after sign-in if no redirect URL is in the URL.
- `signInForceRedirectUrl` — always redirect here after sign-in (overrides any URL parameter).
- `routing` — `'path'` or `'hash'`. Supplying `path` (as above) selects `'path'`; choose `'hash'` only when mounting without a `path`.
- `withSignUp={true}` — render the sign-up flow inside the same component.

> In Core 3, `afterSignInUrl` and `afterSignUpUrl` were removed. Use `signInFallbackRedirectUrl` for a post-sign-in default and `signInForceRedirectUrl` to always redirect to a specific URL.

#### SignUp component

Mirror the sign-in page at `/sign-up/[...sign-up].astro`:

```astro
---
// src/pages/sign-up/[...sign-up].astro
import { SignUp } from '@clerk/astro/components'
import Layout from '../../layouts/Layout.astro'
---

<Layout title="Sign up">
  <SignUp path="/sign-up" />
</Layout>
```

The `unsafeMetadata` prop lets you collect non-trusted signup data (free-text fields like "company size" or "referral source") that lives on the user object.

#### UserButton component

The `<UserButton />` is the avatar dropdown. It handles profile navigation, session management, and sign-out:

```astro
---
// src/layouts/Header.astro
import { SignInButton, UserButton, Show } from '@clerk/astro/components'
---

<header class="flex items-center justify-between p-4">
  <a href="/">My App</a>
  <nav class="flex items-center gap-4">
    <Show when="signed-out">
      <SignInButton mode="modal" />
    </Show>
    <Show when="signed-in">
      <UserButton />
    </Show>
  </nav>
</header>
```

Notable props on `<UserButton />`:

- `showName` — displays the user's name next to the avatar.
- `userProfileMode` — `'modal'` (default) or `'navigation'` to route to `/user`.
- `signInUrl` — override the default `/sign-in` redirect.
- `afterSwitchSessionUrl` — where to go after switching between linked accounts.

> In Core 3, `afterSignOutUrl` is set once on the `clerk()` integration in `astro.config.mjs` and applies to every sign-out flow. `<UserButton />` reads it from there and has no per-instance sign-out redirect prop. For a one-off override, use the `redirectUrl` prop on `<SignOutButton />`. The `as` prop is removed in Core 3 — use `asChild` with a slotted element.

#### Show control component

`<Show />` is the single conditional-rendering primitive in Core 3. It replaces `<SignedIn>`, `<SignedOut>`, and `<Protect>` from older versions.

Basic usage:

```astro
---
import { Show } from '@clerk/astro/components'
---

<Show when="signed-in">
  <p>You are signed in.</p>
</Show>
<Show when="signed-out">
  <p>Please sign in.</p>
</Show>
```

Role and permission gating:

```astro
<Show when={{ role: 'org:admin' }}>
  <a href="/admin">Admin panel</a>
</Show>

<Show when={{ permission: 'org:billing:manage' }}>
  <a href="/billing">Billing</a>
</Show>
```

Function predicate for compound checks:

```astro
<Show when={(has) => has({ role: 'org:admin' }) || has({ permission: 'org:billing:manage' })}>
  <a href="/billing">Billing</a>
</Show>
```

When the page is prerendered in a `server`-output app (or vice versa), add the `isStatic` prop so `<Show />` reads from the client nanostores instead of `locals`:

```astro
<Show when="signed-in" isStatic={true}>
  <UserButton client:load />
</Show>
```

### Customizing Clerk components

Two levers control appearance: the `appearance` prop on the `clerk()` integration (app-wide defaults) and the `appearance` prop on individual components (overrides).

#### Appearance prop basics

Global appearance lives on the integration:

```ts
// astro.config.mjs
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'
import clerk from '@clerk/astro'
import { dark } from '@clerk/themes'

export default defineConfig({
  integrations: [
    clerk({
      appearance: {
        baseTheme: dark,
        variables: { colorPrimary: '#6c47ff' },
        elements: {
          formButtonPrimary: 'bg-indigo-600 hover:bg-indigo-700',
        },
      },
    }),
  ],
  adapter: node({ mode: 'standalone' }),
  output: 'server',
})
```

#### Theming to match your Astro site

Pass a `baseTheme` from `@clerk/themes` (e.g. `dark`, `neobrutalism`) for quick themed looks. Use `variables` for CSS-level changes (primary color, border radius, font). Use `elements` to inject class names on Clerk's internal DOM — useful for projects that ship Tailwind classes.

> Put appearance on the `clerk()` integration for app-wide defaults. Override per-component only when a specific mount needs to look different — for example, a dark-themed `<SignIn />` on a light marketing page.

### Building custom UI with Clerk's stores and hooks

The prebuilt components cover most cases. When you need a fully custom sign-in flow, an inline profile editor, or a dashboard that reads auth state in a non-standard way, drop to the stores or hooks.

#### When to use stores or hooks instead of components

Common reasons:

- You need a sign-in UI that is pixel-identical to an existing design system.
- You want to read user data inside a client island without re-rendering the whole auth shell.
- You need to trigger auth actions imperatively (for example, on a button click outside a Clerk component).

#### Accessing auth state in Astro islands via nanostores

Nanostores are framework-agnostic. Use `@nanostores/react`, `@nanostores/vue`, `@nanostores/svelte`, or the raw `useStore` helper from `nanostores` directly.

```tsx
// src/components/ProfileCard.tsx
import { useStore } from '@nanostores/react'
import { $userStore } from '@clerk/astro/client'

export default function ProfileCard() {
  const user = useStore($userStore)
  if (user === undefined) return <div>Loading...</div>
  if (user === null) return <div>Signed out.</div>
  return <div>Signed in as {user.firstName}.</div>
}
```

Mount it as a React island with `client:load`:

```astro
---
import ProfileCard from '../components/ProfileCard.tsx'
---

<ProfileCard client:load />
```

#### Using `useAuth` in React islands

The `@clerk/astro/react` subpath ships one hook — `useAuth` — alongside React versions of the Clerk components. It returns the same auth primitives available in `.astro` frontmatter (`isLoaded`, `isSignedIn`, `userId`, `sessionId`, `orgId`, `orgRole`, `has`, `getToken`, `signOut`) and needs a React island with a `client:*` directive to work:

```tsx
// src/components/Greeting.tsx
import { useAuth } from '@clerk/astro/react'
import { useStore } from '@nanostores/react'
import { $userStore } from '@clerk/astro/client'

export default function Greeting() {
  const { isLoaded, isSignedIn } = useAuth()
  const user = useStore($userStore)
  if (!isLoaded) return <span>Loading...</span>
  if (!isSignedIn) return <span>Please sign in.</span>
  return <span>Hello, {user?.firstName}.</span>
}
```

Pair `useAuth` with the relevant nanostore when you need full resources — `$userStore` for the user record, `$organizationStore` for the active organization, `$sessionStore` for the session, and `$signInStore` or `$signUpStore` for in-progress auth flows.

> `useAuth` and the nanostores only work inside client-side islands. In `.astro` frontmatter, use `Astro.locals.auth()` on the server. Mixing the client-only helpers into `.astro` frontmatter will throw at build time.

## Protecting routes with Astro middleware

Middleware is the single place to enforce auth across many routes at once. Clerk's `clerkMiddleware()` wraps Astro's middleware API and adds route matching, auth population, and redirects.

### How Astro middleware works

`src/middleware.ts` exports `onRequest` (preferred) or a default export. The function receives an `APIContext` and a `next` callback. It runs on every SSR request, before the page renders:

```ts
// src/middleware.ts — vanilla Astro middleware
import { defineMiddleware } from 'astro:middleware'

export const onRequest = defineMiddleware(async (context, next) => {
  context.locals.startedAt = Date.now()
  const response = await next()
  return response
})
```

Access `context.request`, `context.cookies`, `context.locals`, `context.redirect()`, `context.rewrite()`, and `context.url`. Compose middleware with `sequence()`:

```ts
import { sequence } from 'astro:middleware'
import { clerkMiddleware } from '@clerk/astro/server'

export const onRequest = sequence(clerkMiddleware(), myOtherMiddleware)
```

> Middleware only runs on SSR requests. Prerendered pages bypass middleware entirely — the HTML was baked at build time and never hits your server. Any route that needs auth must be SSR.

### Adding Clerk's clerkMiddleware()

The minimal setup:

```ts
// src/middleware.ts
import { clerkMiddleware } from '@clerk/astro/server'

export const onRequest = clerkMiddleware()
```

By default, every route is **public**. The middleware populates `Astro.locals.auth()` and `Astro.locals.currentUser()` on every SSR request whether the route is protected or not — protecting is opt-in.

### Defining protected routes

There are two patterns. Opt-in (default public, list the protected routes) is simpler. Opt-out (default protected, list the public routes) is safer for B2B apps where missing a protection is worse than a user seeing an unexpected sign-in page.

#### Protecting pages

Use `createRouteMatcher()` to describe protected paths, then redirect unauthenticated visitors inside the middleware callback:

```ts
// src/middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/astro/server'

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)', '/orgs/(.*)'])

export const onRequest = clerkMiddleware((auth, context) => {
  const { isAuthenticated, redirectToSignIn } = auth()
  if (!isAuthenticated && isProtectedRoute(context.request)) {
    return redirectToSignIn()
  }
})
```

The matcher syntax is Express-style. `(.*)` matches everything after the prefix, so `/dashboard(.*)` covers `/dashboard`, `/dashboard/team`, and so on.

#### Protecting API endpoints

API routes return JSON, so redirecting to a sign-in page does not help a JSON client. `@clerk/astro` has no `protect()` helper — the `auth` parameter is a function that returns the auth object, so check `auth().isAuthenticated` and return a `401` `Response` yourself for unauthenticated API requests:

```ts
// src/middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/astro/server'

const isApiRoute = createRouteMatcher(['/api/(.*)'])

export const onRequest = clerkMiddleware((auth, context) => {
  if (isApiRoute(context.request) && !auth().isAuthenticated) {
    return new Response(JSON.stringify({ error: 'Unauthorized' }), {
      status: 401,
      headers: { 'Content-Type': 'application/json' },
    })
  }
})
```

You can also enforce auth inside the endpoint itself by reading `Astro.locals.auth()` in the route handler and returning a `401` there — handy when only a few endpoints need protection.

#### Route matchers for public vs private

The opt-out pattern protects everything and lists exceptions:

```ts
// src/middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/astro/server'

const isPublicRoute = createRouteMatcher([
  '/',
  '/sign-in(.*)',
  '/sign-up(.*)',
  '/pricing',
  '/blog(.*)',
])

export const onRequest = clerkMiddleware((auth, context) => {
  const { isAuthenticated, redirectToSignIn } = auth()
  if (!isPublicRoute(context.request) && !isAuthenticated) {
    return redirectToSignIn()
  }
})
```

This is safer for B2B dashboards: every new route you add is protected by default. You have to explicitly add it to the public list to expose it.

### Handling SSR and prerendered pages

Prerendered pages do not run middleware. If you need auth on a mostly-static site, two options:

1. Switch to `output: 'server'` and mark static pages with `export const prerender = true`.
2. Stay on `output: 'static'` and mark auth routes with `export const prerender = false`. Middleware runs for those routes only.

The first option is usually cleaner for apps where most pages need auth. The second fits marketing sites with a single logged-in portal.

### Redirect behavior for unauthenticated users

`redirectToSignIn()` sends unauthenticated visitors to your sign-in URL and appends a `redirect_url` query parameter so they return to their original destination after signing in. Configure that URL with the `signInUrl` option on the `clerk()` integration or the `PUBLIC_CLERK_SIGN_IN_URL` environment variable — for example, `PUBLIC_CLERK_SIGN_IN_URL=/sign-in` to use the in-app `/sign-in` route from earlier. Without it, Clerk falls back to its hosted Account Portal sign-in page.

To override the return destination Clerk appends — where the user lands after signing in — pass `returnBackUrl`:

```ts
return auth().redirectToSignIn({ returnBackUrl: '/dashboard' })
```

> Always allow `/sign-in(.*)` and `/sign-up(.*)` through your protection check. If the sign-in page itself redirects unauthenticated visitors to `/sign-in`, you get an infinite redirect loop. The opt-out pattern above shows the correct shape.

## Conclusion

With the Clerk Astro SDK configured, you can build powerful authentication flows and secure pages with minimal boilerplate. Prebuilt components like `<SignIn />` and `<UserButton />` get you to production quickly, while the client-side nanostores and React hooks give you the flexibility to build entirely custom UIs when needed. Astro middleware ties it all together, ensuring that protected routes remain secure whether you are building a server-rendered dashboard or a hybrid static site.

In the next part of this series, we will dive into the core mechanics of session management, building multi-tenant dashboards with Organizations, and implementing role-based access control (RBAC).

## Frequently asked questions

## FAQ

### Does Clerk support Astro's SSR and SSG rendering modes?

Yes — both `output: 'server'` and `output: 'static'`. Pass `isStatic={true}` to control components on prerendered pages, omit it on SSR pages. Middleware runs only on SSR requests, so any auth-aware page must be SSR: `export const prerender = false` in a `static` app, or `export const prerender = true` for marketing pages in a `server` app.

### How do I use Clerk's nanostores in Astro islands?

Use `@nanostores/react`, `@nanostores/vue`, or `@nanostores/svelte` to subscribe to `$authStore`, `$userStore`, and others from `@clerk/astro/client`. These stores provide framework-agnostic reactive state inside client islands. Always handle the initial `undefined` loading state to avoid hydration mismatches.

## In this series

1. [Authentication for Astro Sites](https://clerk.com/articles/authentication-for-astro-sites.md)
2. **Authentication for Astro Sites - Part 2** (you are here)
3. [Authentication for Astro Sites - Part 3](https://clerk.com/articles/authentication-for-astro-sites-3.md)
4. [Authentication for Astro Sites - Part 4](https://clerk.com/articles/authentication-for-astro-sites-4.md)
