💡

next/linkとreact-router-domの違いを調べてみた

2022/11/14に公開

Next.jsの場合は、next/link、React(Next.jsを使用しない)の場合はreact-router-domを特に何も考えず使用していたため、違いなどについてまとめていこうと思います。

next/linkとは

https://nextjs.org/docs/api-reference/next/link

https://nextjs.org/docs/routing/introduction

Next.js has a file-system based router built on the concept of pages.
When a file is added to the pages directory, it's automatically available as a route.
The files inside the pages directory can be used to define most common patterns.

The Next.js router allows you to do client-side route transitions between pages, similar to a single-page application.
A React component called Link is provided to do this client-side route transition.

Next.js側でファイルシステムルータシステムがあり、シングルページのアプリケーションと同様にページ間のクライアントサイドのルート遷移を行うためのコンポーネント。

基本的な使い方

以下のページに対してルーティングを行う場合

  • pages/index.js
  • pages/about.js
  • pages/blog/[slug].js
function Home() {
  return (
    <ul>
      <li>
        <Link href="/">Home</Link>
      </li>
      <li>
        <Link href="/about">About Us</Link>
      </li>
      <li>
        <Link href="/blog/hello-world">Blog Post</Link>
      </li>
    </ul>
  )
}

型の情報について

検証したバージョンは13.0.3です。

declare type InternalLinkProps = {
  href: Url;
  as?: Url;
  replace?: boolean;
  scroll?: boolean;
  shallow?: boolean;
  passHref?: boolean;
  prefetch?: boolean;
  locale?: string | false;
  legacyBehavior?: boolean;
  onMouseEnter?: (e: any) => void;
  onTouchStart?: (e: any) => void;
  onClick?: (e: any) => void;
};
export declare type LinkProps = InternalLinkProps;

declare const Link: React.<Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof InternalLinkProps> & InternalLinkProps & {
    children?: React.ReactNode;
} & React.RefAttributes<HTMLAnchorElement>>;

https://onl.tw/GVhMRHz

https://developer.mozilla.org/ja/docs/Web/API/HTMLAnchorElement

react-router-domとは

https://reactrouter.com/en/main/start/overview

In traditional websites, the browser requests a document from a web server, downloads and evaluates CSS and JavaScript assets, and renders the HTML sent from the server. When the user clicks a link, it starts the process all over again for a new page.
Client side routing allows your app to update the URL from a link click without making another request for another document from the server. Instead, your app can immediately render some new UI and make data requests with fetch to update the page with new information.
This enables faster user experiences because the browser doesn't need to request an entirely new document or re-evaluate CSS and JavaScript assets for the next page. It also enables more dynamic user experiences with things like animation.

クライアントサイドルーティングを可能にする仕組み。
ページ遷移する場合、通常のWebサイトではページを読み込むたびにCSS,JavaScriptのアセットをダウントーろして評価したり、サーバーから送られてきたHTMLをレンダリングしている。
クライアントサイドルーティングを使用すると、サーバーから別のドキュメントを再度要求することなく、リンククリックによるURLの更新を行うことサーバーから別のドキュメントを再度要求することなく、リンククリックによるURLの更新を行うことができる。
そのため、通常のWebブラウザでの遷移よりクライアントサイドルーティングを使用することで高速にページ遷移を行うことができる。

基本的な使い方

const router = createBrowserRouter([
  {
    path: "/",
    element: (
      <div>
        <h1>Hello World</h1>
        <Link to="about">About Us</Link>
      </div>
    ),
  },
  {
    path: "about",
    element: <div>About</div>,
  },
]);

createRoot(document.getElementById("root")).render(
  <RouterProvider router={router} />
);

型の情報について

検証したバージョンは6.4.3です。

declare function Link(props: LinkProps): React.ReactElement;

interface LinkProps
  extends Omit<
    React.AnchorHTMLAttributes<HTMLAnchorElement>,
    "href"
  > {
  replace?: boolean;
  state?: any;
  to: To;
  reloadDocument?: boolean;
}

type To = string | Partial<Path>;

next/linkreact-router-domの違いについて

Next.jsを使用する場合、react-router-domではなく、next/linkを使用した方がよい理由について

https://nextjs.org/docs/migrating/from-react-router

  • Decrease bundle size by removing React Router as a dependency.
  • Define your application routes through the file system.
  • Utilize the latest improvements to the Next.js framework.Utilize the latest improvements to the Next.js framework.

バンドルサイズを削減することができ、ファイルシステムを利用してルートの定義を設定することができる。

react-router-domでアプリケーション構造を設定したい場合

import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'

export default function App() {
  return (
    <Router>
      <Switch>
        <Route path="/about">
          <h1>About</h1>
        </Route>
        <Route path="/blog">
          <h1>Blog</h1>
        </Route>
        <Route path="/">
          <h1>Home</h1>
        </Route>
      </Switch>
    </Router>
  )
}

Next.jsでアプリケーション構造を設定したい場合

以下のディレクトリを作成するのみ

  • pages/about.js/about
  • pages/blog.js/blog
  • pages/index.js/

動的なパスを設定したい場合

https://nextjs.org/docs/migrating/from-react-router#nested-routes

