From 89a8cb1dab10fe8699e12986f916f9840bfc73c3 Mon Sep 17 00:00:00 2001 From: BridgerB Date: Sat, 28 Feb 2026 13:41:54 -0700 Subject: [PATCH 1/4] feat: allow resolve() to accept pathnames with query strings and hash fragments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `ResolvablePath` type: `Pathname | ${Pathname}?${string} | ${Pathname}#${string}` - Update `resolve()` and `resolveRoute()` signatures to accept `ResolvablePath` - No runtime changes needed — `resolve_route()` already passes through `?` and `#` - Enables `goto(resolve('/products?page=2'))` to satisfy both type checker and `svelte/no-navigation-without-resolve` lint rule Closes #14750 --- .changeset/resolve-accept-query-hash.md | 7 +++++++ packages/kit/src/runtime/app/paths/public.d.ts | 4 ++-- packages/kit/src/runtime/app/paths/types.d.ts | 4 ++-- packages/kit/src/types/ambient.d.ts | 10 ++++++++++ packages/kit/types/index.d.ts | 16 +++++++++++++--- 5 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 .changeset/resolve-accept-query-hash.md diff --git a/.changeset/resolve-accept-query-hash.md b/.changeset/resolve-accept-query-hash.md new file mode 100644 index 000000000000..f0d0cd461a2a --- /dev/null +++ b/.changeset/resolve-accept-query-hash.md @@ -0,0 +1,7 @@ +--- +'@sveltejs/kit': patch +--- + +feat: allow `resolve()` to accept pathnames with query strings and hash fragments + +Adds a `ResolvablePath` type that extends `Pathname` to also accept `?${string}` and `#${string}` suffixes. This allows usage like `goto(resolve('/map?x=1'))` which satisfies both the type checker and the `svelte/no-navigation-without-resolve` lint rule. diff --git a/packages/kit/src/runtime/app/paths/public.d.ts b/packages/kit/src/runtime/app/paths/public.d.ts index ae8b2ed0b0ac..cd31f8b36f5e 100644 --- a/packages/kit/src/runtime/app/paths/public.d.ts +++ b/packages/kit/src/runtime/app/paths/public.d.ts @@ -1,4 +1,4 @@ -import { RouteId, Pathname, ResolvedPathname } from '$app/types'; +import { RouteId, ResolvablePath, ResolvedPathname } from '$app/types'; import { ResolveArgs } from './types.js'; export { resolve, asset, match } from './client.js'; @@ -24,6 +24,6 @@ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit /** * @deprecated Use [`resolve(...)`](https://svelte.dev/docs/kit/$app-paths#resolve) instead */ -export function resolveRoute( +export function resolveRoute( ...args: ResolveArgs ): ResolvedPathname; diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts index 295201c5fd71..7f66f99313a9 100644 --- a/packages/kit/src/runtime/app/paths/types.d.ts +++ b/packages/kit/src/runtime/app/paths/types.d.ts @@ -1,6 +1,6 @@ -import { Pathname, RouteId, RouteParams } from '$app/types'; +import { ResolvablePath, RouteId, RouteParams } from '$app/types'; -export type ResolveArgs = T extends RouteId +export type ResolveArgs = T extends RouteId ? RouteParams extends Record ? [route: T] : [route: T, params: RouteParams] diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index dd7c958e6c1a..3e100a293833 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -123,6 +123,16 @@ declare module '$app/types' { */ export type Pathname = ReturnType; + /** + * A pathname that may optionally include a query string and/or hash fragment. + * Used as input to the `resolve` function. + */ + export type ResolvablePath = + | Pathname + | `${Pathname}?${string}` + | `${Pathname}#${string}` + | `${Pathname}?${string}#${string}`; + /** * `Pathname`, but possibly prefixed with a base path. Used for `page.url.pathname`. */ diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 7d7b33a81863..4e47e561b6a0 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3124,7 +3124,7 @@ declare module '$app/navigation' { } declare module '$app/paths' { - import type { RouteId, Pathname, ResolvedPathname, RouteParams, Asset } from '$app/types'; + import type { RouteId, ResolvablePath, ResolvedPathname, RouteParams, Asset, Pathname } from '$app/types'; /** * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). * @@ -3146,10 +3146,10 @@ declare module '$app/paths' { /** * @deprecated Use [`resolve(...)`](https://svelte.dev/docs/kit/$app-paths#resolve) instead */ - export function resolveRoute( + export function resolveRoute( ...args: ResolveArgs ): ResolvedPathname; - type ResolveArgs = T extends RouteId + type ResolveArgs = T extends RouteId ? RouteParams extends Record ? [route: T] : [route: T, params: RouteParams] @@ -3596,6 +3596,16 @@ declare module '$app/types' { */ export type Pathname = ReturnType; + /** + * A pathname that may optionally include a query string and/or hash fragment. + * Used as input to the `resolve` function. + */ + export type ResolvablePath = + | Pathname + | `${Pathname}?${string}` + | `${Pathname}#${string}` + | `${Pathname}?${string}#${string}`; + /** * `Pathname`, but possibly prefixed with a base path. Used for `page.url.pathname`. */ From 9f3e39688c807b5299f1a8a55789fa0f45599577 Mon Sep 17 00:00:00 2001 From: BridgerB Date: Sat, 28 Feb 2026 14:08:12 -0700 Subject: [PATCH 2/4] fix: update remaining resolve() signatures to use ResolvablePath - Update resolve() in types/index.d.ts (was still using Pathname) - Update JSDoc template in client.js --- packages/kit/src/runtime/app/paths/client.js | 4 ++-- packages/kit/types/index.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/runtime/app/paths/client.js b/packages/kit/src/runtime/app/paths/client.js index d6288783f24d..704eb44b6ae8 100644 --- a/packages/kit/src/runtime/app/paths/client.js +++ b/packages/kit/src/runtime/app/paths/client.js @@ -1,4 +1,4 @@ -/** @import { Asset, RouteId, Pathname, ResolvedPathname } from '$app/types' */ +/** @import { Asset, RouteId, Pathname, ResolvablePath, ResolvedPathname } from '$app/types' */ /** @import { ResolveArgs } from './types.js' */ import { base, assets, hash_routing } from './internal/client.js'; import { resolve_route } from '../../../utils/routing.js'; @@ -47,7 +47,7 @@ const pathname_prefix = hash_routing ? '#' : ''; * ``` * @since 2.26 * - * @template {RouteId | Pathname} T + * @template {RouteId | ResolvablePath} T * @param {ResolveArgs} args * @returns {ResolvedPathname} */ diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 4e47e561b6a0..fc7b90ac6cfd 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3191,7 +3191,7 @@ declare module '$app/paths' { * @since 2.26 * * */ - export function resolve(...args: ResolveArgs): ResolvedPathname; + export function resolve(...args: ResolveArgs): ResolvedPathname; /** * Match a path or URL to a route ID and extracts any parameters. * From 066911f736ec862f6179b3515bb357e616608537 Mon Sep 17 00:00:00 2001 From: BridgerB Date: Mon, 2 Mar 2026 20:47:19 -0700 Subject: [PATCH 3/4] fix: address PR review feedback for resolve() types - Rename ResolvablePath to PathnameWithSearchOrHash - Remove redundant ${Pathname}?${string}#${string} variant - Add RouteIdWithSearchOrHash type so resolve('/[slug]?foo=bar', { slug: 'banana' }) works - Update ResolveArgs to extract params from RouteId with search/hash suffix - Shorten changeset text --- .changeset/resolve-accept-query-hash.md | 4 +- packages/kit/src/runtime/app/paths/client.js | 4 +- .../kit/src/runtime/app/paths/public.d.ts | 9 ++++- packages/kit/src/runtime/app/paths/types.d.ts | 28 ++++++++++--- packages/kit/src/types/ambient.d.ts | 13 +++--- packages/kit/types/index.d.ts | 40 +++++++++++++------ 6 files changed, 67 insertions(+), 31 deletions(-) diff --git a/.changeset/resolve-accept-query-hash.md b/.changeset/resolve-accept-query-hash.md index f0d0cd461a2a..4ff18375d1a3 100644 --- a/.changeset/resolve-accept-query-hash.md +++ b/.changeset/resolve-accept-query-hash.md @@ -2,6 +2,4 @@ '@sveltejs/kit': patch --- -feat: allow `resolve()` to accept pathnames with query strings and hash fragments - -Adds a `ResolvablePath` type that extends `Pathname` to also accept `?${string}` and `#${string}` suffixes. This allows usage like `goto(resolve('/map?x=1'))` which satisfies both the type checker and the `svelte/no-navigation-without-resolve` lint rule. +feat: allow `resolve()` to accept pathnames with a search string and/or hash diff --git a/packages/kit/src/runtime/app/paths/client.js b/packages/kit/src/runtime/app/paths/client.js index 704eb44b6ae8..ec64dee44646 100644 --- a/packages/kit/src/runtime/app/paths/client.js +++ b/packages/kit/src/runtime/app/paths/client.js @@ -1,4 +1,4 @@ -/** @import { Asset, RouteId, Pathname, ResolvablePath, ResolvedPathname } from '$app/types' */ +/** @import { Asset, RouteId, RouteIdWithSearchOrHash, Pathname, PathnameWithSearchOrHash, ResolvedPathname } from '$app/types' */ /** @import { ResolveArgs } from './types.js' */ import { base, assets, hash_routing } from './internal/client.js'; import { resolve_route } from '../../../utils/routing.js'; @@ -47,7 +47,7 @@ const pathname_prefix = hash_routing ? '#' : ''; * ``` * @since 2.26 * - * @template {RouteId | ResolvablePath} T + * @template {RouteIdWithSearchOrHash | PathnameWithSearchOrHash} T * @param {ResolveArgs} args * @returns {ResolvedPathname} */ diff --git a/packages/kit/src/runtime/app/paths/public.d.ts b/packages/kit/src/runtime/app/paths/public.d.ts index cd31f8b36f5e..db6fb6b14f49 100644 --- a/packages/kit/src/runtime/app/paths/public.d.ts +++ b/packages/kit/src/runtime/app/paths/public.d.ts @@ -1,4 +1,9 @@ -import { RouteId, ResolvablePath, ResolvedPathname } from '$app/types'; +import { + RouteId, + RouteIdWithSearchOrHash, + PathnameWithSearchOrHash, + ResolvedPathname +} from '$app/types'; import { ResolveArgs } from './types.js'; export { resolve, asset, match } from './client.js'; @@ -24,6 +29,6 @@ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit /** * @deprecated Use [`resolve(...)`](https://svelte.dev/docs/kit/$app-paths#resolve) instead */ -export function resolveRoute( +export function resolveRoute( ...args: ResolveArgs ): ResolvedPathname; diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts index 7f66f99313a9..d8c371450182 100644 --- a/packages/kit/src/runtime/app/paths/types.d.ts +++ b/packages/kit/src/runtime/app/paths/types.d.ts @@ -1,7 +1,23 @@ -import { ResolvablePath, RouteId, RouteParams } from '$app/types'; +import { + PathnameWithSearchOrHash, + RouteId, + RouteIdWithSearchOrHash, + RouteParams +} from '$app/types'; -export type ResolveArgs = T extends RouteId - ? RouteParams extends Record - ? [route: T] - : [route: T, params: RouteParams] - : [route: T]; +type StripSearchOrHash = T extends `${infer Base}?${string}` + ? Base + : T extends `${infer Base}#${string}` + ? Base + : T; + +export type ResolveArgs = + T extends RouteId + ? RouteParams extends Record + ? [route: T] + : [route: T, params: RouteParams] + : StripSearchOrHash extends infer U extends RouteId + ? RouteParams extends Record + ? [route: T] + : [route: T, params: RouteParams] + : [route: T]; diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index 3e100a293833..b08a2f963160 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -104,6 +104,11 @@ declare module '$app/types' { */ export type RouteId = ReturnType; + /** + * `RouteId`, but possibly suffixed with a search string and/or hash. + */ + export type RouteIdWithSearchOrHash = RouteId | `${RouteId}?${string}` | `${RouteId}#${string}`; + /** * A utility for getting the parameters associated with a given route. */ @@ -124,14 +129,12 @@ declare module '$app/types' { export type Pathname = ReturnType; /** - * A pathname that may optionally include a query string and/or hash fragment. - * Used as input to the `resolve` function. + * `Pathname`, but possibly suffixed with a search string and/or hash. */ - export type ResolvablePath = + export type PathnameWithSearchOrHash = | Pathname | `${Pathname}?${string}` - | `${Pathname}#${string}` - | `${Pathname}?${string}#${string}`; + | `${Pathname}#${string}`; /** * `Pathname`, but possibly prefixed with a base path. Used for `page.url.pathname`. diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index fc7b90ac6cfd..8361ac00eb23 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3124,7 +3124,7 @@ declare module '$app/navigation' { } declare module '$app/paths' { - import type { RouteId, ResolvablePath, ResolvedPathname, RouteParams, Asset, Pathname } from '$app/types'; + import type { RouteId, RouteIdWithSearchOrHash, PathnameWithSearchOrHash, ResolvedPathname, RouteParams, Asset, Pathname } from '$app/types'; /** * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). * @@ -3146,14 +3146,25 @@ declare module '$app/paths' { /** * @deprecated Use [`resolve(...)`](https://svelte.dev/docs/kit/$app-paths#resolve) instead */ - export function resolveRoute( + export function resolveRoute( ...args: ResolveArgs ): ResolvedPathname; - type ResolveArgs = T extends RouteId - ? RouteParams extends Record - ? [route: T] - : [route: T, params: RouteParams] - : [route: T]; + type StripSearchOrHash = T extends `${infer Base}?${string}` + ? Base + : T extends `${infer Base}#${string}` + ? Base + : T; + + type ResolveArgs = + T extends RouteId + ? RouteParams extends Record + ? [route: T] + : [route: T, params: RouteParams] + : StripSearchOrHash extends infer U extends RouteId + ? RouteParams extends Record + ? [route: T] + : [route: T, params: RouteParams] + : [route: T]; /** * Resolve the URL of an asset in your `static` directory, by prefixing it with [`config.kit.paths.assets`](https://svelte.dev/docs/kit/configuration#paths) if configured, or otherwise by prefixing it with the base path. * @@ -3191,7 +3202,7 @@ declare module '$app/paths' { * @since 2.26 * * */ - export function resolve(...args: ResolveArgs): ResolvedPathname; + export function resolve(...args: ResolveArgs): ResolvedPathname; /** * Match a path or URL to a route ID and extracts any parameters. * @@ -3577,6 +3588,11 @@ declare module '$app/types' { */ export type RouteId = ReturnType; + /** + * `RouteId`, but possibly suffixed with a search string and/or hash. + */ + export type RouteIdWithSearchOrHash = RouteId | `${RouteId}?${string}` | `${RouteId}#${string}`; + /** * A utility for getting the parameters associated with a given route. */ @@ -3597,14 +3613,12 @@ declare module '$app/types' { export type Pathname = ReturnType; /** - * A pathname that may optionally include a query string and/or hash fragment. - * Used as input to the `resolve` function. + * `Pathname`, but possibly suffixed with a search string and/or hash. */ - export type ResolvablePath = + export type PathnameWithSearchOrHash = | Pathname | `${Pathname}?${string}` - | `${Pathname}#${string}` - | `${Pathname}?${string}#${string}`; + | `${Pathname}#${string}`; /** * `Pathname`, but possibly prefixed with a base path. Used for `page.url.pathname`. From 977191d43b0fb7f4d286becb8aeaf2b51b786d4c Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 3 Mar 2026 19:02:30 +0800 Subject: [PATCH 4/4] clean up unused type --- packages/kit/src/runtime/app/paths/public.d.ts | 7 +------ packages/kit/src/runtime/app/paths/types.d.ts | 8 ++++---- packages/kit/types/index.d.ts | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/kit/src/runtime/app/paths/public.d.ts b/packages/kit/src/runtime/app/paths/public.d.ts index db6fb6b14f49..600a10317234 100644 --- a/packages/kit/src/runtime/app/paths/public.d.ts +++ b/packages/kit/src/runtime/app/paths/public.d.ts @@ -1,9 +1,4 @@ -import { - RouteId, - RouteIdWithSearchOrHash, - PathnameWithSearchOrHash, - ResolvedPathname -} from '$app/types'; +import { RouteIdWithSearchOrHash, PathnameWithSearchOrHash, ResolvedPathname } from '$app/types'; import { ResolveArgs } from './types.js'; export { resolve, asset, match } from './client.js'; diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts index d8c371450182..f8bad8f8cf21 100644 --- a/packages/kit/src/runtime/app/paths/types.d.ts +++ b/packages/kit/src/runtime/app/paths/types.d.ts @@ -5,10 +5,10 @@ import { RouteParams } from '$app/types'; -type StripSearchOrHash = T extends `${infer Base}?${string}` - ? Base - : T extends `${infer Base}#${string}` - ? Base +type StripSearchOrHash = T extends `${infer Pathname}?${string}` + ? Pathname + : T extends `${infer Pathname}#${string}` + ? Pathname : T; export type ResolveArgs = diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 8361ac00eb23..edf70db5e79e 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3124,7 +3124,7 @@ declare module '$app/navigation' { } declare module '$app/paths' { - import type { RouteId, RouteIdWithSearchOrHash, PathnameWithSearchOrHash, ResolvedPathname, RouteParams, Asset, Pathname } from '$app/types'; + import type { RouteIdWithSearchOrHash, PathnameWithSearchOrHash, ResolvedPathname, RouteId, RouteParams, Asset, Pathname } from '$app/types'; /** * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). *