تخطّى إلى المحتوى الرئيسي
إطلاق أعلام الميزات في Next.js باستخدام Vercel Flags SDK

إطلاق أعلام الميزات في Next.js باستخدام Vercel Flags SDK

October 8, 2026
هندسة الواجهات
6 دقيقة قراءة

أهم النقاط

يُشحن Flags SDK كحزمة `flags`، مع نقطة دخول مخصّصة لـ Next.js في `flags/next` تُصدّر دالة `flag()` تُعرّف علم ميزة واحد ككود قابل للاستدعاء ومُحدّد النوع.

تعمل دالة `decide()` الخاصة بالعلم من جانب الخادم فقط، لذا فإن استدعاء العلم داخل مكوّن خادم لا يُسرّب منطق الاستهداف إلى حزمة جافاسكريبت الخاصة بالعميل أبدًا — وهو أمر لا يستطيع متغير بيئة من نوع `NEXT_PUBLIC_*` تحقيقه.

قراءة عدة أعلام باستخدام `evaluate()` بدل استدعاءات `await` متتالية أو `Promise.all()` تُجمّع العمل في دفعة واحدة؛ خطاف `bulkDecide` الخاص بمهايئ Vercel يمنح تقليصًا بنحو 10 أضعاف في زمن التقييم عند حل مئات الأعلام في استدعاء واحد.

يُحرّك نمط `precompute` تقييم الأعلام إلى طبقة الـ Middleware ويُرمّز النتيجة داخل الرابط، ما يسمح لصفحة محمية بعلم بالبقاء مُولَّدة بشكل ثابت بدل الانتقال إلى مسار الرندر وقت الطلب.

مسار اكتشاف عند `app/.well-known/vercel/flags/route.ts` يُغذّي أداة Flags Explorer في Vercel Toolbar، فتصبح كل الأعلام وقيمها الحالية مرئية في أي نشر دون بناء لوحة تحكم مخصّصة.

ما المشكلة التي يحلّها Flags SDK لدينا؟

يسدّ Flags SDK الفجوة بين علم ميزة هو مجرد قيمة منطقية، وعلم يحتاج استهدافًا ولوحة تحكم وسلوك تخزين مؤقت لا يُضرّ بالأداء. كنا نحمي ميزات أحد عملائنا خلف `process.env.NEXT_PUBLIC_NEW_PRICING`، وهذا يعمل إلى أن يحتاج العلم قيمة مختلفة بين المعاينة والإنتاج، أو طرحًا تدريجيًا لنسبة 10% من المستخدمين، أو إيقافًا فوريًا دون إعادة نشر. متغير بيئة خام لا يُجيب على أي من هذه الاحتياجات، وبناء جدول قاعدة بيانات مع طبقة تخزين مؤقت يعني امتلاك المخطط والتخزين المؤقت ولوحة التحكم بأنفسنا.

العلم المُعرَّف بـ `flag()` من `flags/next` هو ببساطة دالة غير متزامنة واحدة مُصدَّرة لكل علم:

```ts // flags.ts import { flag } from 'flags/next'; import { vercelAdapter } from '@flags-sdk/vercel'; export const showNewPricing = flag({ key: 'show-new-pricing', description: 'Show the redesigned pricing page', adapter: vercelAdapter, }); ```

يربط `vercelAdapter` العلم بمنتج Vercel Flags، فتعيش القيمة وقواعد الاستهداف في لوحة تحكم Vercel بدل ملف إعدادات آخر خاص بالكود فقط. توجد أيضًا مهايئات لـ LaunchDarkly وStatsig وPostHog وGrowthBook وغيرها قد يدفع لها العميل مسبقًا — موضع الاستدعاء لا يتغيّر، فقط استيراد المهايئ.

كيف نقرأ علمًا داخل مكوّن خادم؟

