رابط (Link)

<Link> هو مكون React يمتد عنصر HTML <a> لتوفير الجلب المسبق (Prefetching) والتنقل من جانب العميل بين المسارات. وهو الطريقة الأساسية للتنقل بين المسارات في Next.js.

الاستخدام الأساسي:

import Link from 'next/link'

export default function Page() {
  return <Link href="/dashboard">لوحة التحكم</Link>
}
import Link from 'next/link'

export default function Page() {
  return <Link href="/dashboard">لوحة التحكم</Link>
}

المرجع

يمكن تمرير الخصائص التالية إلى مكون <Link>:

الخاصيةمثالالنوعمطلوب
hrefhref="/dashboard"نص أو كائننعم
replacereplace={false}منطقي-
scrollscroll={false}منطقي-
prefetchprefetch={false}منطقي أو null-
onNavigateonNavigate={(e) => {}}دالة-

معلومة مفيدة: يمكن إضافة سمات <a> مثل className أو target="_blank" إلى <Link> كخصائص وسيتم تمريرها إلى عنصر <a> الأساسي.

href (مطلوب)

المسار أو URL للانتقال إليه.

import Link from 'next/link'

// الانتقال إلى /about?name=test
export default function Page() {
  return (
    <Link
      href={{
        pathname: '/about',
        query: { name: 'test' },
      }}
    >
      حول
    </Link>
  )
}
import Link from 'next/link'

// الانتقال إلى /about?name=test
export default function Page() {
  return (
    <Link
      href={{
        pathname: '/about',
        query: { name: 'test' },
      }}
    >
      حول
    </Link>
  )
}

replace

القيمة الافتراضية false. عند true، سيستبدل next/link حالة التاريخ الحالية بدلاً من إضافة URL جديد إلى تاريخ المتصفح.

import Link from 'next/link'

export default function Page() {
  return (
    <Link href="/dashboard" replace>
      لوحة التحكم
    </Link>
  )
}
import Link from 'next/link'

export default function Page() {
  return (
    <Link href="/dashboard" replace>
      لوحة التحكم
    </Link>
  )
}

scroll

القيمة الافتراضية true. السلوك الافتراضي للتمرير في <Link> في Next.js هو الحفاظ على موضع التمرير، مشابه لكيفية تعامل المتصفحات مع التنقل للخلف وللأمام. عند الانتقال إلى صفحة جديدة، سيظل موضع التمرير كما هو طالما أن الصفحة مرئية في نافذة العرض. ومع ذلك، إذا لم تكن الصفحة مرئية في نافذة العرض، فسوف يقوم Next.js بالتمرير إلى أعلى أول عنصر في الصفحة.

عند scroll = {false}، لن يحاول Next.js التمرير إلى أول عنصر في الصفحة.

معلومة مفيدة: يتحقق Next.js من scroll: false قبل إدارة سلوك التمرير. إذا كان التمرير ممكّنًا، فإنه يحدد عقدة DOM ذات الصلة بالتنقل ويتفحص كل عنصر من المستوى الأعلى. يتم تخطي جميع العناصر غير القابلة للتمرير وتلك التي لا تحتوي على HTML معروض، بما في ذلك العناصر ذات الموضع الثابت أو المثبت، والعناصر غير المرئية مثل تلك المحسوبة باستخدام getBoundingClientRect. ثم يستمر Next.js عبر العناصر الشقيقة حتى يحدد عنصرًا قابلًا للتمرير مرئيًا في نافذة العرض.

import Link from 'next/link'

export default function Page() {
  return (
    <Link href="/dashboard" scroll={false}>
      لوحة التحكم
    </Link>
  )
}
import Link from 'next/link'

export default function Page() {
  return (
    <Link href="/dashboard" scroll={false}>
      لوحة التحكم
    </Link>
  )
}

prefetch

يحدث الجلب المسبق (Prefetching) عندما يدخل مكون <Link /> نافذة عرض المستخدم (مبدئيًا أو من خلال التمرير). يقوم Next.js بجلب وتحميل المسار المرتبط (المشار إليه بواسطة href) وبياناته في الخلفية لتحسين أداء التنقل من جانب العميل. إذا انتهت صلاحية البيانات التي تم جلبها مسبقًا بحلول وقت تحويم المستخدم فوق <Link />، فسيحاول Next.js جلبها مرة أخرى. الجلب المسبق ممكّن فقط في بيئة الإنتاج.

