كيفية التعامل مع إعادة التوجيه في Next.js

هناك عدة طرق يمكنك من خلالها التعامل مع إعادة التوجيه في Next.js. ستغطي هذه الصفحة كل خيار متاح، حالات الاستخدام، وكيفية إدارة أعداد كبيرة من عمليات إعادة التوجيه.

APIالغرضالمكانرمز الحالة
useRouterتنفيذ تنقل من جانب العميلالمكوناتغير متاح
redirects في next.config.jsإعادة توجيه طلب وارد بناءً على مسارملف next.config.js307 (مؤقت) أو 308 (دائم)
NextResponse.redirectإعادة توجيه طلب وارد بناءً على شرطMiddlewareأي

ربط useRouter()

إذا كنت بحاجة إلى إعادة التوجيه داخل مكون، يمكنك استخدام طريقة push من ربط useRouter. على سبيل المثال:

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.push('/dashboard')}>
      لوحة التحكم
    </button>
  )
}
import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.push('/dashboard')}>
      لوحة التحكم
    </button>
  )
}

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

  • إذا لم تكن بحاجة إلى التنقل برمجيًا للمستخدم، يجب استخدام مكون <Link>.

راجع مرجع API لـ useRouter لمزيد من المعلومات.

redirects في next.config.js

يتيح لك خيار redirects في ملف next.config.js إعادة توجيه مسار طلب وارد إلى مسار وجهة مختلف. يكون هذا مفيدًا عند تغيير بنية URL للصفحات أو عند وجود قائمة بعمليات إعادة التوجيه معروفة مسبقًا.

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

لاستخدام redirects، أضف الخيار إلى ملف next.config.js الخاص بك:

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  async redirects() {
    return [
      // إعادة توجيه أساسية
      {
        source: '/about',
        destination: '/',
        permanent: true,
      },
      // مطابقة مسار باستخدام حرف البدل
      {
        source: '/blog/:slug',
        destination: '/news/:slug',
        permanent: true,
      },
    ]
  },
}

export default nextConfig
module.exports = {
  async redirects() {
    return [
      // إعادة توجيه أساسية
      {
        source: '/about',
        destination: '/',
        permanent: true,
      },
      // مطابقة مسار باستخدام حرف البدل
      {
        source: '/blog/:slug',
        destination: '/news/:slug',
        permanent: true,
      },
    ]
  },
}

راجع مرجع API لـ redirects لمزيد من المعلومات.

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

  • يمكن أن يُرجع redirects رمز حالة 307 (إعادة توجيه مؤقتة) أو 308 (إعادة توجيه دائمة) مع خيار permanent.
  • قد يكون لـ redirects حد على بعض المنصات. على سبيل المثال، في Vercel، هناك حد لـ 1,024 إعادة توجيه. لإدارة عدد كبير من عمليات إعادة التوجيه (1000+)، فكر في إنشاء حل مخصص باستخدام Middleware. راجع إدارة عمليات إعادة التوجيه على نطاق واسع للمزيد.
  • يعمل redirects قبل Middleware.

NextResponse.redirect في Middleware

يسمح لك Middleware بتشغيل الكود قبل اكتمال الطلب. ثم، بناءً على الطلب الوارد، يمكنك إعادة توجيه إلى عنوان URL مختلف باستخدام NextResponse.redirect. يكون هذا مفيدًا إذا كنت ترغب في إعادة توجيه المستخدمين بناءً على شرط (مثل المصادقة، إدارة الجلسة، إلخ) أو لديك عدد كبير من عمليات إعادة التوجيه.

على سبيل المثال، لإعادة توجيه المستخدم إلى صفحة /login إذا لم يكن مصادقًا عليه:

import { NextResponse, NextRequest } from 'next/server'
import { authenticate } from 'auth-provider'

export function middleware(request: NextRequest) {
  const isAuthenticated = authenticate(request)

  // إذا كان المستخدم مصادقًا عليه، تابع بشكل طبيعي
  if (isAuthenticated) {
    return NextResponse.next()
  }

  // إعادة توجيه إلى صفحة تسجيل الدخول إذا لم يكن مصادقًا عليه
  return NextResponse.redirect(new URL('/login', request.url))
}

