fix(link): preserve unsafe href click handlers#1264
Conversation
Vinext strips dangerous Link href values before rendering the anchor. That keeps javascript: URLs inert, but the unsafe render branch also dropped the developer-provided onClick handler, so links used as click targets stopped working after hydration. The dangerous-href branch now preserves the anchor ref, link status provider, and user click handler while continuing to omit href. The regression fixture verifies that the click handler runs and the browser URL stays unchanged.
commit: |
|
/bigbonk review |
There was a problem hiding this comment.
Clean fix. The unsafe branch now structurally mirrors the normal branch where it should (ref, LinkStatusContext, intent handlers) and diverges where it must (raw onClick instead of handleClick, no href). The prop ordering is safe — href is Omitted from AnchorHTMLAttributes in LinkProps and destructured before rest, so {...anchorProps} cannot reintroduce it. Prefetching remains correctly disabled via canLinkPrefetch returning false when isDangerous is true.
The E2E test is well-structured: it verifies no href attribute, confirms the click handler fires, and asserts the URL is unchanged. The fixture page is minimal and focused.
Minor suggestion (not blocking): The existing unit test at tests/link-navigation.test.ts:794 ("preserves user intent handlers on dangerous inert links") covers onMouseEnter and onTouchStart but not onClick. Since the core fix is about preserving onClick, consider extending that test to also pass an onClick mock and assert it's captured on the rendered anchor props. The E2E test covers the runtime behavior, but a unit-level assertion would make the coverage more explicit and faster to run.
|
Review submitted. Here's a summary of my findings: Approved. The fix is correct and well-scoped:
One minor suggestion: extend the existing unit test at |
Overview
next/linkhrefs inert without dropping developer click handlers.LinkStatusContext, and useronClickwhile still omittinghref.packages/vinext/src/shims/link.tsx,tests/e2e/app-router/nextjs-compat/javascript-urls.spec.tsjavascript:,data:, andvbscript:Link hrefs remain blocked, but custom interactive handlers continue to work.Why
A blocked URL should not erase unrelated anchor behavior. Next.js attaches user
onClickto the rendered anchor before its internal navigation handling, and its JavaScript URL protections block the dangerous navigation path rather than making the anchor lose user event handlers entirely. Vinext already strips the unsafehref, which is stricter and useful, but the separate unsafe branch accidentally omittedonClick.hreffor unsafe schemes.onClickthrough on the unsafe branch.LinkStatusContextwrapper.What changed
<Link href="javascript:void(0)" onClick={trackEvent}>href, but the customonClickis dropped.href, the customonClickruns, and the page URL does not change.Maintainer review path
packages/vinext/src/shims/link.tsx: inspect the dangerous href branch and confirm it still does not passhrefor call normal navigation logic.tests/fixtures/app-basic/app/nextjs-compat/javascript-urls/link-onclick/page.tsx: fixture proving a custom click handler is observable after hydration.tests/e2e/app-router/nextjs-compat/javascript-urls.spec.ts: regression test asserting nohref, click handler execution, and unchanged URL.Validation
vp test run tests/link.test.tsvp run vinext#buildPLAYWRIGHT_PROJECT=app-router npx playwright test tests/e2e/app-router/nextjs-compat/javascript-urls.spec.ts -g "preserves custom Link onClick"PLAYWRIGHT_PROJECT=app-router npx playwright test tests/e2e/app-router/nextjs-compat/javascript-urls.spec.tsvp checkknip.Risk / compatibility
href; this PR does not make the dangerous URL reachable through Link navigation.Non-goals
pushorreplaceJavaScript URL handling.data:andvbscript:Link hrefs.javascript:anchors.References
onClickis attached to Link's anchor props before internal navigation handling.