استدعاء دالة العلم المُصدَّرة هو كل واجهة القراءة لعلم واحد — لا حاجة لإعداد عميل منفصل لكل مسار.

```tsx import { showNewPricing } from '../flags'; export default async function PricingPage() { const enabled = await showNewPricing(); return enabled ? <NewPricing /> : <OldPricing />; } ```

القيمة المنطقية المحلولة فقط هي ما يصل إلى الناتج المُولَّد — نفس ضمان السرية الذي توفّره وحدة خاصة بالخادم لمفتاح API سرّي. لهذا نجعل هذا النمط الافتراضي حتى للأعلام التي لا تحمل منطقًا حساسًا: النهج يبقى واحدًا حين يحتاج العلم لاحقًا للتحقق من شيء لا نريد ظهوره في حزمة جافاسكريبت، كقائمة مستخدمين داخلية.

كيف نستهدف مستخدمًا محددًا بدل قيمة عامة واحدة؟

نضيف دالة `identify` تقرأ الطلب وتُعيد الكيان الذي ستُقيَّم `decide()` بناءً عليه، ملفوفة بـ `dedupe` كي تعمل مرة واحدة لكل طلب حتى لو شاركتها عدة أعلام.

```ts import { dedupe, flag } from 'flags/next'; import type { ReadonlyRequestCookies } from 'flags'; interface Entities { user?: { id: string }; } const identify = dedupe( ({ cookies }: { cookies: ReadonlyRequestCookies }): Entities => { const userId = cookies.get('user-id')?.value; return { user: userId ? { id: userId } : undefined }; }, ); export const newDashboard = flag<boolean, Entities>({ key: 'new-dashboard', identify, decide({ entities }) { if (!entities?.user) return false; return ['user_42', 'user_107'].includes(entities.user.id); }, }); ```

مع `vercelAdapter`، أسماء السمات في الكائن الذي تُعيده `identify` — مثل `user.id` هنا — هي بالضبط ما تُطابقه قواعد الاستهداف في لوحة التحكم. أخطئ في اسم سمة في مكان واحد، وسيتوقف الاستهداف عن مطابقة أي أحد بصمت، دون أي خطأ.

كيف نقرأ عدة أعلام دون تسلسل زمن الاستجابة؟

نستخدم `evaluate()` من `flags/next` متى احتاجت صفحة لأكثر من قيمة علم، بدل استدعاءين متتاليين لـ `await` أو `Promise.all()`. الاستدعاءات المتتالية تجعل إجمالي زمن الاستجابة مجموع تقييم كل علم؛ `Promise.all()` يُشغّلها بالتوازي لكنه يُقيّم كل علم بمعزل عن الآخر. تقرأ `evaluate()` الترويسات وملفات تعريف الارتباط والتجاوزات مرة واحدة للدفعة كاملة، وتسمح لمهايئ بحل المجموعة في استدعاء واحد — وهذا مصدر التقليص بنحو 10 أضعاف في زمن التقييم لمئات الأعلام عبر خطاف `bulkDecide` الخاص بمهايئ Vercel.

كيف نُبقي صفحة محمية بعلم مُولَّدة بشكل ثابت؟

علم يُقيَّم وقت الطلب يُخرج صفحته من مسار الرندر الثابت الكامل، لأن الاستجابة أصبحت تعتمد على شيء معروف فقط لكل طلب. نمط `precompute` يتجنّب ذلك: تُقيّم الـ Middleware مجموعة مُعرَّفة من الأعلام، وتستدعي `precompute(flagGroup)` للحصول على سلسلة رمز قصيرة، ثم تُعيد كتابة الطلب إلى رابط يُرمّز ذلك الرمز. بعدها تقرأ الصفحة قيم أعلامها من الرمز عبر `await myFlag(code, flagGroup)` بدل إعادة تشغيل `decide()`، فيمكن توليدها مسبقًا لكل قيمة رمز وتقديمها من الشبكة كأي مسار ثابت آخر.