يمكن تمرير القيم التالية إلى خاصية prefetch:

  • null (افتراضي): يعتمد سلوك الجلب المسبق على ما إذا كان المسار ثابتًا أو ديناميكيًا. بالنسبة للمسارات الثابتة، سيتم جلب المسار بالكامل (بما في ذلك جميع بياناته). بالنسبة للمسارات الديناميكية، سيتم جلب جزء المسار حتى أقرب مقطع به حد loading.js.
  • true: سيتم جلب المسار بالكامل لكل من المسارات الثابتة والديناميكية.
  • false: لن يحدث الجلب المسبق أبدًا سواء عند الدخول إلى نافذة العرض أو عند التحويم.
import Link from 'next/link'

export default function Page() {
  return (
    <Link href="/dashboard" prefetch={false}>
      لوحة التحكم
    </Link>
  )
}
import Link from 'next/link'

export default function Page() {
  return (
    <Link href="/dashboard" prefetch={false}>
      لوحة التحكم
    </Link>
  )
}

onNavigate

معالج حدث يتم استدعاؤه أثناء التنقل من جانب العميل. يتلقى المعالج كائن حدث يتضمن طريقة preventDefault()، مما يسمح لك بإلغاء التنقل إذا لزم الأمر.

import Link from 'next/link'

export default function Page() {
  return (
    <Link
      href="/dashboard"
      onNavigate={(e) => {
        // ينفذ فقط أثناء التنقل SPA
        console.log('جارٍ التنقل...')

        // إلغاء التنقل اختياريًا
        // e.preventDefault()
      }}
    >
      لوحة التحكم
    </Link>
  )
}
import Link from 'next/link'

export default function Page() {
  return (
    <Link
      href="/dashboard"
      onNavigate={(e) => {
        // ينفذ فقط أثناء التنقل SPA
        console.log('جارٍ التنقل...')

        // إلغاء التنقل اختياريًا
        // e.preventDefault()
      }}
    >
      لوحة التحكم
    </Link>
  )
}

معلومة مفيدة: بينما قد يبدو onClick و onNavigate متشابهين، إلا أنهما يخدمان أغراضًا مختلفة. ينفذ onClick لجميع أحداث النقر، بينما يعمل onNavigate فقط أثناء التنقل من جانب العميل. بعض الاختلافات الرئيسية:

  • عند استخدام مفاتيح التعديل (Ctrl/Cmd + النقر)، ينفذ onClick ولكن onNavigate لا ينفذ لأن Next.js يمنع التنقل الافتراضي لعلامات التبويب الجديدة.
  • لن تؤدي عناوين URL الخارجية إلى تشغيل onNavigate لأنها مخصصة فقط للتنقل من جانب العميل ونفس المصدر.
  • ستعمل الروابط ذات سمة download مع onClick ولكن ليس مع onNavigate لأن المتصفح سيعامل عنوان URL المرتبط كتنزيل.

أمثلة

توضح الأمثلة التالية كيفية استخدام مكون <Link> في سيناريوهات مختلفة.

الربط مع المقاطع الديناميكية

عند الربط مع المقاطع الديناميكية، يمكنك استخدام القوالب الحرفية والاستيفاء لإنشاء قائمة من الروابط. على سبيل المثال، لإنشاء قائمة من منشورات المدونة:

import Link from 'next/link'

interface Post {
  id: number
  title: string
  slug: string
}

export default function PostList({ posts }: { posts: Post[] }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}
import Link from 'next/link'

export default function PostList({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}

التحقق من الروابط النشطة

يمكنك استخدام usePathname() لتحديد ما إذا كان الرابط نشطًا. على سبيل المثال، لإضافة فئة إلى الرابط النشط، يمكنك التحقق مما إذا كان pathname الحالي يتطابق مع href الرابط:

'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function Links() {
  const pathname = usePathname()

  return (
    <nav>
      <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
        Home
      </Link>

      <Link
        className={`link ${pathname === '/about' ? 'active' : ''}`}
        href="/about"
      >
        About
      </Link>
    </nav>
  )
}
'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function Links() {
  const pathname = usePathname()

  return (
    <nav>
      <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
        Home
      </Link>

      <Link
        className={`link ${pathname === '/about' ? 'active' : ''}`}
        href="/about"
      >
        About
      </Link>
    </nav>
  )
}

التمرير إلى id

إذا كنت ترغب في التمرير إلى id معين عند التنقل، يمكنك إضافة رابط # هاش إلى عنوان URL الخاص بك أو تمرير رابط هاش إلى خاصية href. هذا ممكن لأن <Link> يتم عرضه كعنصر <a>.

<Link href="/dashboard#settings">Settings</Link>

