إطار Next.js

بيئة التشغيل على الحافة ووظائف الحافة

18 دقيقة الدرس 27 من 40

بيئة التشغيل على الحافة ووظائف الحافة

تجلب الحوسبة على الحافة كودك بالقرب من المستخدمين من خلال تشغيله على خوادم موزعة في جميع أنحاء العالم. توفر Next.js بيئة التشغيل على الحافة لتنفيذ الكود على الحافة، مما يوفر زمن استجابة أقل وأداءً محسنًا مقارنة بالتنفيذ التقليدي من جانب الخادم.

فهم بيئة التشغيل على الحافة

بيئة التشغيل على الحافة هي بيئة تشغيل JavaScript خفيفة الوزن تعتمد على معايير الويب، مصممة للتشغيل على شبكات الحافة مثل Vercel Edge Network أو Cloudflare Workers أو Deno Deploy.

الحافة مقابل بيئة تشغيل Node.js: تستخدم بيئة التشغيل على الحافة عزلات V8 (وليس عمليات Node.js)، وتبدأ على الفور، ولديها واجهات برمجة تطبيقات محدودة، ولكنها توفر توزيعًا عالميًا وأوقات استجابة بالمللي ثانية.

الاختلافات الرئيسية بين بيئة تشغيل الحافة و Node.js

/* بيئة تشغيل Node.js (افتراضي) */ - الوصول الكامل إلى API في Node.js (fs، path، crypto، إلخ) - أوقات بدء باردة أطول (~100-500ms) - نشر إقليمي (مركز بيانات واحد) - لا حدود للذاكرة/CPU (يعتمد على الاستضافة) - دعم كامل لحزم npm /* بيئة التشغيل على الحافة */ - واجهات برمجة تطبيقات معايير الويب فقط (fetch، Response، Headers، إلخ) - بدء بارد شبه فوري (~0-10ms) - نشر عالمي (أكثر من 150 موقعًا) - حد الذاكرة: 1-4 ميجابايت لكل طلب - دعم محدود لحزم npm

تمكين بيئة التشغيل على الحافة

يمكنك تكوين بيئة التشغيل على الحافة على مستوى المسار:

مسارات الحافة في App Router

// app/api/edge/route.ts export const runtime = 'edge'; // تمكين بيئة التشغيل على الحافة export async function GET(request: Request) { return new Response('مرحبًا من الحافة!', { status: 200, headers: { 'Content-Type': 'text/plain', }, }); }

مسارات الصفحات مع بيئة التشغيل على الحافة

// app/dashboard/page.tsx export const runtime = 'edge'; export default function Dashboard() { return <h1>صفحة لوحة التحكم (تم العرض على الحافة)</h1>; }

مسارات API للحافة في Pages Router

// pages/api/edge-function.ts export const config = { runtime: 'edge', }; export default async function handler(req: Request) { return new Response(JSON.stringify({ message: 'وظيفة الحافة' }), { status: 200, headers: { 'Content-Type': 'application/json', }, }); }

مسارات API للحافة

قم بإنشاء نقاط نهاية API عالية الأداء تعمل عالميًا:

مسار API أساسي للحافة

// app/api/geolocation/route.ts export const runtime = 'edge'; export async function GET(request: Request) { // الوصول إلى الموقع الجغرافي للطلب (Vercel Edge) const geo = request.headers.get('x-vercel-ip-country'); const city = request.headers.get('x-vercel-ip-city'); return Response.json({ country: geo, city: city, timestamp: new Date().toISOString(), }); }

Edge API مع معاملات الاستعلام

// app/api/weather/route.ts export const runtime = 'edge'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const city = searchParams.get('city') || 'London'; // الجلب من API خارجي const response = await fetch( `https://api.weather.com/v1/current?city=${city}`, { headers: { 'Authorization': `Bearer ${process.env.WEATHER_API_KEY}`, }, } ); const data = await response.json(); return Response.json(data, { headers: { 'Cache-Control': 's-maxage=300, stale-while-revalidate', }, }); }

Edge API مع طلب POST

// app/api/transform/route.ts export const runtime = 'edge'; export async function POST(request: Request) { try { const body = await request.json(); const { text } = body; // تحويل النص (مثال: أحرف كبيرة) const transformed = text.toUpperCase(); return Response.json({ original: text, transformed, }); } catch (error) { return Response.json( { error: 'نص الطلب غير صالح' }, { status: 400 } ); } }

