diff --git a/.changeset/path-matcher-segment-wildcard.md b/.changeset/path-matcher-segment-wildcard.md new file mode 100644 index 00000000000..aedd7fca173 --- /dev/null +++ b/.changeset/path-matcher-segment-wildcard.md @@ -0,0 +1,6 @@ +--- +'@clerk/shared': patch +'@clerk/nextjs': patch +--- + +`createPathMatcher()` and `createRouteMatcher()` route suggestions now use the `:path*` subtree form (e.g. `/dashboard/:path*`) instead of `(.*)`. Unlike `/dashboard(.*)`, which also matches sibling routes such as `/dashboardxyz`, `/dashboard/:path*` matches only `/dashboard` and its path-segment subtree. The new suggestion type is exported as `WithPathSegmentWildcard`; the existing `WithPathPatternWildcard` type is unchanged (now deprecated), and `(.*)` patterns keep working. This only changes the type-level autocomplete suggestion. diff --git a/.github/workflows/api-changes.yml b/.github/workflows/api-changes.yml index f5657d74394..66b72f6bb69 100644 --- a/.github/workflows/api-changes.yml +++ b/.github/workflows/api-changes.yml @@ -107,7 +107,7 @@ jobs: - name: Generate API snapshot run: | - npx --yes "@clerk/break-check@0.5.0" snapshot \ + npx --yes "@clerk/break-check@0.6.0" snapshot \ --output "$GITHUB_WORKSPACE/.api-snapshots-baseline" - name: Upload baseline artifact @@ -172,9 +172,9 @@ jobs: run: echo "BREAK_CHECK_FILTERS=$BREAK_CHECK_FILTERS" >> "$GITHUB_ENV" - name: Break Check - uses: clerk/break-check@28f2635be18aa1127189f088a564645efb3aad90 # v0.5.0 + uses: clerk/break-check@92be9cc726c848b9567c7b869e9b11dc761ddd6e # v0.6.0 with: - break-check-version: '0.5.0' + break-check-version: '0.6.0' # The PR head is already checked out, so build it in place rather than # in a second head worktree. head-ref: '' diff --git a/packages/nextjs/src/server/routeMatcher.ts b/packages/nextjs/src/server/routeMatcher.ts index 428fc3e9dec..15dd32cc8aa 100644 --- a/packages/nextjs/src/server/routeMatcher.ts +++ b/packages/nextjs/src/server/routeMatcher.ts @@ -1,10 +1,10 @@ -import { createPathMatcher, type WithPathPatternWildcard } from '@clerk/shared/pathMatcher'; +import { createPathMatcher, type WithPathSegmentWildcard } from '@clerk/shared/pathMatcher'; import type { Autocomplete } from '@clerk/shared/types'; import type Link from 'next/link'; import type { NextRequest } from 'next/server'; type NextTypedRoute['0']['href']> = T extends string ? T : never; -type RouteMatcherWithNextTypedRoutes = Autocomplete | NextTypedRoute>; +type RouteMatcherWithNextTypedRoutes = Autocomplete | NextTypedRoute>; export type RouteMatcherParam = | Array diff --git a/packages/shared/src/pathMatcher.ts b/packages/shared/src/pathMatcher.ts index d5e6e49f516..dc9c901f4a1 100644 --- a/packages/shared/src/pathMatcher.ts +++ b/packages/shared/src/pathMatcher.ts @@ -1,8 +1,26 @@ import { pathToRegexp } from './pathToRegexp'; import type { Autocomplete } from './types'; +/** + * @deprecated Prefer {@link WithPathSegmentWildcard}; `(.*)` also matches sibling routes + * (e.g. `/dashboard(.*)` matches `/dashboardxyz`). + */ export type WithPathPatternWildcard = `${T & string}(.*)`; -export type PathPattern = Autocomplete; + +type StripTrailingSlash = T extends '/' + ? T + : T extends `${infer Prefix}/` + ? StripTrailingSlash + : T; + +/** + * Suggests the `:path*` subtree form (e.g. `/dashboard/:path*`), which matches on + * path-segment boundaries. `/` is special-cased to `/:path*` to avoid a malformed `//:path*`. + */ +export type WithPathSegmentWildcard = T extends '/' + ? '/:path*' + : `${StripTrailingSlash}/:path*`; +export type PathPattern = Autocomplete; export type PathMatcherParam = Array | RegExp | PathPattern; export class MalformedURLError extends Error {