@@ -69,6 +69,7 @@ import {
6969 type PendingLinkSetter ,
7070} from "./internal/link-status-registry.js" ;
7171import { getCurrentRoutePathnameForWarning } from "./internal/route-pattern-for-warning.js" ;
72+ import { normalizeRouterHref } from "./internal/normalize-router-href.js" ;
7273import { scheduleAppPrefetchFetch } from "./internal/app-prefetch-fetch-queue.js" ;
7374
7475type NavigateEvent = {
@@ -258,73 +259,6 @@ function applyPagesNavigationFallback(href: string, replace: boolean): void {
258259 window . dispatchEvent ( new PopStateEvent ( "popstate" ) ) ;
259260}
260261
261- /**
262- * Collapse repeated forward-slashes (and convert backslashes to forward-slashes)
263- * in the path portion of a URL, preserving any query string.
264- *
265- * Ported from Next.js: packages/next/src/shared/lib/utils/normalize-repeated-slashes.ts
266- * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/utils/normalize-repeated-slashes.ts
267- */
268- function normalizeRepeatedSlashes ( url : string ) : string {
269- const urlParts = url . split ( "?" ) ;
270- const urlNoQueryString = urlParts . shift ( ) ?? "" ;
271- const queryString = urlParts . join ( "?" ) ;
272- return (
273- urlNoQueryString . replace ( / \\ / g, "/" ) . replace ( / \/ \/ + / g, "/" ) +
274- ( queryString ? `?${ queryString } ` : "" )
275- ) ;
276- }
277-
278- /**
279- * Emit Next.js's "Invalid href" `console.error` when `href` contains repeated
280- * forward slashes or backslashes in its path portion, and return the
281- * normalized URL (with `\\` converted to `/` and runs of `/` collapsed). If
282- * the href is already well-formed, the original string is returned unchanged.
283- *
284- * Ported from Next.js: packages/next/src/client/resolve-href.ts
285- * https://github.com/vercel/next.js/blob/canary/packages/next/src/client/resolve-href.ts
286- *
287- * Matches the message asserted by:
288- * test/e2e/repeated-forward-slashes-error/repeated-forward-slashes-error.test.ts
289- *
290- * Note: Next.js fires this warning unconditionally on every call to
291- * `resolveHref`. We mirror that behaviour (no dedup) for exact parity.
292- *
293- * Note: Next.js uses `router.pathname` (the route pattern, e.g.
294- * `/posts/[id]`) for the "in page" segment of the message. The Next.js
295- * compat test asserts this exact text (`in page: '/my/path/[name]'`), so we
296- * source it from the current render's route pattern via
297- * `getCurrentRoutePathnameForWarning()`: the Pages Router SSR context's route
298- * pattern on the server, `window.location.pathname` on the client, falling
299- * back to `"/"`.
300- */
301- function warnAndNormalizeRepeatedSlashesInHref ( urlAsString : string ) : string {
302- // Protocol-relative URLs (e.g. "//example.com/path") are treated by vinext
303- // as external — see `isAbsoluteOrProtocolRelativeUrl` in url-utils. We
304- // intentionally skip the repeated-slash warning and normalization for them
305- // so that locale prefixing and same-origin detection elsewhere in this
306- // shim continue to receive the original href. (Next.js itself does flag
307- // these, but our external-URL handling supersedes that behaviour.)
308- if ( urlAsString . startsWith ( "//" ) ) return urlAsString ;
309-
310- // Strip any protocol prefix (e.g. "https://") so we do not flag the
311- // legitimate `//` that separates the scheme from the authority.
312- const urlProtoMatch = urlAsString . match ( / ^ [ a - z ] [ a - z 0 - 9 + . - ] * : \/ \/ / i) ;
313- const urlAsStringNoProto = urlProtoMatch
314- ? urlAsString . slice ( urlProtoMatch [ 0 ] . length )
315- : urlAsString ;
316- const urlParts = urlAsStringNoProto . split ( "?" , 1 ) ;
317- if ( ! ( urlParts [ 0 ] || "" ) . match ( / ( \/ \/ | \\ ) / ) ) return urlAsString ;
318-
319- const pathname = getCurrentRoutePathnameForWarning ( ) ;
320- console . error (
321- `Invalid href '${ urlAsString } ' passed to next/router in page: '${ pathname } '. Repeated forward-slashes (//) or backslashes \\ are not valid in the href.` ,
322- ) ;
323-
324- const normalizedNoProto = normalizeRepeatedSlashes ( urlAsStringNoProto ) ;
325- return ( urlProtoMatch ? urlProtoMatch [ 0 ] : "" ) + normalizedNoProto ;
326- }
327-
328262export function resolveLinkPrefetchMode (
329263 prefetchProp : LinkProps [ "prefetch" ] ,
330264 isDangerous : boolean ,
@@ -1138,7 +1072,7 @@ const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
11381072 // See packages/next/src/client/resolve-href.ts.
11391073 const resolvedHref =
11401074 typeof rawResolvedHref === "string"
1141- ? warnAndNormalizeRepeatedSlashesInHref ( rawResolvedHref )
1075+ ? normalizeRouterHref ( rawResolvedHref , getCurrentRoutePathnameForWarning ( ) )
11421076 : rawResolvedHref ;
11431077
11441078 const isDangerous = typeof resolvedHref === "string" && isDangerousScheme ( resolvedHref ) ;
0 commit comments