From 7e23db2e17accd023ff24e168329d24dea0c1407 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Mon, 27 Jul 2026 11:58:57 -0700 Subject: [PATCH 1/4] chore(nuxt): Remove createRouteMatcher support --- .changeset/major-nuxt-remove-route-matcher.md | 66 +++++++ .../nuxt-node/app/middleware/auth.global.js | 16 -- .../nuxt-node/app/middleware/auth.js | 7 + .../app/pages/user-profile/[...slug].vue | 2 + integration/tests/nuxt/middleware.test.ts | 163 +++++------------- packages/nuxt/src/module.ts | 4 - .../client/__tests__/routeMatcher.test.ts | 26 --- packages/nuxt/src/runtime/client/index.ts | 1 - .../nuxt/src/runtime/client/routeMatcher.ts | 43 ----- .../server/__tests__/clerkMiddleware.test.ts | 9 +- .../server/__tests__/routeMatcher.test.ts | 31 ---- packages/nuxt/src/runtime/server/index.ts | 1 - .../nuxt/src/runtime/server/routeMatcher.ts | 47 ----- 13 files changed, 124 insertions(+), 292 deletions(-) create mode 100644 .changeset/major-nuxt-remove-route-matcher.md delete mode 100644 integration/templates/nuxt-node/app/middleware/auth.global.js create mode 100644 integration/templates/nuxt-node/app/middleware/auth.js delete mode 100644 packages/nuxt/src/runtime/client/__tests__/routeMatcher.test.ts delete mode 100644 packages/nuxt/src/runtime/client/routeMatcher.ts delete mode 100644 packages/nuxt/src/runtime/server/__tests__/routeMatcher.test.ts delete mode 100644 packages/nuxt/src/runtime/server/routeMatcher.ts diff --git a/.changeset/major-nuxt-remove-route-matcher.md b/.changeset/major-nuxt-remove-route-matcher.md new file mode 100644 index 00000000000..5579947104e --- /dev/null +++ b/.changeset/major-nuxt-remove-route-matcher.md @@ -0,0 +1,66 @@ +--- +'@clerk/nuxt': major +--- + +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. + +Protect API routes inside the event handler itself: + +```ts +export default defineEventHandler(event => { + const { userId } = event.context.auth(); + + if (!userId) { + throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }); + } + + return { userId }; +}); +``` + +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: + +```ts +// app/middleware/auth.ts +export default defineNuxtRouteMiddleware(() => { + const { isSignedIn } = useAuth(); + + if (!isSignedIn.value) { + return navigateTo('/sign-in'); + } +}); +``` + +```vue + +``` + +If you want to hand this work to a coding agent, use this migration prompt: + +```md +Migrate my Nuxt project away from Clerk's removed `createRouteMatcher` API. + +1. Find every matcher created with `createRouteMatcher`, along with the logic + that uses it (throwing 401 errors, calling `navigateTo('/sign-in')`, etc.). + Matchers can appear in Nitro server middleware (imported from + `@clerk/nuxt/server`) or in Nuxt route middleware (auto-imported). +2. For every API route those matchers protected, move the auth check into the + event handler itself: + const { userId } = event.context.auth(); + if (!userId) throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }); + Keep any role or permission checks (`event.context.auth().has(...)`) with + the resource as well. +3. For every page those matchers protected, create a named route middleware in + `app/middleware/` that checks `useAuth()` and redirects with `navigateTo()`, + then opt pages into it with `definePageMeta({ middleware: 'auth' })`. Child + routes inherit the middleware applied to their parent. +4. Remove the `createRouteMatcher` imports and calls. Keep `clerkMiddleware()` + itself. Middleware logic unrelated to auth protection (headers, locale + redirects, etc.) may stay, using plain `getRequestURL(event).pathname` + checks. +5. Make sure every route previously covered by a matcher pattern (including + glob patterns like `/dashboard(.*)`) now has its own check, then verify the + project builds. +``` diff --git a/integration/templates/nuxt-node/app/middleware/auth.global.js b/integration/templates/nuxt-node/app/middleware/auth.global.js deleted file mode 100644 index 0e6f082773b..00000000000 --- a/integration/templates/nuxt-node/app/middleware/auth.global.js +++ /dev/null @@ -1,16 +0,0 @@ -export default defineNuxtRouteMiddleware(to => { - const { userId } = useAuth(); - - const isPublicPage = createRouteMatcher(['/sign-in(.*)', '/sign-up(.*)']); - const isProtectedPage = createRouteMatcher(['/user-profile(.*)']); - - // Is authenticated and trying to access a public page - if (userId.value && isPublicPage(to)) { - return navigateTo('/user-profile'); - } - - // Is not authenticated and trying to access a protected page - if (!userId.value && isProtectedPage(to)) { - return navigateTo('/sign-in'); - } -}); diff --git a/integration/templates/nuxt-node/app/middleware/auth.js b/integration/templates/nuxt-node/app/middleware/auth.js new file mode 100644 index 00000000000..3ef9b853669 --- /dev/null +++ b/integration/templates/nuxt-node/app/middleware/auth.js @@ -0,0 +1,7 @@ +export default defineNuxtRouteMiddleware(() => { + const { isSignedIn } = useAuth(); + + if (!isSignedIn.value) { + return navigateTo('/sign-in'); + } +}); diff --git a/integration/templates/nuxt-node/app/pages/user-profile/[...slug].vue b/integration/templates/nuxt-node/app/pages/user-profile/[...slug].vue index bc5be9d7d67..03dcb56f95b 100644 --- a/integration/templates/nuxt-node/app/pages/user-profile/[...slug].vue +++ b/integration/templates/nuxt-node/app/pages/user-profile/[...slug].vue @@ -1,4 +1,6 @@ diff --git a/integration/tests/nuxt/middleware.test.ts b/integration/tests/nuxt/middleware.test.ts index 9b6e58a7a47..b12eb269a33 100644 --- a/integration/tests/nuxt/middleware.test.ts +++ b/integration/tests/nuxt/middleware.test.ts @@ -14,23 +14,21 @@ const nuxtConfigFile = () => `export default defineNuxtConfig({ } });`; -const clerkMiddlewareFile = () => `import { clerkMiddleware, createRouteMatcher } from '@clerk/nuxt/server'; +const clerkMiddlewareFile = () => `import { clerkMiddleware } from '@clerk/nuxt/server'; - const isProtectedRoute = createRouteMatcher(['/api/me', '/api/admin(.*)']); + export default clerkMiddleware(); + `; - export default clerkMiddleware((event) => { +const adminApiRouteFile = () => `export default defineEventHandler((event) => { const { userId } = event.context.auth(); - if (!userId && isProtectedRoute(event)) { + if (!userId) { throw createError({ statusCode: 401, statusMessage: 'You are not authorized to access this resource.' }) } - }); - `; -const adminApiRouteFile = () => `export default defineEventHandler((event) => { return { status: 'ok' }; });`; @@ -44,7 +42,37 @@ const mePageFile = () => `