react-router-domの場合

https://reactrouter.com/en/main/route/route#dynamic-segments

https://reactrouter.com/en/main/hooks/use-params

import {
  BrowserRouter as Router,
  Switch,
  Route,
  useRouteMatch,
  useParams,
} from 'react-router-dom'

export default function Blog() {
  // Nested route under /blog
  const match = useRouteMatch()

  return (
    <Router>
      <Switch>
        <Route path={`${match.path}/:slug`}>
          <Post />
        </Route>
        <Route path={match.path}>
          <h1>All Blog Posts</h1>
        </Route>
      </Switch>
    </Router>
  )
}

function Post() {
  const { slug } = useParams()
  return <h1>Post Slug: {slug}</h1>
}

Next.jsの場合

https://nextjs.org/docs/api-reference/next/router#userouter

// pages/blog/index.js

export default function Blog() {
  return <h1>All Blog Posts</h1>
}

// pages/blog/[slug].js

import { useRouter } from 'next/router'

export default function Post() {
  const router = useRouter()
  const { slug } = router.query

  return <h1>Post Slug: {slug}</h1>
}

違いについて

  • react-router-domでは、そもそものルーティング機能・ページ遷移まで全てを提供している。使用する場合、ルート部分でルーティングの設定の必要あり。
  • next/linkでは、ルーティング機能はNext.jsが提供しており、ページ遷移のコンポーネントを提供している。

各種UIライブラリでのLinkコンポーネントの実装について

ルーティングの違いについて、上記のとおりだが、各種コンポーネントライブラリがどのように実装されているのか

chakra uiの場合

https://chakra-ui.com/docs/components/link

https://github.com/chakra-ui/chakra-ui/blob/main/packages/components/layout/src/link.tsx

react-router-domの場合、asを送るのみで良い。

// 1. import { Link as ReachLink } from "@reach/router"
// 2. Then use it like this
<Link as={ReachLink} to='/home'>
  Home
</Link>

next/linkの場合、next/linkで囲む必要あり

// 1. import NextLink from "next/link"
// 2. Then use it like this
<NextLink href='/home' passHref>
  <Link>Home</Link>
</NextLink>

Next.jsのバージョンが13以上の場合は、以下にする必要あり

パターン1: legacyBehaviorpassHrefをpropsに入れる

import NextLink from 'next/link'
import { Link, Heading } from '@chakra-ui/react'
...
<NextLink href='...' legacyBehavior passHref>
  <Link>
    <Heading>...</Heading>
  </Link>
</NextLink>

パターン2: カスタムコンポーネントの作成

import NextLink, { type LinkProps as NextLinkProps } from 'next/link'
import { chakra } from '@chakra-ui/react'
// wrap the NextLink with Chakra UI's factory function
const MagicLink = chakra<typeof NextLink, NextLinkProps>(NextLink, {
  // ensure that you're forwarding all of the required props for your case
  shouldForwardProp: (prop) => ['href', 'target', 'children', ...].includes(prop),
})
// use the MagicLink just like you'd use the ordinary Chakra UI link
<MagicLink
  href='...'
  color='...'
  target='...'
>
  ...
</MagicLink>

Material UIの場合

https://mui.com/material-ui/guides/routing/

https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Link/Link.js

react-router-domの場合、Linkコンポーネントにcomponentを送るのみか、カスタムコンポーネントを作成し、それをpropsに送る。

パターン1: Linkコンポーネントにcomponentを送る

import {
  Link as RouterLink,
} from 'react-router-dom';
import Link from '@mui/material/Link';
<Link component={RouterLink} to="/">
  With prop forwarding
</Link>

パターン2: カスタムコンポーネントを作成し、それをpropsに送る

import {
  Link as RouterLink,
  LinkProps as RouterLinkProps,
} from 'react-router-dom';
const LinkBehavior = React.forwardRef<any, Omit<RouterLinkProps, 'to'>>(
  (props, ref) => (
    <RouterLink
      ref={ref}
      to="/material-ui/getting-started/installation/"
      {...props}
    />
  ),
);
<Link component={LinkBehavior}>Without prop forwarding</Link>

Next.jsの場合、以下のナビゲーションの処理のみを行うコンポーネントを使用する。

https://github.com/mui/material-ui/blob/HEAD/examples/nextjs-with-typescript/src/Link.tsx

import Button from '@mui/material/Button';
import { NextLinkComposed } from '../src/Link';

export default function Index() {
  return (
    <Button
      component={NextLinkComposed}
      to={{
        pathname: '/about',
        query: { name: 'test' },
      }}
    >
      Button link
    </Button>
  );
}

また、リンクスタイル付きのものを使用したい場合は以下を使用する。

import Link from '../src/Link';

export default function Index() {
  return (
    <Link
      href={{
        pathname: '/about',
        query: { name: 'test' },
      }}
    >
      Link
    </Link>
  );
}

各種ライブラリでの対応について

  • 基本コンポーネントをpropsで送ることができるようにしている
  • 例外でのパターンは独自でコンポーネントを作ってそれを適応させたり、カスタムコンポーネントを作成したりなどさまざま

Discussion