Next.jsのルーターにAnt Designのメニューと統合する方法

この投稿では、Ant Designの`<Menu>`コンポーネントとNext.jsのルーターを統合して、次のことができるようにする方法を説明します。

- サイドバーのメニューをクリックしたときに、Next.jsのルーターを使って画面遷移できるようにする
- 現在アクセス中のURLに応じてメニューをハイライトする

次の動画のような振る舞いを作っていきます。

<iframe width="560" height="315" src="https://www.youtube.com/embed/cYyeXjK5gJc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

このチュートリアルは、

https://qiita.com/suin/items/29c8ccf0ddc73565c91d

の続きです。

このチュートリアルで作ったプログラムの完成形は[GitHub](https://github.com/suin/nextjs-ant-design-typescript/tree/5614900afe9f04dc9bc553ac370f9430b4e31f7c)にアップしてあります。

実装後の動作デモは[Vercelにて見れます](https://nextjs-ant-design-typescript-m27i2q2w0-suin.vercel.app/)。

## Next.jsのルーターにAnt Designのメニューと統合する手順

まず、管理画面レイアウト`AdminLayout`が全ページで適用されるようにしたいので、`index.tsx`から`_app.tsx`に移動します:

```tsx:pages/_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import "antd/dist/antd.css";
import dynamic from "next/dynamic";

const AdminLayout = dynamic(() => import("../layouts/adminLayout"), {
  ssr: false,
});

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <AdminLayout>
      <Component {...pageProps} />
    </AdminLayout>
  );
}
```

次に、AdminLayoutのメニューにNext.jsのルーターを組み込んでいきます:

```tsx
// layouts/adminLayout.tsx
import {
  DashboardOutlined,
  LaptopOutlined,
  MenuFoldOutlined,
  MenuUnfoldOutlined,
  UserOutlined,
} from "@ant-design/icons";
import { Layout, Menu } from "antd";
import Link from "next/link";
import { useRouter } from "next/router";
import { ReactNode, useState } from "react";
import styles from "./adminLayout.module.css";

const { Header, Sider, Content } = Layout;

export default function AdminLayout({
  children,
}: {
  readonly children: ReactNode;
}) {
  const [collapsed, setCollapsed] = useState<boolean>(false);
  const router = useRouter();

  function toggle() {
    setCollapsed(!collapsed);
  }

  return (
    <Layout>
      <Sider trigger={null} collapsible collapsed={collapsed}>
        <div className={styles.logo} />
        <Menu theme="dark" mode="inline" selectedKeys={[router.pathname]}>
          <Menu.Item key="/" icon={<DashboardOutlined />}>
            <Link href="/">ダッシュボード</Link>
          </Menu.Item>
          <Menu.Item key="/users" icon={<UserOutlined />}>
            <Link href="/users">利用者</Link>
          </Menu.Item>
          <Menu.Item key="/equipments" icon={<LaptopOutlined />}>
            <Link href="/equipments">備品</Link>
          </Menu.Item>
        </Menu>
      </Sider>
      <Layout className="site-layout">
        <Header className={styles.siteLayoutBackground} style={{ padding: 0 }}>
          {collapsed ? (
            <MenuUnfoldOutlined className={styles.trigger} onClick={toggle} />
          ) : (
            <MenuFoldOutlined className={styles.trigger} onClick={toggle} />
          )}
        </Header>
        <Content
          className={styles.siteLayoutBackground}
          style={{
            margin: "24px 16px",
            padding: 24,
            minHeight: 280,
          }}
        >
          {children}
        </Content>
      </Layout>
    </Layout>
  );
}
```

ポイントとしては、

- `<Menu>`コンポーネントの`selectedKeys`に現在ページの`pathname`を与えるようにする
- `<Menu.Item>`内のchildrenは、`next/link`の`<Link>`コンポーネントにする

です。

あとは、3つの画面を実装していきます:

```tsx
// pages/index.tsx
export default function Home() {
  return "ダッシュボード";
}

// pages/users.tsx
export default function Users() {
  return "利用者";
}

// pages/equipments.tsx
export default function Equipments() {
  return "備品";
}
```

これでAnt DesignのメニューとNext.jsのRouterが統合できました。

冒頭の動画ように、URLとメニューが連動するような動作になっているはずです。