Skip to content

fix(link): warn on repeated forward slashes in href#1563

Open
james-elicx wants to merge 2 commits into
mainfrom
fix/link-repeated-slashes-warning
Open

fix(link): warn on repeated forward slashes in href#1563
james-elicx wants to merge 2 commits into
mainfrom
fix/link-repeated-slashes-warning

Conversation

@james-elicx
Copy link
Copy Markdown
Member

Closes #1554

Summary

  • Emit console.error matching Next.js when <Link href> contains repeated forward slashes (e.g. //foo//).

Test plan

  • Test asserts console.error called
  • vp check passes

Closes #1554

When a `<Link href>` contains repeated forward-slashes (e.g. `/foo//bar`)
or a backslash, Next.js's `resolveHref` emits a `console.error` so the
developer notices the invalid URL during dev/CI. vinext's Link shim did
not surface that warning, which caused the Next.js Deploy Suite test
`test/e2e/repeated-forward-slashes-error` to fail waiting for the
message in the CLI output.

Add a small `warnOnRepeatedSlashesInHref` helper that mirrors the regex
and message used by Next.js, and call it from the Link body after the
href is resolved. The href continues to render and navigate unchanged —
Next.js does not block, it only warns.

Ported from Next.js: packages/next/src/client/resolve-href.ts
@james-elicx
Copy link
Copy Markdown
Member Author

/bigbonk review

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 22, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@1563

commit: 074d07f

@github-actions
Copy link
Copy Markdown

Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
web preview production

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good change that closes the parity gap from #1554. The warning regex is correct, the test coverage is solid, and CI is green. Three items to address before merging:

1. Module-level dedup Set is a test-ordering hazard and a Next.js parity gap

warnedRepeatedSlashHrefs is a module-level Set that is never exported and never cleared. This means:

  • Test flakiness risk: If a future test reuses the same bad href (e.g. /hello//world) after another test already rendered it, the warning is silently suppressed. The current tests happen to use unique hrefs so this doesn't fail today, but it's fragile.
  • Parity gap: Next.js does not deduplicate this warning. It fires console.error every time resolveHref encounters repeated slashes — on every render, every navigation. The comment says "Mirrors React's behaviour for repeated warnings" but that's not what Next.js does here.

Two options depending on how strongly you want parity:

  • (a) Remove the Set entirely to match Next.js (simplest, exact parity).
  • (b) Keep it but export a _resetRepeatedSlashWarnings() test helper and call it in afterEach, and add a comment documenting the intentional divergence from Next.js.

I'd lean toward (a) since the dedup is only ~3 lines and removing it gets exact parity.

2. Missing URL normalization after the warning

Next.js doesn't just warn — it also normalizes the URL after detecting repeated slashes. In packages/next/src/client/resolve-href.ts:

const normalizedUrl = normalizeRepeatedSlashes(urlAsStringNoProto)
urlAsString = (urlProtoMatch ? urlProtoMatch[0] : '') + normalizedUrl

This means in Next.js, <Link href="/hello//world"> renders href="/hello/world" (normalized). In vinext, it renders href="/hello//world" (raw). This is a functional difference — the browser will navigate to different paths.

This doesn't need to block this PR if you file a follow-up, but it should be tracked since it affects actual navigation behavior, not just the warning message.

3. router.pathname vs window.location.pathname in the message

Minor: Next.js uses router.pathname (the route pattern, e.g. /posts/[id]) in the warning message. vinext uses window.location.pathname (the actual browser URL, e.g. /posts/42) or "/" during SSR. This difference is cosmetic and unlikely to affect the compat test, but worth noting in a comment if you keep the current approach.

Comment thread packages/vinext/src/shims/link.tsx Outdated

// Track hrefs we have already warned about so we do not spam the console on
// every re-render. Mirrors React's behaviour for repeated warnings.
const warnedRepeatedSlashHrefs = new Set<string>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dedup Set diverges from Next.js, which fires console.error on every call to resolveHref — no dedup. Consider removing it for exact parity. If the dedup is intentional, document it as a deliberate divergence and export a test helper to clear state between test runs (the module-level Set persists across tests within a Vitest worker).

console.error(
`Invalid href '${urlAsString}' passed to next/router in page: '${pathname}'. Repeated forward-slashes (//) or backslashes \\ are not valid in the href.`,
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next.js also normalizes the URL after warning — it calls normalizeRepeatedSlashes() to collapse /// and \\/, then continues routing with the cleaned URL. Without that step, <Link href="/hello//world"> renders href="/hello//world" in the DOM (vinext) vs href="/hello/world" (Next.js). This affects actual navigation, not just the warning.

Fine to ship the warning first and follow up with normalization, but worth filing a follow-up issue.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 22, 2026

Review posted on PR #1563. Here's a summary of the findings:

Overall: Good change that closes the parity gap from #1554. The warning regex is correct (identical to Next.js), tests cover the key cases (positive match, backslash, protocol prefix, clean hrefs, query string), and CI is nearly all green.

Three items flagged (requested changes):

  1. Dedup Set diverges from Next.js — Next.js fires console.error on every render/navigation with repeated slashes; it does not deduplicate. The module-level Set is also a latent test-ordering hazard since it persists across tests and is never cleared. Suggested either removing it for exact parity or documenting the intentional divergence.

  2. Missing URL normalization — Next.js doesn't just warn, it also normalizes the URL (collapses // to /, replaces \\ with /) before routing. Without this, <Link href="/hello//world"> renders the raw malformed href in the DOM. This is a functional difference that affects navigation. Filed as ok to follow up separately.

  3. router.pathname vs window.location.pathname — Minor cosmetic difference in the warning message (Next.js shows the route pattern, vinext shows the browser URL or "/" during SSR). Noted in the review body but not blocking.

github run

Address review feedback on #1563:

- Remove the module-level `warnedRepeatedSlashHrefs` Set. Next.js fires
  this warning on every `resolveHref` call, so dedup was an unintended
  divergence and a latent test-ordering hazard.
- Add `normalizeRepeatedSlashes` (port of
  packages/next/src/shared/lib/utils/normalize-repeated-slashes.ts) and
  apply it after warning, so `<Link href="/a//b">` renders `href="/a/b"`
  exactly as Next.js does.
- Preserve protocol-relative URLs (`//example.com/...`) unchanged —
  vinext treats those as external via `isAbsoluteOrProtocolRelativeUrl`,
  so we skip both the warning and the normalization for them to avoid
  regressing the existing "locale does not mangle protocol-relative
  URLs" test.
- Document the `window.location.pathname` fallback (Next.js uses
  `router.pathname`; the difference is cosmetic and not asserted by the
  compat test).
- Add tests covering no-dedup behaviour, href normalization (including
  backslashes, query-string preservation, and protocol-prefixed URLs).
@james-elicx
Copy link
Copy Markdown
Member Author

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three items from the previous review are resolved in the follow-up commit:

  1. Dedup Set removed — No trace of warnedRepeatedSlashHrefs remains. Warning fires on every render, matching Next.js. The "no dedup" behavior is explicitly tested ("fires on every render") and documented in the JSDoc.

  2. URL normalization implementednormalizeRepeatedSlashes() ported from Next.js collapses /// and \\/ in the path portion, preserving query strings and protocol prefixes. The rendered href attribute now matches Next.js behavior. Tests cover: slash collapsing, backslash normalization, query string preservation, and protocol preservation for absolute URLs.

  3. window.location.pathname fallback documented — JSDoc explicitly notes the cosmetic difference from router.pathname and that the Next.js compat test doesn't assert on this segment.

Implementation looks correct. The protocol-relative URL early return (startsWith("//")) is a reasonable divergence since vinext treats those as external URLs — and it's documented inline. Test coverage is thorough with 10 test cases covering positive/negative/edge cases.

CI: Check, all E2E projects, and deploys are green. Vitest shards still running but the relevant link.test.ts changes are unit-level and already validated by Check.

Ship it.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 22, 2026

Review posted — approved PR #1563. All three items from the previous round (dedup Set removal, URL normalization, pathname fallback documentation) are resolved. CI is green across Check, E2E, and deploys.

github run

@james-elicx james-elicx marked this pull request as ready for review May 22, 2026 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App Router: missing console warning for <Link href> with repeated slashes

1 participant