かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Next.js v13 × Chakra UI NextLink を使うTips

Next.js の Link に Chakra UI の Link や Button のデザインを適応させる方法の Tips

環境
  • Next.js 13.2.4
    • Reat 18.2.0
  • @chakra-ui/react 2.5.3
  • TypeScript 5.0.2

cf. Getting Started with Next.js - Chakra UI

$ npm i @chakra-ui/next-js

Link

import { Link } from '@chakra-ui/next-js';

export default Page(): JSX.Element {
  return (
    <Link href="/page">Link Text</Link>
  );
}

Chakra UI の as に NextLink を渡せばOK

import NextLink from 'next/link';
import { Button } from '@chakra-ui/react';

export default Page(): JSX.Element {
  return (
    <Button as={NextLink}>Button Label</Button>
  );
}

Next.js では SPA的なページ遷移をするには Next が用意している Link コンポーネントを使う必要があります
Next の Link コンポーネント<a> タグを出力するので同様に <a> タグや <button> タグを出力するコンポーネントを使用する時は passHref を使うよう書かれています

If the child is a custom component that wraps an <a> tag - Next.js

If the child of Link is a custom component that wraps an <a> tag, you must add passHref to Link. This is necessary if you’re using libraries like styled-components. Without this, the <a> tag will not have the href attribute, which hurts your site's accessibility and might affect SEO. If you're using ESLint, there is a built-in rule next/link-passhref to ensure correct usage of passHref.

import Link from 'next/link'
import styled from 'styled-components'

// This creates a custom component that wraps an <a> tag
const RedLink = styled.a`
  color: red;
`

function NavLink({ href, name }) {
  return (
    <Link href={href} passHref legacyBehavior>
      <RedLink>{name}</RedLink>
    </Link>
  )
}

export default NavLink
If the child is a functional component

If the child of Link is a functional component, in addition to using passHref and legacyBehavior, you must wrap the component in React.forwardRef:

import Link from 'next/link'

// `onClick`, `href`, and `ref` need to be passed to the DOM element
// for proper handling
const MyButton = React.forwardRef(({ onClick, href }, ref) => {
  return (
    <a href={href} onClick={onClick} ref={ref}>
      Click Me
    </a>
  )
})

function Home() {
  return (
    <Link href="/about" passHref legacyBehavior>
      <MyButton />
    </Link>
  )
}

export default Home

cf. next/link | Next.js

Next.js v13 では Chakra UI のコンポーネントを使った時に passHref入れ子にすると Hydration failed エラーが発生する問題

Next.js v13 ではドキュメントの例に習い Chakra UI の <Link>, <Button> コンポーネントを使おうとすると、リロード時に <a> タグが入れ子になっているという理由で Hydration failed エラーが発生してしまうようです

import NextLink from 'next/link';
import { Link } from '@chakra-ui/react';

export default Page(): JSX.Element {
  return (
    <NextLink href='/page' passHref>
      <Link>Link Text</Link>
    </NextLink>
  );
}

=> Error: Hydration failed because the initial UI does not match what was rendered on the server. Warning: Expected server HTML to contain a matching <a> in <a>.

Button も as="a" で a タグ扱いにすると同様のエラーが発生する

Button のデザインを使いたいリンクを作成したい時にありがちなパターンも passHref入れ子にすると Hydration failed エラーが発生する

import NextLink from 'next/link';
import { Button } from '@chakra-ui/react';

export default Page(): JSX.Element {
  return (
    <NextLink href='/page' passHref>
      <Button as="a">Link Text</Button>
    </NextLink>
  );
}

=> Error: Hydration failed because the initial UI does not match what was rendered on the server. Warning: Expected server HTML to contain a matching <a> in <a>.

Next.js v13 で Chakra UI の Link, Buttton を使う方法

cf. Getting Started with Next.js - Chakra UI

$ npm i @chakra-ui/next-js
- import NextLink from 'next/link';
- import { Link } from '@chakra-ui/react';
+ import { Link } from '@chakra-ui/next-js'

export default Page(): JSX.Element {
  return (
-   <NextLink href='/page' passHref>
-     <Link>Link Text</Link>
-   </NextLink>
+   <Link href="/page">Link Text</Link>
  );
}

passHref を使うのではなく Chakra UI の as に NextLink を指定すれば NextLink な ボタンになる

import NextLink from 'next/link';
import { Button } from '@chakra-ui/react';

export default Page(): JSX.Element {
  return (
-   <NextLink href='/page' passHref>
-     <Button as="a">Link Text</Button>
-   </NextLink>
+   <Button as={NextLink}>Link Text</Button>
  );
}

所感

Next.js と Chakra UI を使ってサクッとサンプルを作りたかっただけなのですが、思いもしない箇所でハマってしまいました。
Hydration failed のエラーについては理解が浅かったのでまた別途調べておきたいと思いました。

おわり


[参考]