كيفية التعامل مع إعادة التوجيه في Next.js
هناك عدة طرق يمكنك من خلالها التعامل مع إعادة التوجيه في Next.js. ستغطي هذه الصفحة كل خيار متاح، حالات الاستخدام، وكيفية إدارة أعداد كبيرة من عمليات إعادة التوجيه.
API | الغرض | المكان | رمز الحالة |
---|---|---|---|
redirect | إعادة توجيه المستخدم بعد تغيير أو حدث | مكونات الخادم، إجراءات الخادم، معالجات المسار | 307 (مؤقت) أو 303 (إجراء الخادم) |
permanentRedirect | إعادة توجيه المستخدم بعد تغيير أو حدث | مكونات الخادم، إجراءات الخادم، معالجات المسار | 308 (دائم) |
useRouter | تنفيذ تنقل من جانب العميل | معالجات الأحداث في مكونات العميل | غير متاح |
redirects في next.config.js | إعادة توجيه طلب وارد بناءً على مسار | ملف next.config.js | 307 (مؤقت) أو 308 (دائم) |
NextResponse.redirect | إعادة توجيه طلب وارد بناءً على شرط | Middleware | أي |
دالة redirect
تتيح لك دالة redirect
إعادة توجيه المستخدم إلى عنوان URL آخر. يمكنك استدعاء redirect
في مكونات الخادم، معالجات المسار، وإجراءات الخادم.
غالبًا ما تُستخدم redirect
بعد تغيير أو حدث. على سبيل المثال، إنشاء منشور:
'use server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function createPost(id: string) {
try {
// استدعاء قاعدة البيانات
} catch (error) {
// معالجة الأخطاء
}
revalidatePath('/posts') // تحديث المنشورات المخزنة مؤقتًا
redirect(`/post/${id}`) // الانتقال إلى صفحة المنشور الجديدة
}
'use server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function createPost(id) {
try {
// استدعاء قاعدة البيانات
} catch (error) {
// معالجة الأخطاء
}
revalidatePath('/posts') // تحديث المنشورات المخزنة مؤقتًا
redirect(`/post/${id}`) // الانتقال إلى صفحة المنشور الجديدة
}
معلومة مفيدة:
- تُرجع
redirect
رمز حالة 307 (إعادة توجيه مؤقتة) افتراضيًا. عند استخدامها في إجراء خادم، تُرجع 303 (انظر آخر)، والذي يُستخدم عادةً لإعادة التوجيه إلى صفحة نجاح نتيجة لطلب POST.- تُطلق
redirect
خطأ داخليًا لذا يجب استدعاؤها خارج كتلtry/catch
.- يمكن استدعاء
redirect
في مكونات العميل أثناء عملية التصيير ولكن ليس في معالجات الأحداث. يمكنك استخدام رباطuseRouter
بدلاً من ذلك.- تقبل
redirect
أيضًا عناوين URL المطلقة ويمكن استخدامها لإعادة التوجيه إلى روابط خارجية.- إذا كنت ترغب في إعادة التوجيه قبل عملية التصيير، استخدم
next.config.js
أو Middleware.
راجع مرجع API لـ redirect
لمزيد من المعلومات.
دالة permanentRedirect
تتيح لك دالة permanentRedirect
إعادة توجيه المستخدم بشكل دائم إلى عنوان URL آخر. يمكنك استدعاء permanentRedirect
في مكونات الخادم، معالجات المسار، وإجراءات الخادم.
غالبًا ما تُستخدم permanentRedirect
بعد تغيير أو حدث يغير عنوان URL الأساسي لكيان، مثل تحديث عنوان URL لملف تعريف المستخدم بعد تغيير اسم المستخدم:
'use server'
import { permanentRedirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function updateUsername(username: string, formData: FormData) {
try {
// استدعاء قاعدة البيانات
} catch (error) {
// معالجة الأخطاء
}
revalidateTag('username') // تحديث جميع الإشارات إلى اسم المستخدم
permanentRedirect(`/profile/${username}`) // الانتقال إلى ملف تعريف المستخدم الجديد
}
'use server'
import { permanentRedirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function updateUsername(username, formData) {
try {
// استدعاء قاعدة البيانات
} catch (error) {
// معالجة الأخطاء
}
revalidateTag('username') // تحديث جميع الإشارات إلى اسم المستخدم
permanentRedirect(`/profile/${username}`) // الانتقال إلى ملف تعريف المستخدم الجديد
}
معلومة مفيدة:
- تُرجع
permanentRedirect
رمز حالة 308 (إعادة توجيه دائمة) افتراضيًا.- تقبل
permanentRedirect
أيضًا عناوين URL المطلقة ويمكن استخدامها لإعادة التوجيه إلى روابط خارجية.- إذا كنت ترغب في إعادة التوجيه قبل عملية التصيير، استخدم
next.config.js
أو Middleware.
راجع مرجع API لـ permanentRedirect
لمزيد من المعلومات.
ربط useRouter()
إذا كنت بحاجة إلى إعادة التوجيه داخل معالج حدث في مكون عميل، يمكنك استخدام طريقة push
من ربط useRouter
. على سبيل المثال:
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
لوحة التحكم
</button>
)
}
'use client'
import { useRouter } from 'next/navigation'
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. هذا يسمح لك بالتعامل مع عمليات إعادة التوجيه برمجيًا دون الحاجة إلى إعادة نشر تطبيقك.
للقيام بذلك، ستحتاج إلى النظر في:
- إنشاء وتخزين خريطة إعادة توجيه.
- تحسين أداء البحث عن البيانات.
مثال 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، ثم التحقق مما إذا كان مسار الطلب الوارد موجودًا في مرشح بلوم.
إذا كان موجودًا، يتم تمرير الطلب إلى معالج المسار (Route Handler) والذي سيتحقق من الملف الفعلي ويعيد توجيه المستخدم إلى الرابط المناسب. هذا يتجنب استيراد ملف إعادة توجيه كبير إلى 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()
}
ثم، في معالج المسار (Route Handler):
import { NextRequest, NextResponse } from 'next/server'
import redirects from '@/app/redirects/redirects.json'
type RedirectEntry = {
destination: string
permanent: boolean
}
export function GET(request: NextRequest) {
const pathname = request.nextUrl.searchParams.get('pathname')
if (!pathname) {
return new Response('Bad Request', { status: 400 })
}
// 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 new Response('No redirect', { status: 400 })
}
// Return the redirect entry
return NextResponse.json(redirect)
}
import { NextResponse } from 'next/server'
import redirects from '@/app/redirects/redirects.json'
export function GET(request) {
const pathname = request.nextUrl.searchParams.get('pathname')
if (!pathname) {
return new Response('Bad Request', { status: 400 })
}
// Get the redirect entry from the redirects.json file
const redirect = redirects[pathname]
// Account for bloom filter false positives
if (!redirect) {
return new Response('No redirect', { status: 400 })
}
// Return the redirect entry
return NextResponse.json(redirect)
}
معلومة مفيدة:
- لإنشاء مرشح بلوم، يمكنك استخدام مكتبة مثل
bloom-filters
.- يجب عليك التحقق من صحة الطلبات المرسلة إلى معالج المسار (Route Handler) لمنع الطلبات الضارة.