Learn Next.js 官方教學筆記 - Chapter 5 : Navigating Between Pages

此系列為 Next.js 官方教學 Learn Next.js 的筆記。

文章連結:Chapter 5 : Navigating Between Pages


在前一個章節,我們建立了 dashboard 的 layout 以及 pages。

現在要來新增一些連結讓使用者可以在 dashboard routes 間作切換。

在這一章節可以學到以下幾個項目:

  • 怎麼使用 next/link 元件。
  • 怎麼使用 usePathname() hook 來顯示 active link。
  • navigation 怎麼在 Next.js 運作的。

Why optimize navigation?

為了使各個頁面有連結,傳統上會使用 <a> HTML tag 。且在現在的程式中,sidebar link 就是使用 <a> 。但需要注意一些事情,就是你有發現當你點擊這些連結時發生了什麼嗎?

沒錯!這樣的方式會造成整個頁面的重新渲染。

The <Link> component

在 Next.js 中,你可以使用 <Link /> component 來連結應用程式間的各個頁面。<Link /> 可以讓你用 JavaScript 來使用 client-side navigation

所以為了使用 <Link /> ,打開 /app/ui/dashboard/nav-links.tsx,然後從 next/link import Link 元件。最後用 <Link /> 取代 <a>

import {
  UserGroupIcon,
  HomeIcon,
  DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';

// ...

export default function NavLinks() {
  return (
    <>
      {links.map((link) => {
        const LinkIcon = link.icon;
        return (
          <Link
            key={link.name}
            href={link.href}
            className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3"
          >
            <LinkIcon className="w-6" />
            <p className="hidden md:block">{link.name}</p>
          </Link>
        );
      })}
    </>
  );
}

就如同你所看到的,Link 元件的用法跟 <a> tag 用法很相似,但不是使用 <a href="…"> 而是使用 <Link href="…">

所以在更改並存儲後,可以看到當點擊切換頁面的按鈕後,會發現頁面並不會全部重新整理。

Automatic code-splitting and prefetching

為了改善 navigation 的體驗,Next.js 會自動利用 route segments 切分程式碼。這樣的方式跟傳統的 React SPA 在最初載入時就會載入全部的程式進瀏覽器的方式不太一樣。

利用 routes 來切分程式碼代表頁面變成獨立的。所以若頁面發生錯誤了,剩下的程式還是可以照常執行。

更甚者,不論 <Link> 元件何時會出現在瀏覽器的 viewpoint 中,Next.js 都會自動為了linked route,在背景中 prefetch 程式碼。而在使用者點擊 link 前,這個頁面的程式就會先在背景中被載入,藉此來讓頁面間的轉換看起來像是及時的。

更多內容可以參考 how navigation works

一個常見的 UI pattern 是藉由顯示 active link 來指出使用者當前在哪個頁面。為了做到這點,你需要從 URL 去得到使用者當前的路徑。Next.js 提供一個 hook usePathname() 來讓你去確認並執行這個 pattern。

而因為 usePathname() 是一個 hook ,所以你需要把 nav-links.tsx 轉換為一個 Client Component。並加上 React 的 "use client" 在檔案的最上方,然後從 next/navigation import usePathname()

'use client';

import {
  UserGroupIcon,
  HomeIcon,
  InboxIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';
import { usePathname } from 'next/navigation';

// ...

接下來在 <NavLinks /> 裡利用 usePathname() 賦值給一個叫 pathname 的變數:

export default function NavLinks() {
  const pathname = usePathname();
  // ...
}

接下來可以使用 clsx 這個 library (在 CSS styling 介紹過)來讓 link active時,可以依據條件來應用不同的 className。條件是當 link.hrefpathname 符合時,link 會顯示藍色的字以及藍色的背景。

以下是最終的程式碼:

'use client';

import {
  UserGroupIcon,
  HomeIcon,
  DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import clsx from 'clsx';

// ...

export default function NavLinks() {
  const pathname = usePathname();

  return (
    <>
      {links.map((link) => {
        const LinkIcon = link.icon;
        return (
          <Link
            key={link.name}
            href={link.href}
            className={clsx(
              'flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3',
              {
                'bg-sky-100 text-blue-600': pathname === link.href,
              },
            )}
          >
            <LinkIcon className="w-6" />
            <p className="hidden md:block">{link.name}</p>
          </Link>
        );
      })}
    </>
  );
}