وسيطة الحافة

تعمل الوسيطة قبل كل طلب، مثالية للمصادقة وإعادة التوجيه ومعالجة الطلبات:

إعداد الوسيطة الأساسي

// middleware.ts (جذر المشروع) import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // تسجيل جميع الطلبات console.log('الطلب:', request.url); return NextResponse.next(); } // تحديد المسارات التي تعمل فيها الوسيطة export const config = { matcher: '/api/:path*', };

وسيطة المصادقة

// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const token = request.cookies.get('auth-token')?.value; // حماية مسارات /dashboard if (request.nextUrl.pathname.startsWith('/dashboard')) { if (!token) { return NextResponse.redirect(new URL('/login', request.url)); } } return NextResponse.next(); } export const config = { matcher: ['/dashboard/:path*', '/api/protected/:path*'], };

إعادة التوجيه بناءً على الموقع الجغرافي

// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const country = request.geo?.country || 'US'; const { pathname } = request.nextUrl; // إعادة التوجيه بناءً على البلد if (pathname === '/') { if (country === 'GB') { return NextResponse.redirect(new URL('/en-gb', request.url)); } else if (country === 'FR') { return NextResponse.redirect(new URL('/fr', request.url)); } } return NextResponse.next(); }

إضافة رؤوس مخصصة

// middleware.ts import { NextResponse } from 'next/server'; export function middleware(request: NextRequest) { const response = NextResponse.next(); // إضافة رؤوس مخصصة response.headers.set('x-custom-header', 'my-value'); response.headers.set('x-request-id', crypto.randomUUID()); return response; } export const config = { matcher: '/:path*', };

وسيطة اختبار A/B

// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // الحصول على أو تعيين ملف تعريف الارتباط المتغير let variant = request.cookies.get('variant')?.value; if (!variant) { // تعيين عشوائي للمتغير A أو B variant = Math.random() > 0.5 ? 'A' : 'B'; } const response = NextResponse.next(); // تعيين ملف تعريف الارتباط المتغير response.cookies.set('variant', variant, { maxAge: 60 * 60 * 24 * 30, // 30 يومًا }); // إضافة رأس للوصول من جانب العميل response.headers.set('x-variant', variant); return response; } export const config = { matcher: '/', };

قيود بيئة التشغيل على الحافة

فهم ما هو غير متاح في بيئة التشغيل على الحافة:

غير متاح في بيئة التشغيل على الحافة: واجهات برمجة تطبيقات Node.js (fs، path، process)، والوحدات الأصلية، وتقييم الكود الديناميكي (eval)، وبعض حزم npm، والحسابات طويلة المدى (مهلة أكثر من 30 ثانية)، واستخدام الذاكرة الكبيرة (أكثر من 4 ميجابايت).

واجهات برمجة تطبيقات الويب المتوافقة

// متاح في بيئة التشغيل على الحافة - fetch, Request, Response, Headers - URL, URLSearchParams - TextEncoder, TextDecoder - atob, btoa - crypto (Web Crypto API) - console - setTimeout, setInterval // مثال: استخدام Web Crypto API const hash = await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode('hello') ); const hashArray = Array.from(new Uint8Array(hash)); const hashHex = hashArray .map(b => b.toString(16).padStart(2, '0')) .join('');

التحقق من توافق الحزمة

// بعض الحزم تعمل، والبعض الآخر لا import jwt from 'jsonwebtoken'; // ❌ غير متوافق (يستخدم Node.js crypto) import { jwtVerify } from 'jose'; // ✅ متوافق (يعتمد على Web Crypto) // اختبر الحزم دائمًا قبل النشر export const runtime = 'edge'; export async function GET() { try { // هذا سيعمل const token = await new jose.SignJWT({ foo: 'bar' }) .setProtectedHeader({ alg: 'HS256' }) .sign(new TextEncoder().encode('secret')); return Response.json({ token }); } catch (error) { return Response.json({ error: error.message }, { status: 500 }); } }

وظائف الحافة لمعالجة الصور

حوّل الصور بشكل مباشر على الحافة:

