لینک‌سازی با Link

در درس «ساخت اسکلت اولیه‌ی DevNotes» برای اولین بار از کامپوننت Link استفاده کردیم و گفتیم جزئیاتش را بعداً می‌بینیم. حالا وقتش است. لینک ساده‌ترین راه حرکت کاربر بین صفحات است، اما در Next.js فقط یک تگ <a> ساده نیست؛ ناوبری سریع، Prefetch و حفظ Layout را هم به همراه دارد. در این درس یاد می‌گیریم Link چه کاری می‌کند، چطور آدرس‌های مختلف را به آن بدهیم و چطور لینک «فعال» بسازیم.

پیش‌نیاز

  • فصل «App Router، صفحات و Layoutها» و اسکلت پروژه‌ی DevNotes
  • آشنایی با تگ <a> و ویژگی href در HTML

چرا Link و نه تگ a؟

با تگ <a> معمولی، هر کلیک مرورگر را وادار می‌کند کل صفحه را از اول از سرور بگیرد و همه‌چیز را از نو بسازد. کامپوننت Link همین کار را بهتر انجام می‌دهد:

ویژگی تگ <a> کامپوننت Link
نوع ناوبری بارگذاری کامل صفحه ناوبری سمت کلاینت؛ فقط بخش تغییرکرده به‌روز می‌شود
Layoutهای مشترک از نو ساخته می‌شوند حفظ می‌شوند
Prefetch (بارگیری پیشاپیش مسیر) ندارد دارد؛ جزئیاتش در درس «Prefetching و رفتار Navigation»
خروجی HTML <a> همان <a> واقعی؛ برای SEO و دسترس‌پذیری مشکلی ایجاد نمی‌کند

نکته‌ی مهم: Link خودش یک تگ <a> رندر می‌کند، پس لازم نیست داخل آن تگ <a> جداگانه بگذارید. این الگو در آموزش‌های قدیمی دیده می‌شود و در نسخه‌های جدید لازم نیست.

استفاده‌ی پایه

tsx
import Link from "next/link";

export default function Example() {
  return <Link href="/posts">نوشته‌ها</Link>;
}
  • Link را از next/link وارد می‌کنیم.
  • ویژگی href الزامی است و مسیر مقصد را می‌گیرد.
  • می‌توان از Link هم در Server Component و هم در Client Component استفاده کرد؛ نیازی به "use client" نیست.
  • ویژگی‌های معمول تگ a مثل className، aria-* و id را هم می‌پذیرد.

شکل‌های مختلف href

۱. رشته‌ی ساده

tsx
<Link href="/about">درباره</Link>

۲. مسیر پویا با Template String

وقتی بخشی از آدرس از داده می‌آید، مسیر را با Template String می‌سازیم. این الگو برای لینک‌دادن از فهرست نوشته‌ها به صفحه‌ی هر نوشته کاربرد دارد؛ خود مسیر پویا را در درس بعد می‌سازیم:

tsx
type Post = { slug: string; title: string };

function PostLink({ post }: { post: Post }) {
  return (
    <Link href={`/posts/${post.slug}`}>
      {post.title}
    </Link>
  );
}

اگر بخشی از آدرس ممکن است کاراکتر خاص (فاصله، / یا ?) داشته باشد، آن را با encodeURIComponent رمزگذاری کنید تا آدرس خراب نشود:

tsx
<Link href={`/posts?q=${encodeURIComponent(searchText)}`}>
  جست‌وجو
</Link>

۳. آبجکت URL

به‌جای رشته می‌توان یک آبجکت داد. این روش برای ساخت Query String خواناتر است:

tsx
<Link
  href={{
    pathname: "/posts",
    query: { tag: "nextjs", page: 2 },
  }}
>
  نوشته‌های Next.js
</Link>

این لینک به /posts?tag=nextjs&page=2 می‌رود. خواندن Query String در صفحه را در درس «searchParams و Query String» یاد می‌گیریم.

۴. لینک به بخشی از همان صفحه (Hash)

tsx
<Link href="/about#contact">تماس با ما</Link>

ویژگی‌های پرکاربرد Link

ویژگی کاربرد
href مقصد لینک (رشته یا آبجکت)؛ الزامی است.
replace به‌جای افزودن یک رکورد جدید به تاریخچه‌ی مرورگر، رکورد فعلی را جایگزین می‌کند؛ دکمه‌ی «بازگشت» به صفحه‌ی قبل از آن نمی‌رود.
scroll پیش‌فرض true است و بعد از ناوبری به بالای صفحه می‌رود. با scroll={false} این رفتار خاموش می‌شود.
prefetch کنترل Prefetch؛ مثلاً prefetch={false} آن را غیرفعال می‌کند. به‌صورت پیش‌فرض، Next.js خودش تصمیم می‌گیرد.
tsx
<Link href="/posts" replace>نوشته‌ها</Link>
<Link href="/posts" scroll={false}>نوشته‌ها</Link>
<Link href="/posts" prefetch={false}>نوشته‌ها</Link>

Link گزینه‌های دیگری هم دارد که کاربرد تخصصی‌تری دارند و در این دوره لازم نمی‌شوند.

لینک به بیرون از سایت

برای آدرس‌های خارجی مزیت ناوبری سمت کلاینت وجود ندارد، پس تگ <a> معمولی انتخاب ساده‌تری است. اگر لینک را در تب جدید باز می‌کنید، rel مناسب را هم بنویسید:

tsx
<a
  href="https://nextjs.org/docs"
  target="_blank"
  rel="noopener noreferrer"