export const config = {
  matcher: '/dashboard/:path*',
}
import { NextResponse } from 'next/server'
import { authenticate } from 'auth-provider'

export function middleware(request) {
  const isAuthenticated = authenticate(request)

  // إذا كان المستخدم مصادقًا عليه، تابع بشكل طبيعي
  if (isAuthenticated) {
    return NextResponse.next()
  }

  // إعادة توجيه إلى صفحة تسجيل الدخول إذا لم يكن مصادقًا عليه
  return NextResponse.redirect(new URL('/login', request.url))
}

export const config = {
  matcher: '/dashboard/:path*',
}

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

  • يعمل Middleware بعد redirects في next.config.js وقبل التصيير.

راجع وثائق Middleware لمزيد من المعلومات.

إدارة عمليات إعادة التوجيه على نطاق واسع (متقدم)

لإدارة عدد كبير من عمليات إعادة التوجيه (1000+)، يمكنك التفكير في إنشاء حل مخصص باستخدام Middleware. هذا يسمح لك بالتعامل مع عمليات إعادة التوجيه برمجيًا دون الحاجة إلى إعادة نشر تطبيقك.

للقيام بذلك، ستحتاج إلى النظر في:

  1. إنشاء وتخزين خريطة إعادة توجيه.
  2. تحسين أداء البحث عن البيانات.

مثال Next.js: راجع مثالنا Middleware مع مرشح Bloom لتنفيذ التوصيات أدناه.

1. إنشاء وتخزين خريطة إعادة توجيه

خريطة إعادة التوجيه هي قائمة بعمليات إعادة التوجيه التي يمكنك تخزينها في قاعدة بيانات (عادةً مخزن مفتاح-قيمة) أو ملف JSON.

ضع في الاعتبار بنية البيانات التالية:

{
  "/old": {
    "destination": "/new",
    "permanent": true
  },
  "/blog/post-old": {
    "destination": "/blog/post-new",
    "permanent": true
  }
}

في Middleware، يمكنك القراءة من قاعدة بيانات مثل Edge Config من Vercel أو Redis، وإعادة توجيه المستخدم بناءً على الطلب الوارد:

import { NextResponse, NextRequest } from 'next/server'
import { get } from '@vercel/edge-config'

type RedirectEntry = {
  destination: string
  permanent: boolean
}

export async function middleware(request: NextRequest) {
  const pathname = request.nextUrl.pathname
  const redirectData = await get(pathname)

  if (redirectData && typeof redirectData === 'string') {
    const redirectEntry: RedirectEntry = JSON.parse(redirectData)
    const statusCode = redirectEntry.permanent ? 308 : 307
    return NextResponse.redirect(redirectEntry.destination, statusCode)
  }

  // لم يتم العثور على إعادة توجيه، تابع بدون إعادة توجيه
  return NextResponse.next()
}
import { NextResponse } from 'next/server'
import { get } from '@vercel/edge-config'

export async function middleware(request) {
  const pathname = request.nextUrl.pathname
  const redirectData = await get(pathname)

  if (redirectData) {
    const redirectEntry = JSON.parse(redirectData)
    const statusCode = redirectEntry.permanent ? 308 : 307
    return NextResponse.redirect(redirectEntry.destination, statusCode)
  }

  // لم يتم العثور على إعادة توجيه، تابع بدون إعادة توجيه
  return NextResponse.next()
}

2. تحسين أداء البحث عن البيانات

قراءة مجموعة بيانات كبيرة لكل طلب وارد يمكن أن تكون بطيئة ومكلفة. هناك طريقتان لتحسين أداء البحث عن البيانات:

  • استخدام قاعدة بيانات مُحسّنة للقراءة السريعة
  • استخدام استراتيجية بحث عن البيانات مثل مرشح بلوم (Bloom filter) للتحقق بكفاءة مما إذا كان هناك إعادة توجيه موجودة قبل قراءة ملف أو قاعدة بيانات إعادة التوجيه الأكبر.