// app/api/image/route.ts export const runtime = 'edge'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const imageUrl = searchParams.get('url'); const width = searchParams.get('w'); const quality = searchParams.get('q') || '75'; if (!imageUrl) { return new Response('عنوان URL للصورة مفقود', { status: 400 }); } // جلب الصورة الأصلية const response = await fetch(imageUrl); const imageBuffer = await response.arrayBuffer(); // استخدم مكتبة أو خدمة صور متوافقة مع الحافة const transformedImage = await transformImage(imageBuffer, { width: width ? parseInt(width) : undefined, quality: parseInt(quality), }); return new Response(transformedImage, { headers: { 'Content-Type': 'image/webp', 'Cache-Control': 'public, max-age=31536000, immutable', }, }); }

وظائف الحافة لصور OG الديناميكية

قم بإنشاء صور Open Graph ديناميكيًا:

// app/api/og/route.tsx import { ImageResponse } from 'next/og'; export const runtime = 'edge'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const title = searchParams.get('title') || 'العنوان الافتراضي'; return new ImageResponse( ( <div style={{ fontSize: 60, background: 'linear-gradient(to bottom, #4f46e5, #7c3aed)', width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontWeight: 'bold', }} > {title} </div> ), { width: 1200, height: 630, } ); } // استخدام في البيانات الوصفية // <meta property="og:image" content="/api/og?title=My Page" />

مراقبة الأداء على الحافة

راقب أداء وظائف الحافة:

// app/api/monitored/route.ts export const runtime = 'edge'; export async function GET(request: Request) { const start = Date.now(); try { // منطق وظيفة الحافة الخاصة بك const data = await fetchSomeData(); const duration = Date.now() - start; return Response.json({ data, performance: { duration: `${duration}ms`, region: process.env.VERCEL_REGION || 'unknown', }, }); } catch (error) { const duration = Date.now() - start; // التسجيل في خدمة المراقبة console.error('خطأ في وظيفة الحافة:', { error: error.message, duration, url: request.url, }); return Response.json( { error: 'خطأ داخلي في الخادم' }, { status: 500 } ); } }

أفضل ممارسات بيئة التشغيل على الحافة

إرشادات بيئة التشغيل على الحافة:
  • استخدم الحافة للعمليات الحساسة لزمن الاستجابة (المصادقة، إعادة التوجيه، الموقع الجغرافي)
  • اجعل وظائف الحافة صغيرة ومركزة (أقل من 1 ميجابايت)
  • تجنب الحسابات الثقيلة أو معالجة البيانات الكبيرة
  • استخدم بيئة تشغيل Node.js للعمليات المعقدة أو الوصول إلى نظام الملفات أو المهام كثيفة الاستخدام لوحدة المعالجة المركزية
  • اختبر الحزم بدقة من أجل التوافق مع الحافة
  • قم بالتخزين المؤقت بشكل مكثف مع الرؤوس المناسبة
  • راقب أوقات البدء الباردة ومدة التنفيذ

تصحيح أخطاء وظائف الحافة

// app/api/debug/route.ts export const runtime = 'edge'; export async function GET(request: Request) { return Response.json({ // معلومات الطلب url: request.url, method: request.method, headers: Object.fromEntries(request.headers), // معلومات الحافة runtime: 'edge', region: process.env.VERCEL_REGION, // معلومات جغرافية (Vercel) geo: { country: request.headers.get('x-vercel-ip-country'), region: request.headers.get('x-vercel-ip-country-region'), city: request.headers.get('x-vercel-ip-city'), }, }); }

تمرين: إنشاء وظائف الحافة

  1. قم بإنشاء مسار API للحافة يعيد معلومات الموقع الجغرافي للمستخدم
  2. قم بتنفيذ وسيطة المصادقة التي تحمي مسارات /dashboard
  3. قم ببناء وسيطة اختبار A/B مع تتبع المتغيرات المستندة إلى ملفات تعريف الارتباط
  4. قم بإنشاء وظيفة حافة تقوم بتحويل الصور (تغيير الحجم، تحويل التنسيق)
  5. قم بإنشاء صور Open Graph ديناميكية باستخدام next/og
  6. قم بتنفيذ وسيطة تحديد المعدل على الحافة
  7. قم بإنشاء وسيطة إعادة توجيه بناءً على وكيل المستخدم (الجوال/سطح المكتب)
  8. راقب وسجل مقاييس أداء وظائف الحافة