>
  مستندات Next.js
</a>

لینک فعال (Active Link)

در منوی ناوبری معمولاً می‌خواهیم لینکِ صفحه‌ی جاری مشخص باشد. برای این کار باید مسیر فعلی را بدانیم. Hook با نام usePathname این مسیر را می‌دهد، اما فقط در Client Component کار می‌کند. در فصل «Server Components و Client Components» دلیل این محدودیت را کامل می‌فهمیم؛ فعلاً فقط کافی است بدانیم فایلی که با "use client" شروع شود در مرورگر هم اجرا می‌شود.

یک کامپوننت کوچک می‌سازیم که فقط همین وظیفه را دارد:

tsx
// components/NavLink.tsx
"use client";

import type { ReactNode } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";

type NavLinkProps = {
  href: string;
  children: ReactNode;
};

export default function NavLink({ href, children }: NavLinkProps) {
  const pathname = usePathname();

  const isActive =
    href === "/"
      ? pathname === "/"
      : pathname === href || pathname.startsWith(`${href}/`);

  return (
    <Link href={href} aria-current={isActive ? "page" : undefined}>
      {children}
    </Link>
  );
}

توضیح کد

  • "use client" در بالای فایل، آن را Client Component می‌کند؛ لازمه‌ی استفاده از usePathname است.
  • usePathname را از next/navigation وارد می‌کنیم (نه next/router که مربوط به Pages Router است).
  • برای صفحه‌ی اصلی مقایسه‌ی دقیق (===) لازم است، وگرنه چون هر آدرسی با / شروع می‌شود، لینک «خانه» همیشه فعال می‌شد.
  • برای بقیه‌ی لینک‌ها، زیرمسیرها هم حساب می‌شوند: وقتی در /posts/hello هستیم، لینک /posts فعال است.
  • ویژگی aria-current="page" به صفحه‌خوان‌ها می‌گوید این لینک صفحه‌ی جاری است. فعلاً استایلی نداریم؛ بعداً می‌توان با انتخابگر [aria-current="page"] آن را ظاهراً هم متمایز کرد.

عملی: استفاده‌ی NavLink در هدر DevNotes

فایل‌های متأثر: فایل جدید components/NavLink.tsx و تغییر components/SiteHeader.tsx. خود SiteHeader همچنان Server Component می‌ماند و فقط NavLink کوچک در مرورگر اجرا می‌شود.

tsx
// components/SiteHeader.tsx
import NavLink from "@/components/NavLink";

export default function SiteHeader() {
  return (
    <header>
      <strong>DevNotes</strong>
      <nav aria-label="ناوبری اصلی">
        <ul>
          <li><NavLink href="/">خانه</NavLink></li>
          <li><NavLink href="/posts">نوشته‌ها</NavLink></li>
          <li><NavLink href="/about">درباره</NavLink></li>
          <li><NavLink href="/login">ورود</NavLink></li>
        </ul>
      </nav>
    </header>
  );
}

خروجی و رفتار مورد انتظار

  • با کلیک روی لینک‌های هدر، صفحه بدون بارگذاری کامل عوض می‌شود و هدر و فوتر از نو ساخته نمی‌شوند.
  • برای بررسی ویژگی aria-current، در مرورگر روی لینک صفحه‌ی جاری کلیک راست و گزینه‌ی «Inspect» را بزنید؛ در /posts باید aria-current="page" فقط روی لینک «نوشته‌ها» دیده شود.
  • در ابزار Network مرورگر می‌بینید که هنگام ناوبری، به‌جای دریافت کل سند HTML، درخواست‌های کوچک‌تری فرستاده می‌شود.

نکته‌های مهم

  • برای لینک داخلی همیشه Link: اگر به‌جایش <a href="/posts"> بنویسید، ناوبری سریع از بین می‌رود و ESLint در Next.js هشدار می‌دهد.
  • در Server Component فقط Hook نخواهید: usePathname و useRouter فقط در Client Component کار می‌کنند. خود Link این محدودیت را ندارد.
  • Link برای ناوبری کاربر است: اگر باید بعد از یک رویداد (مثلاً موفقیت ورود) از داخل کد به صفحه‌ی دیگر بروید، از useRouter یا redirect استفاده می‌کنیم؛ موضوع درس‌های «مسیریابی برنامه‌ای با useRouter» و «Redirect، not-found.tsx و notFound()».
  • در حالت توسعه Prefetch محدود است: رفتار کامل Prefetch فقط در نسخه‌ی Production دیده می‌شود. این را در درس «Prefetching و رفتار Navigation» بررسی می‌کنیم.

جمع‌بندی

  • Link کامپوننت next/link است که یک تگ <a> واقعی با ناوبری سریع سمت کلاینت و Prefetch می‌سازد.
  • href می‌تواند رشته (ثابت یا با Template String)، آبجکت pathname/query یا آدرس همراه Hash باشد.
  • ویژگی‌های replace، scroll و prefetch رفتار ناوبری را تنظیم می‌کنند.
  • برای لینک به بیرون از سایت تگ a معمولی کافی است.
  • لینک فعال با usePathname در یک Client Component کوچک ساخته می‌شود؛ در DevNotes این کار را با NavLink انجام دادیم.
  • در درس بعد، «مسیرهای پویا، params ناهمگام و مسیرهای تودرتو»، مسیرهایی می‌سازیم که بخشی از آدرس‌شان متغیر است، مثل صفحه‌ی هر نوشته.