Flags SDK مقابل متغير بيئة مقابل جدول مبني يدويًا — أيّهما يناسب مشروعًا معينًا؟

متغير البيئة لا يمنح أي استهداف، ويبقى مرئيًا في حزمة العميل إن كان عامًا، ويُبقي الصفحات ثابتة — مناسب لمفتاح عام واحد فقط ولا شيء آخر.

جدول قاعدة بيانات مبني يدويًا مع تخزين مؤقت يمنح أي استهداف تبنيه، ويبقى من جانب الخادم إن قُرئ بشكل صحيح، لكنه يُبقي الصفحات ثابتة فقط إن بنيت طبقة التخزين المؤقت بنفسك — وتمتلك المخطط والتخزين المؤقت ولوحة التحكم.

Flags SDK مع `vercelAdapter` يمنح استهدافًا مدمجًا عبر `identify` وقواعد لوحة التحكم، ويبقى من جانب الخادم، ويُبقي الصفحات ثابتة عبر `precompute`، وتديره منصة Vercel Flags مع بقائه قابلًا للتبديل لمزوّد آخر عبر المهايئ.

Flags SDK مع مهايئ خارجي (LaunchDarkly وStatsig وغيرهما) يمنح أي استهداف يدعمه ذلك المزوّد، ويبقى من جانب الخادم، ويُبقي الصفحات ثابتة عبر `precompute`، وهو الخيار المناسب حين يكون العميل يدفع بالفعل لذلك المزوّد — الحزمة هنا هي فقط الربط مع Next.js.

الأسئلة الشائعة

**س: هل يستبدل Flags SDK متغيرات البيئة من نوع `NEXT_PUBLIC_*` لأعلام الميزات؟**

لأي شيء يتجاوز مفتاح تشغيل/إيقاف عام واحد، نعم. متغير `NEXT_PUBLIC_*` مرئي في حزمة العميل ولا يستطيع التعبير عن استهداف لكل مستخدم أو طرح تدريجي؛ علم Flags SDK يبقى حصرًا من جانب الخادم ويدعم كليهما.

**س: هل علينا استخدام Vercel Flags، أم يمكن لعميل الإبقاء على مزوّد الأعلام الحالي لديه؟**

تُشحن الحزمة مهايئات لـ LaunchDarkly وStatsig وPostHog وGrowthBook وFlagsmith وOptimizely وOpenFeature، إضافة إلى واجهة موثّقة لمهايئ مخصّص. `vercelAdapter` خيار أول طرف، وليس شرطًا.

**س: لماذا تُحوّل إضافة علم صفحة ثابتة إلى صفحة مُولَّدة ديناميكيًا؟**

استدعاء `decide()` وقت الطلب يعني أن الاستجابة تعتمد على شيء معروف فقط لكل طلب، ما يُلغي أهلية الرندر الثابت الكامل. نمط `precompute` يتجنّب ذلك بحل الأعلام في الـ Middleware قبل توليد الصفحة.

**س: ما الذي يحميه `FLAGS_SECRET`؟**

هو مفتاح من 32 بايت عشوائيًا، مُرمَّز بـ base64، يُستخدم لتشفير قيم الأعلام وتعريفاتها المُرسلة إلى Flags Explorer، ولتشفير رموز `precompute`، كي لا تظهر تفاصيل الأعلام كنص صريح في رابط أو وسم سكريبت.

**س: هل `evaluate()` مفيدة فقط عند نطاق كبير؟**

لا — هي صحيحة لأي صفحة تقرأ علمين أو أكثر، لأنها توفّر إعادة قراءة ملفات تعريف الارتباط والترويسات لكل علم حتى مع عدد قليل من الأعلام. الرقم البالغ نحو 10 أضعاف لـ `bulkDecide` يخص تحديدًا المهايئات التي تحل مئات الأعلام في استدعاء بعيد واحد.