rsnext/packages/next/client
Tim Neutkens 489e65ed98
Rework <Link> behavior (backwards compatible) (#36436)
Fixes https://github.com/vercel/next.js/discussions/32233

⚠️ If you're looking at this PR please read the complete description including the part about incremental adoption.

## TLDR:

Official support for `<Link href="/about">About</Link>` / `<Link href="/about"><CustomComponent /></Link>` / `<Link href="/about"><strong>About</strong></Link>` where `<Link>` always renders `<a>` without edge cases where it doesn’t render `<a>`. You'll no longer have to put an empty `<a>` in `<Link>` with this enabled.

## Full context

### Changes to `<Link>`

- Added an `legacyBehavior` prop that defaults to `true` to preserve the defaults we have today, this will allow to run a codemod on existing codebases to move them to the version where `legacyBehavior` becomes `false` by default
- When using the new behavior `<Link>` always renders an `<a>` instead of having `React.cloneElement` and passing props onto a child element
- When using the new behavior props that can be passed to `<a>` can be passed to `<Link>`. Previously you could do something like `<Link href="/somewhere"><a target="_blank">Download</a></Link>` but with `<Link>` rendering `<a>` it now allows these props to be set on link. E.g. `<Link href="/somewhere" target="_blank"></Link>` / `<Link href="/somewhere" className="link"></Link>`

### Incremental Adoption / Codemod

The main reason we haven't made these changes before is that it breaks pretty much all Next.js apps, which is why I've been hesitant to make this change in the past. I've spent a bunch of time figuring out what the right approach is to rolling this out and ended up with an approach that requires existing apps to run a codemod that automatically opts their `<Link>` usage into the old behavior in order to keep the app functioning.

This codemod will auto-fix the usage where possible. For example: 

- When you have `<Link href="/about"><a>About</a></Link>` it'll auto-fix to `<Link href="/about">About</Link>`
- When you have `<Link href="/about"><a onClick={() => console.log('clicked')}>About</a></Link>` it'll auto-fix to `<Link href="/about" onClick={() => console.log('clicked')}>About</Link>`
- For cases where auto-fixing can't be applied the `legacyBehavior` prop is added. When you have `<Link href="/about"><Component /></Link>` it'll transform to `<Link href="/about" legacyBehavior><Component /></Link>` so that your app keeps functioning using the old behavior for that particular link. It's then up to the dev to move that case out of the `legacyBehavior` prop.


**This default will be changed in Next.js 13, it does not affect existing apps in Next.js 12 unless opted in via `experimental.newLinkBehavior` and running the codemod.**

Some code samples of what changed:

```jsx
const CustomComponent = () => <strong>Hello</strong>

// Legacy behavior: `<a>` has to be nested otherwise it's excluded

// Renders: <a href="/about">About</a>. `<a>` has to be nested.
<Link href="/about">
  <a>About</a>  
</Link>

// Renders: <strong onClick={nextLinkClickHandler}>Hello</strong>. No `<a>` is included.
<Link href="/about">
  <strong>Hello</strong>
</Link>


// Renders: <strong onClick={nextLinkClickHandler}>Hello</strong>. No `<a>` is included.
<Link href="/about">
  <CustomComponent />
</Link>

// --------------------------------------------------
// New behavior: `<Link>` always renders `<a>`

// Renders: <a href="/about">About</a>. `<a>` no longer has to be nested.
<Link href="/about">
  About
</Link>

// Renders: <a href="/about"><strong>Hello</strong></a>. `<a>` is included.
<Link href="/about">
  <strong>Hello</strong>
</Link>

// Renders: <a href="/about"><strong>Hello</strong></a>. `<a>` is included.
<Link href="/about">
  <CustomComponent />
</Link>
```

---


## Feature

- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Errors have helpful link attached, see `contributing.md`


Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
2022-04-25 22:01:30 +00:00
..
dev Fix to remove webpack message per error (#36452) 2022-04-25 18:28:29 +00:00
portal Use SWC to compile Next.js core server files (#27167) 2021-07-14 18:12:04 +00:00
streaming Update rsc refresh typing and doc example (#35543) 2022-03-25 22:54:07 +01:00
head-manager.ts correctly assess node equality when nonce attribute is present (#27573) 2021-11-10 19:31:32 -06:00
image.tsx Fix next/image usage of onError() (#36305) 2022-04-22 16:13:08 +00:00
index.tsx Fix data encoder and decoder to use stream in renderer (#35936) 2022-04-06 13:34:24 +00:00
link.tsx Rework <Link> behavior (backwards compatible) (#36436) 2022-04-25 22:01:30 +00:00
next-dev.js fix: Prevent page to scroll to top during data HMR (#35739) 2022-03-30 10:45:34 -05:00
next.js Split up & tweak next/client initialization (#33838) 2022-03-07 17:09:55 +00:00
normalize-trailing-slash.ts fix basepath trailing slash (#15200) 2020-07-15 23:53:31 +00:00
page-loader.ts Initial implementation of statically optimized flight data of server component pages (#35619) 2022-04-01 16:13:38 +00:00
performance-relayer.ts Experimental: hook version of reportWebVitals (#28769) 2021-09-24 18:35:33 +00:00
request-idle-callback.ts upgrade to typescript 4.4.3 (#29112) 2021-09-16 18:06:57 +02:00
route-announcer.tsx Fix <RouteAnnouncer/> shouldn't announce initial path under strict mode and React 18 (#34338) 2022-02-14 19:18:39 +00:00
route-loader.ts Improve and refactor some types (#31704) 2021-11-25 10:46:00 +01:00
router.ts Custom app for server components (#33149) 2022-01-14 13:01:00 +00:00
script.tsx Changes to the beforeInteractive strategy to make it work for streaming (#31936) 2022-04-21 21:15:53 +00:00
use-intersection.tsx fix: reset visible state when src/href changed (#35287) 2022-04-04 14:18:49 -04:00
with-router.tsx Update to latest babel versions (#28174) 2021-08-17 09:18:08 +02:00