diff --git a/.changeset/nuxt-deprecate-route-matcher.md b/.changeset/nuxt-deprecate-route-matcher.md new file mode 100644 index 00000000000..f297891d51a --- /dev/null +++ b/.changeset/nuxt-deprecate-route-matcher.md @@ -0,0 +1,20 @@ +--- +'@clerk/nuxt': patch +--- + +Deprecate `createRouteMatcher()` in favor of Nuxt's native route matching. + +To protect API routes, match paths natively inside `clerkMiddleware()`: + +```ts +export default clerkMiddleware(event => { + const { isAuthenticated } = event.context.auth(); + const { pathname } = getRequestURL(event); + + if (!isAuthenticated && pathname.startsWith('/api/admin')) { + throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }); + } +}); +``` + +To protect pages, use Nuxt's built-in route middleware with `definePageMeta({ middleware: 'auth' })`. diff --git a/packages/nuxt/src/runtime/client/__tests__/routeMatcher.test.ts b/packages/nuxt/src/runtime/client/__tests__/routeMatcher.test.ts new file mode 100644 index 00000000000..7fd4551311c --- /dev/null +++ b/packages/nuxt/src/runtime/client/__tests__/routeMatcher.test.ts @@ -0,0 +1,26 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { createRouteMatcher } from '../routeMatcher'; + +const { mockDeprecated } = vi.hoisted(() => ({ + mockDeprecated: vi.fn(), +})); + +vi.mock('@clerk/shared/deprecated', () => ({ + deprecated: mockDeprecated, +})); + +describe('createRouteMatcher', () => { + beforeEach(() => { + mockDeprecated.mockClear(); + }); + + it('should emit a deprecation warning when called', () => { + createRouteMatcher(['/dashboard(.*)']); + + expect(mockDeprecated).toHaveBeenCalledWith( + 'createRouteMatcher', + "Use Nuxt's built-in route middleware to protect pages instead: create a named middleware that checks the user's authentication status and opt pages into it with `definePageMeta({ middleware: 'auth' })`.", + ); + }); +}); diff --git a/packages/nuxt/src/runtime/client/routeMatcher.ts b/packages/nuxt/src/runtime/client/routeMatcher.ts index cf3ec0471fe..1758f1f5002 100644 --- a/packages/nuxt/src/runtime/client/routeMatcher.ts +++ b/packages/nuxt/src/runtime/client/routeMatcher.ts @@ -1,3 +1,4 @@ +import { deprecated } from '@clerk/shared/deprecated'; import { createPathMatcher, type PathMatcherParam } from '@clerk/shared/pathMatcher'; import type { RouteMiddleware } from 'nuxt/app'; @@ -14,8 +15,29 @@ export type RouteMatcherParam = PathMatcherParam; * ['/foo', '/bar(.*)'] * @example * [/^\/foo\/.*$/] + * + * @deprecated This function will be removed in the next major version. Use Nuxt's built-in + * route middleware to protect pages instead: create a named middleware that checks the user's + * authentication status 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'); + * } + * }); + * ``` */ export const createRouteMatcher = (routes: RouteMatcherParam) => { + deprecated( + 'createRouteMatcher', + "Use Nuxt's built-in route middleware to protect pages instead: create a named middleware that checks the user's authentication status and opt pages into it with `definePageMeta({ middleware: 'auth' })`.", + ); + const matcher = createPathMatcher(routes); return (to: RouteLocation) => matcher(to.path); }; diff --git a/packages/nuxt/src/runtime/server/__tests__/routeMatcher.test.ts b/packages/nuxt/src/runtime/server/__tests__/routeMatcher.test.ts new file mode 100644 index 00000000000..20bb76ff391 --- /dev/null +++ b/packages/nuxt/src/runtime/server/__tests__/routeMatcher.test.ts @@ -0,0 +1,31 @@ +import { getRequestURL } from 'h3'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { createRouteMatcher } from '../routeMatcher'; + +const { mockDeprecated } = vi.hoisted(() => ({ + mockDeprecated: vi.fn(), +})); + +vi.mock('@clerk/shared/deprecated', () => ({ + deprecated: mockDeprecated, +})); + +vi.mock('#imports', () => ({ + getRequestURL, +})); + +describe('createRouteMatcher', () => { + beforeEach(() => { + mockDeprecated.mockClear(); + }); + + it('should emit a deprecation warning when called', () => { + createRouteMatcher(['/api/admin(.*)']); + + expect(mockDeprecated).toHaveBeenCalledWith( + 'createRouteMatcher', + "Match API route paths natively inside `clerkMiddleware()` instead, for example: `getRequestURL(event).pathname.startsWith('/api/admin')`. To protect pages, use Nuxt's built-in route middleware.", + ); + }); +}); diff --git a/packages/nuxt/src/runtime/server/routeMatcher.ts b/packages/nuxt/src/runtime/server/routeMatcher.ts index b949cf19133..7509cf09e13 100644 --- a/packages/nuxt/src/runtime/server/routeMatcher.ts +++ b/packages/nuxt/src/runtime/server/routeMatcher.ts @@ -1,3 +1,4 @@ +import { deprecated } from '@clerk/shared/deprecated'; import type { PathMatcherParam } from '@clerk/shared/pathMatcher'; import { createPathMatcher } from '@clerk/shared/pathMatcher'; import type { H3Event } from 'h3'; @@ -15,8 +16,29 @@ export type RouteMatcherParam = PathMatcherParam; * ['/foo', '/bar(.*)'] * @example * [/^\/foo\/.*$/] + * + * @deprecated This function will be removed in the next major version. Match API route paths natively + * inside `clerkMiddleware()` instead, and protect pages with Nuxt's built-in route middleware: + * + * ```ts + * import { clerkMiddleware } from '@clerk/nuxt/server'; + * + * export default clerkMiddleware(event => { + * const { isAuthenticated } = event.context.auth(); + * const { pathname } = getRequestURL(event); + * + * if (!isAuthenticated && pathname.startsWith('/api/admin')) { + * throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }); + * } + * }); + * ``` */ export const createRouteMatcher = (routes: RouteMatcherParam) => { + deprecated( + 'createRouteMatcher', + "Match API route paths natively inside `clerkMiddleware()` instead, for example: `getRequestURL(event).pathname.startsWith('/api/admin')`. To protect pages, use Nuxt's built-in route middleware.", + ); + const matcher = createPathMatcher(routes); return (event: H3Event) => { const url = getRequestURL(event);