// Output
<a href="/dashboard#settings">Settings</a>

معلومة مفيدة:

  • سيقوم Next.js بالتمرير إلى الصفحة إذا لم تكن مرئية في نافذة العرض عند التنقل.

الربط بمقاطع المسار الديناميكي

بالنسبة لمقاطع المسار الديناميكية، يمكن أن يكون استخدام القوالب الحرفية مفيدًا لإنشاء مسار الرابط.

على سبيل المثال، يمكنك إنشاء قائمة من الروابط إلى المسار الديناميكي app/blog/[slug]/page.js:

import Link from 'next/link'

export default function Page({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}
import Link from 'next/link'

export default function Page({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}

إذا كان العنصر الفرعي مكونًا مخصصًا يلف علامة <a>

إذا كان العنصر الفرعي لـ Link هو مكون مخصص يلف علامة <a>، فيجب عليك إضافة passHref إلى Link. هذا ضروري إذا كنت تستخدم مكتبات مثل styled-components. بدون ذلك، لن تحتوي علامة <a> على سمة href، مما يؤثر على إمكانية الوصول إلى موقعك وقد يؤثر على تحسين محركات البحث (SEO). إذا كنت تستخدم ESLint، فهناك قاعدة مدمجة next/link-passhref لضمان الاستخدام الصحيح لـ passHref.

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

// هذا ينشئ مكونًا مخصصًا يلف علامة <a>
const RedLink = styled.a`
  color: red;
`

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

export default NavLink
import Link from 'next/link'
import styled from 'styled-components'

// هذا ينشئ مكونًا مخصصًا يلف علامة <a>
const RedLink = styled.a`
  color: red;
`

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

export default NavLink
  • إذا كنت تستخدم ميزة JSX pragma لـ emotion (@jsx jsx)، فيجب عليك استخدام passHref حتى إذا كنت تستخدم علامة <a> مباشرة.
  • يجب أن يدعم المكون خاصية onClick لتنشيط التنقل بشكل صحيح.

تداخل مكون وظيفي

إذا كان العنصر الفرعي لـ Link هو مكون وظيفي، بالإضافة إلى استخدام passHref و legacyBehavior، يجب عليك لف المكون في React.forwardRef:

import Link from 'next/link'
import React from 'react'

// تعريف نوع الخصائص لـ MyButton
interface MyButtonProps {
  onClick?: React.MouseEventHandler<HTMLAnchorElement>
  href?: string
}

// استخدام React.ForwardRefRenderFunction لكتابة المرجع المقدم بشكل صحيح
const MyButton: React.ForwardRefRenderFunction<
  HTMLAnchorElement,
  MyButtonProps
> = ({ onClick, href }, ref) => {
  return (
    <a href={href} onClick={onClick} ref={ref}>
      Click Me
    </a>
  )
}

// استخدام React.forwardRef لف المكون
const ForwardedMyButton = React.forwardRef(MyButton)

export default function Page() {
  return (
    <Link href="/about" passHref legacyBehavior>
      <ForwardedMyButton />
    </Link>
  )
}
import Link from 'next/link'
import React from 'react'

// `onClick`، `href`، و `ref` يجب تمريرها إلى عنصر DOM
// للمعالجة الصحيحة
const MyButton = React.forwardRef(({ onClick, href }, ref) => {
  return (
    <a href={href} onClick={onClick} ref={ref}>
      Click Me
    </a>
  )
})

// إضافة اسم عرض للمكون (مفيد لتصحيح الأخطاء)
MyButton.displayName = 'MyButton'

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

استبدال URL بدلاً من الدفع

السلوك الافتراضي لمكون Link هو دفع عنوان URL جديد إلى مكدس history. يمكنك استخدام خاصية replace لمنع إضافة إدخال جديد، كما في المثال التالي:

import Link from 'next/link'

export default function Page() {
  return (
    <Link href="/about" replace>
      About us
    </Link>
  )
}
import Link from 'next/link'

export default function Page() {
  return (
    <Link href="/about" replace>
      About us
    </Link>
  )
}

تعطيل التمرير إلى أعلى الصفحة

السلوك الافتراضي للتمرير لـ <Link> في Next.js هو الحفاظ على موضع التمرير، مشابه لكيفية تعامل المتصفحات مع التنقل للخلف وللأمام. عند الانتقال إلى صفحة جديدة، سيظل موضع التمرير كما هو طالما أن الصفحة مرئية في نافذة العرض.

ومع ذلك، إذا لم تكن الصفحة مرئية في نافذة العرض، فسوف يقوم Next.js بالتمرير إلى أعلى عنصر الصفحة الأول. إذا كنت ترغب في تعطيل هذا السلوك، يمكنك تمرير scroll={false} إلى مكون <Link>، أو scroll: false إلى router.push() أو router.replace().

import Link from 'next/link'

export default function Page() {
  return (
    <Link href="/#hashid" scroll={false}>
      Disables scrolling to the top
    </Link>
  )
}
import Link from 'next/link'

export default function Page() {
  return (
    <Link href="/#hashid" scroll={false}>
      Disables scrolling to the top
    </Link>
  )
}

استخدام router.push() أو router.replace():

// useRouter
import { useRouter } from 'next/navigation'

const router = useRouter()

router.push('/dashboard', { scroll: false })

جلب الروابط مسبقًا في Middleware

من الشائع استخدام Middleware للمصادقة أو أغراض أخرى تتضمن إعادة توجيه المستخدم إلى صفحة مختلفة. لكي يعمل مكون <Link /> بشكل صحيح في جلب الروابط مسبقًا مع إعادة التوجيه عبر Middleware، تحتاج إلى إخبار Next.js بكل من عنوان URL المعروض وعنوان URL المطلوب جلبها مسبقًا. هذا مطلوب لتجنب طلبات غير ضرورية إلى middleware لمعرفة المسار الصحيح الذي يجب جلبها مسبقًا.

على سبيل المثال، إذا كنت تريد تقديم مسار /dashboard الذي يحتوي على واجهات للمستخدمين المصادق عليهم والزوار، يمكنك إضافة ما يلي في Middleware الخاص بك لإعادة توجيه المستخدم إلى الصفحة الصحيحة:

import { NextResponse } from 'next/server'

export function middleware(request: Request) {
  const nextUrl = request.nextUrl
  if (nextUrl.pathname === '/dashboard') {
    if (request.cookies.authToken) {
      return NextResponse.rewrite(new URL('/auth/dashboard', request.url))
    } else {
      return NextResponse.rewrite(new URL('/public/dashboard', request.url))
    }
  }
}
import { NextResponse } from 'next/server'

export function middleware(request) {
  const nextUrl = request.nextUrl
  if (nextUrl.pathname === '/dashboard') {
    if (request.cookies.authToken) {
      return NextResponse.rewrite(new URL('/auth/dashboard', request.url))
    } else {
      return NextResponse.rewrite(new URL('/public/dashboard', request.url))
    }
  }
}

في هذه الحالة، سترغب في استخدام الكود التالي في مكون <Link /> الخاص بك:

'use client'

import Link from 'next/link'
import useIsAuthed from './hooks/useIsAuthed' // خطاف المصادقة الخاص بك

export default function Page() {
  const isAuthed = useIsAuthed()
  const path = isAuthed ? '/auth/dashboard' : '/public/dashboard'
  return (
    <Link as="/dashboard" href={path}>
      Dashboard
    </Link>
  )
}
'use client'

import Link from 'next/link'
import useIsAuthed from './hooks/useIsAuthed' // خطاف المصادقة الخاص بك

export default function Page() {
  const isAuthed = useIsAuthed()
  const path = isAuthed ? '/auth/dashboard' : '/public/dashboard'
  return (
    <Link as="/dashboard" href={path}>
      Dashboard
    </Link>
  )
}

منع التنقل

يمكنك استخدام خاصية onNavigate لمنع التنقل عند استيفاء شروط معينة، مثل وجود تغييرات غير محفوظة في نموذج. عندما تحتاج إلى منع التنقل عبر عدة مكونات في تطبيقك (مثل منع التنقل من أي رابط أثناء تحرير نموذج)، يوفر React Context طريقة نظيفة لمشاركة حالة المنع هذه. أولاً، أنشئ سياقًا لتتبع حالة منع التنقل:

'use client'

import { createContext, useState, useContext } from 'react'

interface NavigationBlockerContextType {
  isBlocked: boolean
  setIsBlocked: (isBlocked: boolean) => void
}

export const NavigationBlockerContext =
  createContext<NavigationBlockerContextType>({
    isBlocked: false,
    setIsBlocked: () => {},
  })

export function NavigationBlockerProvider({
  children,
}: {
  children: React.ReactNode
}) {
  const [isBlocked, setIsBlocked] = useState(false)

  return (
    <NavigationBlockerContext.Provider value={{ isBlocked, setIsBlocked }}>
      {children}
    </NavigationBlockerContext.Provider>
  )
}

export function useNavigationBlocker() {
  return useContext(NavigationBlockerContext)
}
'use client'

import { createContext, useState, useContext } from 'react'

export const NavigationBlockerContext = createContext({
  isBlocked: false,
  setIsBlocked: () => {},
})

export function NavigationBlockerProvider({ children }) {
  const [isBlocked, setIsBlocked] = useState(false)

  return (
    <NavigationBlockerContext.Provider value={{ isBlocked, setIsBlocked }}>
      {children}
    </NavigationBlockerContext.Provider>
  )
}

export function useNavigationBlocker() {
  return useContext(NavigationBlockerContext)
}

أنشئ مكون نموذج يستخدم السياق:

'use client'

import { useNavigationBlocker } from '../contexts/navigation-blocker'

export default function Form() {
  const { setIsBlocked } = useNavigationBlocker()

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        setIsBlocked(false)
      }}
      onChange={() => setIsBlocked(true)}
    >
      <input type="text" name="name" />
      <button type="submit">Save</button>
    </form>
  )
}
'use client'

