|
| 1 | +--- |
| 2 | +'@clerk/nuxt': major |
| 3 | +--- |
| 4 | + |
| 5 | +Remove `createRouteMatcher` from `@clerk/nuxt/server` and the auto-imported client-side `createRouteMatcher`. Middleware-based auth checks rely on path matching, which can diverge from how requests are actually routed and leave protected resources reachable. Move auth checks into the resources themselves. |
| 6 | + |
| 7 | +Protect API routes inside the event handler itself: |
| 8 | + |
| 9 | +```ts |
| 10 | +export default defineEventHandler(event => { |
| 11 | + const { userId } = event.context.auth(); |
| 12 | + |
| 13 | + if (!userId) { |
| 14 | + throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }); |
| 15 | + } |
| 16 | + |
| 17 | + return { userId }; |
| 18 | +}); |
| 19 | +``` |
| 20 | + |
| 21 | +Protect pages with a named route middleware and opt pages into it with `definePageMeta()`. Child routes inherit the middleware applied to their parent, so a single declaration can protect a whole section: |
| 22 | + |
| 23 | +```ts |
| 24 | +// app/middleware/auth.ts |
| 25 | +export default defineNuxtRouteMiddleware(() => { |
| 26 | + const { isSignedIn } = useAuth(); |
| 27 | + |
| 28 | + if (!isSignedIn.value) { |
| 29 | + return navigateTo('/sign-in'); |
| 30 | + } |
| 31 | +}); |
| 32 | +``` |
| 33 | + |
| 34 | +```vue |
| 35 | +<script setup> |
| 36 | +definePageMeta({ middleware: 'auth' }); |
| 37 | +</script> |
| 38 | +``` |
| 39 | + |
| 40 | +If you want to hand this work to a coding agent, use this migration prompt: |
| 41 | + |
| 42 | +```md |
| 43 | +Migrate my Nuxt project away from Clerk's removed `createRouteMatcher` API. |
| 44 | + |
| 45 | +1. Find every matcher created with `createRouteMatcher`, along with the logic |
| 46 | + that uses it (throwing 401 errors, calling `navigateTo('/sign-in')`, etc.). |
| 47 | + Matchers can appear in Nitro server middleware (imported from |
| 48 | + `@clerk/nuxt/server`) or in Nuxt route middleware (auto-imported). |
| 49 | +2. For every API route those matchers protected, move the auth check into the |
| 50 | + event handler itself: |
| 51 | + const { userId } = event.context.auth(); |
| 52 | + if (!userId) throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }); |
| 53 | + Keep any role or permission checks (`event.context.auth().has(...)`) with |
| 54 | + the resource as well. |
| 55 | +3. For every page those matchers protected, create a named route middleware in |
| 56 | + `app/middleware/` that checks `useAuth()` and redirects with `navigateTo()`, |
| 57 | + then opt pages into it with `definePageMeta({ middleware: 'auth' })`. Child |
| 58 | + routes inherit the middleware applied to their parent. |
| 59 | +4. Remove the `createRouteMatcher` imports and calls. Keep `clerkMiddleware()` |
| 60 | + itself. Middleware logic unrelated to auth protection (headers, locale |
| 61 | + redirects, etc.) may stay, using plain `getRequestURL(event).pathname` |
| 62 | + checks. Plain pathname checks do not normalize percent-encoding |
| 63 | + (`/api/%61dmin` will not match a check for `/api/admin`), so never use |
| 64 | + them for auth or security decisions. Those belong on the resource itself, |
| 65 | + as in steps 2 and 3. |
| 66 | +5. Make sure every route previously covered by a matcher pattern (including |
| 67 | + glob patterns like `/dashboard(.*)`) now has its own check, then verify the |
| 68 | + project builds. |
| 69 | +``` |
0 commit comments