بالنظر إلى المثال السابق، يمكنك استيراد ملف مرشح بلوم المُنشأ إلى Middleware، ثم التحقق مما إذا كان مسار الطلب الوارد موجودًا في مرشح بلوم.

إذا كان موجودًا، يتم تمرير الطلب إلى مسارات API والذي سيتحقق من الملف الفعلي ويعيد توجيه المستخدم إلى الرابط المناسب. هذا يتجنب استيراد ملف إعادة توجيه كبير إلى Middleware، مما يمكن أن يُبطئ كل طلب وارد.

import { NextResponse, NextRequest } from 'next/server'
import { ScalableBloomFilter } from 'bloom-filters'
import GeneratedBloomFilter from './redirects/bloom-filter.json'

type RedirectEntry = {
  destination: string
  permanent: boolean
}

// Initialize bloom filter from a generated JSON file
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter as any)

export async function middleware(request: NextRequest) {
  // Get the path for the incoming request
  const pathname = request.nextUrl.pathname

  // Check if the path is in the bloom filter
  if (bloomFilter.has(pathname)) {
    // Forward the pathname to the Route Handler
    const api = new URL(
      `/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
      request.nextUrl.origin
    )

    try {
      // Fetch redirect data from the Route Handler
      const redirectData = await fetch(api)

      if (redirectData.ok) {
        const redirectEntry: RedirectEntry | undefined =
          await redirectData.json()

        if (redirectEntry) {
          // Determine the status code
          const statusCode = redirectEntry.permanent ? 308 : 307

          // Redirect to the destination
          return NextResponse.redirect(redirectEntry.destination, statusCode)
        }
      }
    } catch (error) {
      console.error(error)
    }
  }

  // No redirect found, continue the request without redirecting
  return NextResponse.next()
}
import { NextResponse } from 'next/server'
import { ScalableBloomFilter } from 'bloom-filters'
import GeneratedBloomFilter from './redirects/bloom-filter.json'

// Initialize bloom filter from a generated JSON file
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter)

export async function middleware(request) {
  // Get the path for the incoming request
  const pathname = request.nextUrl.pathname

  // Check if the path is in the bloom filter
  if (bloomFilter.has(pathname)) {
    // Forward the pathname to the Route Handler
    const api = new URL(
      `/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
      request.nextUrl.origin
    )

    try {
      // Fetch redirect data from the Route Handler
      const redirectData = await fetch(api)

      if (redirectData.ok) {
        const redirectEntry = await redirectData.json()

        if (redirectEntry) {
          // Determine the status code
          const statusCode = redirectEntry.permanent ? 308 : 307

          // Redirect to the destination
          return NextResponse.redirect(redirectEntry.destination, statusCode)
        }
      }
    } catch (error) {
      console.error(error)
    }
  }

  // No redirect found, continue the request without redirecting
  return NextResponse.next()
}

ثم، في مسار API:

import type { NextApiRequest, NextApiResponse } from 'next'
import redirects from '@/app/redirects/redirects.json'

type RedirectEntry = {
  destination: string
  permanent: boolean
}

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const pathname = req.query.pathname
  if (!pathname) {
    return res.status(400).json({ message: 'Bad Request' })
  }

  // Get the redirect entry from the redirects.json file
  const redirect = (redirects as Record<string, RedirectEntry>)[pathname]

  // Account for bloom filter false positives
  if (!redirect) {
    return res.status(400).json({ message: 'No redirect' })
  }

  // Return the redirect entry
  return res.json(redirect)
}
import redirects from '@/app/redirects/redirects.json'

export default function handler(req, res) {
  const pathname = req.query.pathname
  if (!pathname) {
    return res.status(400).json({ message: 'Bad Request' })
  }

  // Get the redirect entry from the redirects.json file
  const redirect = redirects[pathname]

  // Account for bloom filter false positives
  if (!redirect) {
    return res.status(400).json({ message: 'No redirect' })
  }

  // Return the redirect entry
  return res.json(redirect)
}

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

  • لإنشاء مرشح بلوم، يمكنك استخدام مكتبة مثل bloom-filters.
  • يجب عليك التحقق من صحة الطلبات المرسلة إلى معالج المسار (Route Handler) لمنع الطلبات الضارة.