import { useNavigationBlocker } from '../contexts/navigation-blocker'

export default function Form() {
  const { setIsBlocked } = useNavigationBlocker()

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        setIsBlocked(false)
      }}
      onChange={() => setIsBlocked(true)}
    >
      <input type="text" name="name" />
      <button type="submit">Save</button>
    </form>
  )
}

أنشئ مكون رابط مخصص يمنع التنقل:

'use client'

import Link from 'next/link'
import { useNavigationBlocker } from '../contexts/navigation-blocker'

interface CustomLinkProps extends React.ComponentProps<typeof Link> {
  children: React.ReactNode
}

export function CustomLink({ children, ...props }: CustomLinkProps) {
  const { isBlocked } = useNavigationBlocker()

  return (
    <Link
      onNavigate={(e) => {
        if (
          isBlocked &&
          !window.confirm('You have unsaved changes. Leave anyway?')
        ) {
          e.preventDefault()
        }
      }}
      {...props}
    >
      {children}
    </Link>
  )
}
'use client'

import Link from 'next/link'
import { useNavigationBlocker } from '../contexts/navigation-blocker'

export function CustomLink({ children, ...props }) {
  const { isBlocked } = useNavigationBlocker()

  return (
    <Link
      onNavigate={(e) => {
        if (
          isBlocked &&
          !window.confirm('You have unsaved changes. Leave anyway?')
        ) {
          e.preventDefault()
        }
      }}
      {...props}
    >
      {children}
    </Link>
  )
}

