Repository: vercel/next.js
Pull request: legacyBehavior deprecation error should only trigger once

We added a deprecation warning for `legacyBehavior` on the `Link` component but since we show errors in the error overlay, this could lead to a lot of noise if many links are using the `legacyBehavior` prop.

This copies over the `warn-once` utility to `error-once` and uses that utility to dedupe. 

Changed files:
filename: packages/next/src/client/app-dir/link.tsx
patch: @@ -20,6 +20,7 @@ import {
 } from '../components/links'
 import { isLocalURL } from '../../shared/lib/router/utils/is-local-url'
 import { dispatchNavigateAction } from '../components/app-router-instance'
+import { errorOnce } from '../../shared/lib/utils/error-once'
 
 type Url = string | UrlObject
 type RequiredKeys<T> = {
@@ -684,7 +685,7 @@ export default function LinkComponent(
 
   if (legacyBehavior) {
     if (process.env.NODE_ENV === 'development') {
-      console.error(
+      errorOnce(
         '`legacyBehavior` is deprecated and will be removed in a future ' +
           'release. A codemod is available to upgrade your components:\n\n' +
           'npx @next/codemod@latest new-link .\n\n' +

filename: packages/next/src/client/link.tsx
patch: @@ -18,6 +18,7 @@ import { useIntersection } from './use-intersection'
 import { getDomainLocale } from './get-domain-locale'
 import { addBasePath } from './add-base-path'
 import { useMergedRef } from './use-merged-ref'
+import { errorOnce } from '../shared/lib/utils/error-once'
 
 type Url = string | UrlObject
 type RequiredKeys<T> = {
@@ -668,7 +669,7 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
 
     if (legacyBehavior) {
       if (process.env.NODE_ENV === 'development') {
-        console.error(
+        errorOnce(
           '`legacyBehavior` is deprecated and will be removed in a future ' +
             'release. A codemod is available to upgrade your components:\n\n' +
             'npx @next/codemod@latest new-link .\n\n' +

filename: packages/next/src/shared/lib/utils/error-once.ts
patch: @@ -0,0 +1,12 @@
+let errorOnce = (_: string) => {}
+if (process.env.NODE_ENV !== 'production') {
+  const errors = new Set<string>()
+  errorOnce = (msg: string) => {
+    if (!errors.has(msg)) {
+      console.error(msg)
+    }
+    errors.add(msg)
+  }
+}
+
+export { errorOnce }

filename: test/e2e/app-dir/link-component-deprecations/app/page.tsx
patch: @@ -2,8 +2,13 @@ import Link from 'next/link'
 
 export default function HomePage() {
   return (
-    <Link legacyBehavior href="/target-page">
-      <a>Target page</a>
-    </Link>
+    <>
+      <Link legacyBehavior href="/target-page">
+        <a>Target page</a>
+      </Link>
+      <Link legacyBehavior href="/target-page?foo=bar">
+        <a>Another Target page</a>
+      </Link>
+    </>
   )
 }

filename: test/e2e/app-dir/link-component-deprecations/link-component-deprecations.test.ts
patch: @@ -9,14 +9,14 @@ describe('Link component deprecations', () => {
     const browser = await next.browser('/')
     const logs = await browser.log()
 
-    const didWarn = logs.some(
+    const errors = logs.filter(
       (log) =>
         log.source === 'error' &&
         log.message.includes(
           '`legacyBehavior` is deprecated and will be removed in a future release.'
         )
     )
 
-    expect(didWarn).toBe(isNextDev)
+    expect(errors.length).toBe(isNextDev ? 1 : 0)
   })
 })