diff --git a/packages/vinext/src/shims/link.tsx b/packages/vinext/src/shims/link.tsx index 98e4a656d..d0bbf5b35 100644 --- a/packages/vinext/src/shims/link.tsx +++ b/packages/vinext/src/shims/link.tsx @@ -547,16 +547,24 @@ const Link = forwardRef(function Link( // Block dangerous URI schemes (javascript:, data:, vbscript:). // Render an inert without href to prevent XSS while preserving - // styling and attributes like className, id, aria-*. + // styling, refs, and developer event handlers like onClick. // This check is placed after all hooks to satisfy the Rules of Hooks. if (isDangerous) { if (process.env.NODE_ENV !== "production") { console.warn(` blocked dangerous href: ${resolvedHref}`); } return ( - - {children} - + + + {children} + + ); } diff --git a/tests/e2e/app-router/nextjs-compat/javascript-urls.spec.ts b/tests/e2e/app-router/nextjs-compat/javascript-urls.spec.ts index e85c59e83..3ea25e531 100644 --- a/tests/e2e/app-router/nextjs-compat/javascript-urls.spec.ts +++ b/tests/e2e/app-router/nextjs-compat/javascript-urls.spec.ts @@ -95,4 +95,21 @@ test.describe("javascript-urls", () => { await page.locator('a[href="/nextjs-compat/javascript-urls/safe"]').click(); await expect(page).toHaveURL(`${BASE}/nextjs-compat/javascript-urls/safe`); }); + + // Next.js keeps user-provided Link onClick handlers on anchors whose href is + // later blocked by React/Next.js javascript: URL protections. + // Reference implementation: https://github.com/vercel/next.js/blob/canary/packages/next/src/client/link.tsx + test("preserves custom Link onClick handlers when blocking unsafe hrefs", async ({ page }) => { + await page.goto(`${BASE}/nextjs-compat/javascript-urls/link-onclick`); + await waitForAppRouterHydration(page); + const initialUrl = page.url(); + + const unsafeLink = page.locator("#unsafe-link"); + await expect(unsafeLink).not.toHaveAttribute("href", /.+/); + + await unsafeLink.click(); + + await expect(page.locator("#click-count")).toHaveText("clicks: 1"); + expect(page.url()).toBe(initialUrl); + }); }); diff --git a/tests/fixtures/app-basic/app/nextjs-compat/javascript-urls/link-onclick/page.tsx b/tests/fixtures/app-basic/app/nextjs-compat/javascript-urls/link-onclick/page.tsx new file mode 100644 index 000000000..17f4c6794 --- /dev/null +++ b/tests/fixtures/app-basic/app/nextjs-compat/javascript-urls/link-onclick/page.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { DANGEROUS_JAVASCRIPT_URL } from "../bad-url"; + +export default function Page() { + const [clicks, setClicks] = useState(0); + + return ( + <> +

clicks: {clicks}

+ { + setClicks((value) => value + 1); + }} + > + unsafe link with custom click handler + + + ); +}