أنشئ مكون تنقل:

'use client'

import { CustomLink as Link } from './custom-link'

export default function Nav() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
    </nav>
  )
}
'use client'

import { CustomLink as Link } from './custom-link'

export default function Nav() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
    </nav>
  )
}

أخيرًا، لف تطبيقك بـ NavigationBlockerProvider في التخطيط الجذري واستخدم المكونات في صفحتك:

import { NavigationBlockerProvider } from './contexts/navigation-blocker'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <NavigationBlockerProvider>{children}</NavigationBlockerProvider>
      </body>
    </html>
  )
}
import { NavigationBlockerProvider } from './contexts/navigation-blocker'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <NavigationBlockerProvider>{children}</NavigationBlockerProvider>
      </body>
    </html>
  )
}

ثم استخدم مكونات Nav و Form في صفحتك:

import Nav from './components/nav'
import Form from './components/form'

export default function Page() {
  return (
    <div>
      <Nav />
      <main>
        <h1>Welcome to the Dashboard</h1>
        <Form />
      </main>
    </div>
  )
}
import Nav from './components/nav'
import Form from './components/form'

export default function Page() {
  return (
    <div>
      <Nav />
      <main>
        <h1>Welcome to the Dashboard</h1>
        <Form />
      </main>
    </div>
  )
}

عندما يحاول المستخدم التنقل بعيدًا باستخدام CustomLink بينما يحتوي النموذج على تغييرات غير محفوظة، سيتم مطالبتهم بالتأكيد قبل المغادرة.

سجل الإصدارات

الإصدارالتغييرات
v15.3.0إضافة واجهة برمجة التطبيقات onNavigate
v13.0.0لم يعد يتطلب علامة <a> فرعية. يتم توفير أداة تحويل الكود (codemod) لتحديث قاعدة الكود الخاصة بك تلقائيًا.
v10.0.0يتم حل خاصية href التي تشير إلى مسار ديناميكي تلقائيًا ولم تعد تتطلب خاصية as.
v8.0.0تحسين أداء الجلب المسبق.
v1.0.0تم تقديم next/link.