clerk/skillsReview behavior before running
SKILL DETAIL
clerk-nuxt-patterns
clerk/skills/clerk-nuxt-patterns
Nuxt 3 auth patterns with @clerk/nuxt - middleware, composables, server
Installs · 93View source
Installation
npx skills add https://github.com/clerk/skills --skill clerk-nuxt-patterns
Skill files
SKILL.md
Last synced · Aug 30, 2026
evals/evals.json›
{
"skill_name": "clerk-nuxt-patterns",
"evals": [
{
"id": 1,
"prompt": "i have a nuxt 3 app with @clerk/nuxt installed. i need to protect my /dashboard route so only signed-in users can access it. unauthenticated users should be redirected to the sign-in page.",
"expected_output": "Adds definePageMeta middleware auth guard to dashboard page, or creates a route middleware using clerkMiddleware",
"scaffold": "nuxt-basic-auth",
"expectations": [
"Uses definePageMeta({ middleware: 'auth' }) on the dashboard page OR creates a named route middleware",
"Handles redirect for unauthenticated users",
"Does NOT use client-side only checks for route protection",
"Imports are correct for @clerk/nuxt",
"Does not break the existing app structure"
]
},
{
"id": 2,
"prompt": "i need to create a server API route in my nuxt app that returns the current user's data. the route should only work for authenticated users and return 401 for unauthenticated requests.",
"expected_output": "Creates a Nitro server route using event.context.auth or clerkClient to get the current user",
"scaffold": "nuxt-basic-auth",
"expectations": [
"Creates a file in server/api/ directory",
"Accesses auth state via event.context.auth or imports clerkClient from @clerk/nuxt/server",
"Returns 401 status for unauthenticated requests",
"Returns user data for authenticated requests",
"Uses defineEventHandler from Nitro"
]
},
{
"id": 3,
"prompt": "i want to show user profile information on my nuxt page - the user's name, email, and avatar. how do i access this data in my page component?",
"expected_output": "Uses useUser() composable to access user data in the Vue component",
"scaffold": "nuxt-basic-auth",
"expectations": [
"Uses useUser() composable (auto-imported by @clerk/nuxt)",
"Accesses user.firstName, user.lastName, user.emailAddresses, or user.imageUrl",
"Handles the loading state before rendering user data",
"Does NOT make a server fetch for user data when composables suffice",
"Component uses <script setup> syntax"
]
},
{
"id": 4,
"prompt": "my nuxt saas app needs organization support. users should be able to switch between their organizations in the header. show me how to add org switching and display org-scoped content on the dashboard.",
"expected_output": "Adds OrganizationSwitcher component, reads orgId from useAuth() to scope dashboard content",
"scaffold": "nuxt-basic-auth",
"expectations": [
"Adds <OrganizationSwitcher /> component to the header or layout",
"Uses orgId from useAuth() composable to scope data",
"Handles case where no org is selected (prompts user to select or create one)",
"Does NOT remove existing user authentication logic",
"Dashboard content is conditional on orgId being present"
]
},
{
"id": 5,
"prompt": "i want to replace the hosted sign-in page with a custom one inside my nuxt app. show me how to create a custom sign-in page using clerk components.",
"expected_output": "Creates a pages/sign-in.vue page with <SignIn /> component and configures the sign-in URL",
"scaffold": "nuxt-basic-auth",
"expectations": [
"Creates a sign-in page in the pages/ directory",
"Renders the <SignIn /> component from @clerk/nuxt",
"Configures NUXT_PUBLIC_CLERK_SIGN_IN_URL or signInUrl in nuxt.config.ts",
"Does NOT implement a custom auth form from scratch",
"Page is publicly accessible (no auth middleware on it)"
]
},
{
"id": 6,
"prompt": "i need to protect a nitro server route that handles payments. the route needs to verify the user is authenticated and get their userId to record the payment in my database.",
"expected_output": "Server route using event.context.auth to get userId, returns 401 if not authenticated",
"scaffold": "nuxt-basic-auth",
"expectations": [
"Uses event.context.auth.userId in a Nitro server route",
"Returns createError({ statusCode: 401 }) or setResponseStatus(event, 401) for unauthenticated",
"Extracts userId from auth context to use in database operation",
"File is placed in server/api/ or server/routes/",
"Does NOT re-verify the session token manually"
]
},
{
"id": 7,
"prompt": "i'm adding clerk to my nuxt 3 app. set up the module and protect the /dashboard route so unauthenticated users are redirected to /sign-in.",
"expected_output": "Adds @clerk/nuxt to modules in nuxt.config.ts, creates Nuxt route middleware that checks auth and redirects",
"scaffold": "nuxt-basic-auth",
"expectations": [
"Adds @clerk/nuxt to the modules array in nuxt.config.ts",
"Creates a route middleware that checks authentication",
"Uses useAuth composable inside the middleware",
"Redirects unauthenticated users to /sign-in via navigateTo",
"Applies the middleware to the /dashboard route"
]
},
{
"id": 8,
"prompt": "my nuxt app has a server API route /api/user-data that should only work for authenticated users. implement the server-side auth check.",
"expected_output": "Uses getAuth from @clerk/nuxt/server in the event handler, throws 401 when userId is falsy",
"scaffold": "nuxt-basic-auth",
"expectations": [
"Imports getAuth from @clerk/nuxt/server",
"Calls getAuth(event) to get userId",
"Throws createError({ statusCode: 401 }) when userId is falsy",
"Uses userId in the data fetching logic",
"Does NOT use client-side composables in the server route"
]
},
{
"id": 9,
"prompt": "i need to add a global nuxt server middleware that checks auth on all protected routes and redirects to /sign-in. use clerkMiddleware from @clerk/nuxt.",
"expected_output": "Creates server/middleware/clerk.ts with clerkMiddleware that checks H3Event context for auth",
"scaffold": "nuxt-basic-auth",
"expectations": [
"Creates server/middleware/clerk.ts",
"Uses clerkMiddleware from @clerk/nuxt/server",
"Exports the result as default",
"Can access auth state via event.context.auth",
"Does NOT create the middleware in middleware/ (that is for route middleware, not server middleware)"
]
}
]
}
references/composables.md›
# Composables (HIGH)
All composables are auto-imported by `@clerk/nuxt` — no import statements needed in `<script setup>`.
## useAuth()
Returns reactive auth state and helpers:
```vue
<script setup lang="ts">
const { isSignedIn, isLoaded, userId, sessionId, orgId, orgRole, orgSlug } = useAuth()
</script>
<template>
<div v-if="!isLoaded">Loading...</div>
<div v-else-if="!isSignedIn">Please sign in</div>
<div v-else>Hello {{ userId }}</div>
</template>
```
## useUser()
Returns the full user object with profile data:
```vue
<script setup lang="ts">
const { isLoaded, isSignedIn, user } = useUser()
</script>
<template>
<div v-if="isLoaded && isSignedIn">
<img :src="user.imageUrl" :alt="user.fullName" />
<p>{{ user.firstName }} {{ user.lastName }}</p>
<p>{{ user.emailAddresses[0]?.emailAddress }}</p>
</div>
</template>
```
## useClerk()
Access the Clerk instance for programmatic actions:
```vue
<script setup lang="ts">
const clerk = useClerk()
function handleSignOut() {
clerk.signOut()
}
function openProfile() {
clerk.openUserProfile()
}
</script>
```
## useSignIn() / useSignUp()
For custom auth flows:
```vue
<script setup lang="ts">
const { signIn, setActive } = useSignIn()
async function handleLogin(email: string, password: string) {
const result = await signIn.create({
identifier: email,
password,
})
if (result.status === 'complete') {
await setActive({ session: result.createdSessionId })
navigateTo('/dashboard')
}
}
</script>
```
## Ref vs Value
Composables return `Ref<T>` values. Access with `.value` in `<script setup>` but NOT in templates:
```vue
<script setup lang="ts">
const { isSignedIn, userId } = useAuth()
// In script: isSignedIn.value, userId.value
</script>
<template>
<!-- In template: no .value needed -->
<div v-if="isSignedIn">{{ userId }}</div>
</template>
```
[Docs](https://clerk.com/docs/nuxt/getting-started/quickstart)
references/nuxt-middleware.md›
# Nuxt Middleware (CRITICAL)
## Built-in Auth Middleware
`@clerk/nuxt` auto-registers an `auth` named middleware. Use it with `definePageMeta`:
```vue
<script setup lang="ts">
definePageMeta({ middleware: 'auth' })
</script>
```
This redirects unauthenticated users to the sign-in page automatically.
## Custom Route Middleware
Create `middleware/require-org.ts` for custom logic:
```typescript
export default defineNuxtRouteMiddleware(() => {
const { isSignedIn, orgId } = useAuth()
if (!isSignedIn.value) {
return navigateTo('/sign-in')
}
if (!orgId.value) {
return navigateTo('/select-org')
}
})
```
Apply to a page:
```vue
<script setup lang="ts">
definePageMeta({ middleware: ['auth', 'require-org'] })
</script>
```
## Server-Side Middleware (Nitro)
For API-level protection in `server/middleware/auth.ts`:
```typescript
import { clerkClient } from '@clerk/nuxt/server'
export default defineEventHandler(async (event) => {
const auth = event.context.auth
if (getRequestURL(event).pathname.startsWith('/api/protected')) {
if (!auth?.userId) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
}
}
})
```
## Redirect URLs
Configure in `.env`:
```
NUXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NUXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NUXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/dashboard
NUXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/dashboard
```
[Docs](https://clerk.com/docs/nuxt/getting-started/quickstart)
references/server-api-routes.md›
# Server API Routes (HIGH)
## Auth Context in Nitro
`event.context.auth` is automatically populated by `@clerk/nuxt` for all server routes.
```typescript
// server/api/me.get.ts
export default defineEventHandler(async (event) => {
const { userId } = event.context.auth ?? {}
if (!userId) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
}
return { userId }
})
```
## Using clerkClient
Import `clerkClient` from `@clerk/nuxt/server` to access the Clerk backend API:
```typescript
// server/api/user.get.ts
import { clerkClient } from '@clerk/nuxt/server'
export default defineEventHandler(async (event) => {
const { userId } = event.context.auth ?? {}
if (!userId) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
}
const user = await clerkClient(event).users.getUser(userId)
return {
id: user.id,
firstName: user.firstName,
email: user.emailAddresses[0]?.emailAddress,
}
})
```
## Org-Scoped Server Routes
```typescript
// server/api/projects.get.ts
export default defineEventHandler(async (event) => {
const { userId, orgId } = event.context.auth ?? {}
if (!userId) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
}
if (!orgId) {
throw createError({ statusCode: 403, message: 'No active organization' })
}
const projects = await db.projects.findMany({ where: { orgId } })
return projects
})
```
## Error Codes
- `401` — not authenticated (no valid session)
- `403` — authenticated but no permission (wrong role, no org)
[Docs](https://clerk.com/docs/nuxt/getting-started/quickstart)
references/ssr-auth.md›
# SSR Auth (HIGH)
## Server vs Client Boundary
`@clerk/nuxt` composables (`useAuth`, `useUser`) are client-reactive but the initial state is SSR-safe.
| Context | How to get auth |
|---------|----------------|
| Vue component (`<script setup>`) | `useAuth()`, `useUser()` composables |
| Nitro server route | `event.context.auth` |
| Nuxt plugin (server) | `event.context.auth` via `useNitroApp` |
## SSR-Safe Page Pattern
```vue
<script setup lang="ts">
definePageMeta({ middleware: 'auth' })
const { userId, isLoaded } = useAuth()
</script>
<template>
<div v-if="!isLoaded">Loading...</div>
<main v-else>
<p>User: {{ userId }}</p>
</main>
</template>
```
The middleware runs server-side, so by the time the page renders, unauthenticated users are already redirected.
## Hydration Safety
Avoid rendering auth-dependent content that differs between server and client:
```vue
<!-- WRONG: causes hydration mismatch -->
<template>
<div v-if="isSignedIn">Dashboard</div>
</template>
<!-- CORRECT: use ClientOnly for auth-gated content if SSR causes mismatch -->
<template>
<ClientOnly>
<div v-if="isSignedIn">Dashboard</div>
</ClientOnly>
</template>
```
When using middleware, the server redirect happens before hydration, so this is rarely needed for protected pages.
## useFetch with Auth
Pass the session token to server-side `useFetch` for authenticated API calls:
```vue
<script setup lang="ts">
const { getToken } = useAuth()
const { data } = await useFetch('/api/protected', {
headers: async () => {
const token = await getToken()
return token ? { Authorization: `Bearer ${token}` } : {}
},
})
</script>
```
## Pinia State Hydration
When using Pinia with Clerk, hydrate the store with server auth state to avoid client-side flash:
```ts
// plugins/auth-hydration.ts
import { useAuthStore } from '@/stores/auth'
export default defineNuxtPlugin(async () => {
const store = useAuthStore()
await until(store.isLoaded).toBeTruthy()
})
```
Stores are created per-request on the server — do NOT store sensitive data (tokens) in Pinia.
[Docs](https://clerk.com/docs/nuxt/getting-started/quickstart)
SKILL.md›
---
name: clerk-nuxt-patterns
description: 'Nuxt 3 auth patterns with @clerk/nuxt - middleware, composables, server
API routes, SSR. Triggers on: Nuxt auth, useAuth composable, clerkMiddleware Nuxt,
server API Clerk, Nuxt route protection.'
license: MIT
allowed-tools: WebFetch
metadata:
author: clerk
version: 1.0.0
---
# Nuxt Patterns
## What Do You Need?
| Task | Reference |
|------|-----------|
| Protect routes with middleware | references/nuxt-middleware.md |
| Auth in server API routes (Nitro) | references/server-api-routes.md |
| useAuth / useUser in components | references/composables.md |
| SSR-safe auth patterns | references/ssr-auth.md |
## References
| Reference | Description |
|-----------|-------------|
| `references/nuxt-middleware.md` | Route protection, clerkMiddleware() |
| `references/server-api-routes.md` | Nitro server route auth |
| `references/composables.md` | useAuth, useUser, useClerk |
| `references/ssr-auth.md` | SSR hydration, server vs client |
## Setup
```
npm install @clerk/nuxt
```
`.env`:
```
NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
NUXT_CLERK_SECRET_KEY=sk_...
```
`nuxt.config.ts`:
```typescript
export default defineNuxtConfig({
modules: ['@clerk/nuxt'],
})
```
This single line auto-configures middleware, plugins, and component auto-imports.
## Mental Model
`@clerk/nuxt` auto-imports all Clerk components and composables — no explicit imports needed in `<script setup>`.
- **Composables** (`useAuth`, `useUser`) — client-side reactive, inside `<script setup>`
- **Server routes** (`clerkClient`) — Nitro server routes, `event.context.auth`
- **Middleware** (`clerkMiddleware`) — auto-registered, use `auth().protect()` to lock routes
## Minimal Pattern
```vue
<!-- pages/dashboard.vue -->
<script setup lang="ts">
definePageMeta({ middleware: 'auth' })
const { userId } = useAuth()
</script>
<template>
<Show when="signed-in">
<p>Hello {{ userId }}</p>
</Show>
</template>
```
> `definePageMeta({ middleware: 'auth' })` uses the built-in auth middleware from `@clerk/nuxt`.
## Common Pitfalls
| Symptom | Cause | Fix |
|---------|-------|-----|
| Composables return `undefined` on server | useAuth is client-only | Use `event.context.auth` in server routes |
| Route not protected | Missing `middleware: 'auth'` meta | Add `definePageMeta({ middleware: 'auth' })` |
| `clerkClient` not available | Wrong import path | Import from `@clerk/nuxt/server` |
| Hydration mismatch | Rendering auth state before mounted | Wrap in `<ClientOnly>` or check `isLoaded` |
| Env vars not picked up | Wrong prefix | Nuxt requires `NUXT_PUBLIC_` for public, `NUXT_` for server |
## Org-Aware Pattern
```vue
<script setup lang="ts">
const { orgId, orgRole } = useAuth()
</script>
<template>
<div v-if="orgId">
<p>Org: {{ orgId }}</p>
<p v-if="orgRole === 'org:admin'">Admin panel</p>
</div>
<div v-else>
<OrganizationSwitcher />
</div>
</template>
```
## See Also
- `clerk-setup` - Initial Clerk install
- `clerk-custom-ui` - Custom flows & appearance
- `clerk-orgs` - B2B organizations
## Docs
[Nuxt SDK](https://clerk.com/docs/nuxt/getting-started/quickstart)
templates/nuxt-basic-auth/app.vue›
<script setup lang="ts">
</script>
<template>
<header>
<Show when="signed-out">
<SignInButton />
<SignUpButton />
</Show>
<Show when="signed-in">
<UserButton />
</Show>
</header>
<main>
<NuxtPage />
</main>
</template>
templates/nuxt-basic-auth/nuxt.config.ts›
export default defineNuxtConfig({
modules: ['@clerk/nuxt'],
})
templates/nuxt-basic-auth/package.json›
{
"name": "nuxt-basic-auth",
"private": true,
"scripts": {
"dev": "nuxt dev",
"build": "nuxt build"
},
"dependencies": {
"nuxt": "latest",
"@clerk/nuxt": "latest"
}
}