diff --git a/.changeset/nextjs-deprecate-route-matcher.md b/.changeset/nextjs-deprecate-route-matcher.md
new file mode 100644
index 00000000000..b1979d744ef
--- /dev/null
+++ b/.changeset/nextjs-deprecate-route-matcher.md
@@ -0,0 +1,22 @@
+---
+'@clerk/nextjs': patch
+---
+
+Deprecate `createRouteMatcher()` in favor of resource-based auth checks.
+
+Middleware-based auth checks rely on path matching, which can diverge from how Next.js routes requests and leave protected resources reachable.
+
+For a migration guide, see:
+ https://clerk.com/docs/guides/development/upgrading/upgrade-guides/migrating-from-create-route-matcher
+
+Instead of protecting routes from middleware, move auth checks into each protected page, layout, API route, or Server Function, for example:
+
+```ts
+import { auth } from '@clerk/nextjs/server'
+
+export default async function Page() {
+ await auth.protect()
+
+ return
Dashboard
+}
+```
diff --git a/packages/nextjs/src/server/__tests__/routeMatcher.test.ts b/packages/nextjs/src/server/__tests__/routeMatcher.test.ts
new file mode 100644
index 00000000000..d4871c93d5e
--- /dev/null
+++ b/packages/nextjs/src/server/__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(['/foo(.*)']);
+
+ expect(mockDeprecated).toHaveBeenCalledWith(
+ 'createRouteMatcher',
+ 'Use resource-based auth checks instead. Move auth checks into each page, layout, API route, or Server Function that accesses protected data. Middleware-based auth checks rely on path matching, which can diverge from how Next.js routes requests and leave protected resources reachable. For a migration guide, see: https://clerk.com/docs/guides/development/upgrading/upgrade-guides/migrating-from-create-route-matcher',
+ );
+ });
+});
diff --git a/packages/nextjs/src/server/routeMatcher.ts b/packages/nextjs/src/server/routeMatcher.ts
index 428fc3e9dec..b8fd30705b1 100644
--- a/packages/nextjs/src/server/routeMatcher.ts
+++ b/packages/nextjs/src/server/routeMatcher.ts
@@ -1,3 +1,4 @@
+import { deprecated } from '@clerk/shared/deprecated';
import { createPathMatcher, type WithPathPatternWildcard } from '@clerk/shared/pathMatcher';
import type { Autocomplete } from '@clerk/shared/types';
import type Link from 'next/link';
@@ -19,8 +20,20 @@ export type RouteMatcherParam =
* You can use glob patterns to match multiple routes or a function to match against the request object.
* Path patterns and regular expressions are supported, for example: `['/foo', '/bar(.*)'] or `[/^\/foo\/.*$/]`
* For more information, see: https://clerk.com/docs
+ *
+ * @deprecated This function will be removed in the next major version. Use resource-based auth checks instead.
+ * Move auth checks into each page, layout, API route, or Server Function that accesses protected data.
+ * Middleware-based auth checks rely on path matching, which can diverge from how Next.js routes requests and
+ * leave protected resources reachable.
+ *
+ * For a migration guide, see:
+ * https://clerk.com/docs/guides/development/upgrading/upgrade-guides/migrating-from-create-route-matcher
*/
export const createRouteMatcher = (routes: RouteMatcherParam) => {
+ deprecated(
+ 'createRouteMatcher',
+ 'Use resource-based auth checks instead. Move auth checks into each page, layout, API route, or Server Function that accesses protected data. Middleware-based auth checks rely on path matching, which can diverge from how Next.js routes requests and leave protected resources reachable. For a migration guide, see: https://clerk.com/docs/guides/development/upgrading/upgrade-guides/migrating-from-create-route-matcher',
+ );
if (typeof routes === 'function') {
return (req: NextRequest) => routes(req);
}