لماذا استضافة Payload CMS ذاتياً على VPS
يعتمد Payload CMS نهجاً قائماً على الكود بشكل جذري: يُعرَّف مخطط المحتوى في TypeScript ضمن ملفات إعداد مُصنَّفة بـ Git، ومنذ الإصدار 3 يتثبت Payload مباشرة داخل تطبيق Next.js عبر App Router. هذا يعني أن VPS يتيح لك استضافة نظام إدارة المحتوى والواجهة الأمامية في عملية Node واحدة، تشترك في نفس البناء والـ runtime. تحصل على typing شامل من طرف إلى طرف، وترحيل مخطط مُدار في الكود، دون أي اعتماد على واجهة رسومية لنمذجة المحتوى. الاستضافة الذاتية هي الخيار الطبيعي هنا: صُمِّم Payload للنشر مثل أي تطبيق Next.js، ويمنحك VPS السيطرة الكاملة على قاعدة البيانات (MongoDB أو PostgreSQL)، والرفوعات، ومتغيرات البيئة، دون وسيط.
الفوائد الملموسة
- مخطط المحتوى مُعرَّف بـ TypeScript ومُصنَّف بـ Git: مراجعة كاملة للكود والتاريخ
- نظام إدارة المحتوى والواجهة الأمامية في runtime واحد: بناء واحد وعملية واحدة للنشر
- Typing شامل بين إعداد Payload وAPI والواجهة، دون توليد يدوي
- اختيار قاعدة البيانات: MongoDB أو PostgreSQL عبر المحولات الرسمية
- ترحيل المخطط مُقاد بالكود (
payload migrate)، قابل للتكرار بين البيئات - Local API: وصول مباشر للبيانات دون استدعاءات HTTP من كود Next.js الخادم
- لا تكاليف ترخيص: Payload CMS 3 مفتوح المصدر (MIT) — تدفع فقط مقابل VPS
المتطلبات الأجهزية والبرمجية
بما أن Payload يعمل على Next.js، فإن البناء مُطالِب بالموارد: خطط لـ VPS بـ 2 vCPU و4 GB RAM لبناء التطبيق وتشغيله بشكل مريح. ثبِّت Node.js 20 LTS وDocker وDocker Compose. لقاعدة البيانات، جهِّز MongoDB 7 أو PostgreSQL 16 وفق المحوِّل المختار (@payloadcms/db-mongodb أو @payloadcms/db-postgres). وجِّه نطاقك نحو IP الـ VPS. خصص 15 GB قرصياً لـ node_modules وبناء .next والرفوعات ونسخ قاعدة البيانات الاحتياطية.
خطوات النشر
اختيار محوِّل قاعدة البيانات وإعداده
في
payload.config.ts، صرِّح بالمحوِّل:mongooseAdapterلـ MongoDB أوpostgresAdapterلـ PostgreSQL، مع قراءة URL الاتصال منDATABASE_URI. هذا خيار هيكلي: PostgreSQL يستلزم ترحيلات، بينما MongoDB أكثر مرونة في المخطط.تحضير متغيرات البيئة
أنشئ ملف
.env.productionيحتوي على الحد الأدنى:PAYLOAD_SECRET=سرك-32-حرفاً-على-الأقل DATABASE_URI=postgresql://user:pass@postgres:5432/payload NEXT_PUBLIC_SERVER_URL=https://example.com NODE_ENV=production PAYLOAD_CONFIG_PATH=src/payload.config.tsPAYLOAD_SECRETيُشفِّر رموز JWT وكوكيز الجلسة — قيمة قصيرة أو متوقعة تُضعِف نظام المصادقة بأكمله. ولِّدها بـopenssl rand -hex 32. يجب أن يطابقNEXT_PUBLIC_SERVER_URLURL العام النهائي: يستخدمه Payload لبناء روابط الوسائط وعناوين البريد الإلكتروني.كتابة Docker Compose مع healthchecks
يتضمن
docker-compose.ymlالمتين healthchecks لضمان عدم بدء التطبيق إلا بعد جاهزية قاعدة البيانات:services: app: build: . ports: - "127.0.0.1:3000:3000" env_file: .env.production depends_on: postgres: condition: service_healthy volumes: - uploads:/app/public/media postgres: image: postgres:16-alpine environment: POSTGRES_USER: payload POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: payload volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U payload"] interval: 5s timeout: 5s retries: 10 volumes: pgdata: uploads:بدون healthcheck وشرط
service_healthy، يبدأ التطبيق قبل قبول PostgreSQL للاتصالات، ويفشل تجمع الاتصالات ويعيد تشغيل الحاوية في حلقة.بناء الصورة وتنفيذ الترحيلات
ابنِ ثم نفِّذ الترحيلات بهذا الترتيب:
docker compose build docker compose up -d postgres docker compose run --rm app npx payload migrate docker compose up -d appلـ PostgreSQL، يطبق
npx payload migrateملفات الترحيل المولَّدة فيsrc/migrations/. إن لم تكن موجودة بعد، ولِّدها أولاً بـnpx payload migrate:create. لـ MongoDB، يُطبَّق المخطط تلقائياً عند أول تشغيل.إعداد Nginx كـ reverse proxy
وجِّه vhost نحو
http://127.0.0.1:3000(المنفذ الافتراضي لـ Next.js). زِدclient_max_body_sizeلرفع الوسائط وأضف ترويساتX-Forwarded-Protoلكي يولِّد Payload URLs صحيحة بـ HTTPS.التأمين بـ SSL وتثبيت URL الخادم
نفِّذ
certbot --nginx -d example.com، ثم تحقق من تعريفserverURL: 'https://example.com'فيpayload.config.ts. هذا URL يتحكم في روابط الوسائط ورسائل إعادة تعيين كلمة المرور والعمل الصحيح للوحة إدارة خلف الـ proxy.
المصادقة وإدارة الوصول
يدير Payload 3 المستخدمين عبر مجموعة users المعرَّفة في payload.config.ts. فعِّل المصادقة على المجموعة بـ auth: true — هذا يضيف تلقائياً نقاط النهاية /api/users/login و/api/users/logout و/api/users/me و/api/users/refresh-token.
للأدوار، لا يفرض Payload نموذجاً: عرِّف حقل role (نوع select) على مجموعة users، ثم تحكم في الوصول عبر دوال access على مستوى كل مجموعة وكل عملية:
access: {
read: ({ req: { user } }) => user?.role === 'admin',
create: isAdmin,
update: isAdminOrSelf,
delete: isAdmin,
}للوصول البرمجي (CI والتكاملات الخارجية)، استخدم مفاتيح API: فعِّل useAPIKey: true في إعداد مصادقة المجموعة. يمكن لكل مستخدم حينئذٍ توليد مفتاح من لوحة الإدارة. مرِّره في ترويسة Authorization: users API-Key مفتاحك. مفاتيح API مُجزَّأة في قاعدة البيانات — المفتاح المفقود لا يُسترد، يُعاد توليده فقط.
خطافات المجموعة: الاستجابة لأحداث المحتوى
تتيح خطافات المجموعة في Payload 3 تنفيذ كود قبل أو بعد كل عملية CRUD. تحلّ محلّ الـ webhooks بشكل أنيق حين تعيش المنطق في نفس الـ runtime:
hooks: {
afterChange: [
async ({ doc, operation }) => {
if (operation === 'create') {
await notifySubscribers(doc)
}
},
],
beforeDelete: [
async ({ id }) => {
await cleanupMedia(id)
},
],
}الخطافات المتاحة: beforeOperation وbeforeValidate وbeforeChange وafterChange وbeforeRead وafterRead وbeforeDelete وafterDelete. خطاف afterChange مثالي لإبطال كاش CDN أو إرسال إشعار أو مزامنة مع خدمة خارجية بعد النشر.
التحديثات وترحيل المخطط
يتبع Payload 3 الإصدار الدلالي. تحديثات الـ patch (3.x.y → 3.x.z) آمنة التطبيق دون ترحيلات. قد تضيف تحديثات الـ minor (3.x → 3.y) أعمدة أو فهارس وتستلزم تشغيل payload migrate.
سير العمل الموصى به لكل تحديث:
# 1. تحديث الإصدار في package.json
npm install [email protected] @payloadcms/[email protected]
# 2. توليد ملف ترحيل إذا تغيّر المخطط
npx payload migrate:create
# 3. الاختبار محلياً على نسخة قاعدة البيانات
npx payload migrate
# 4. حفظ ملف الترحيل مع تحديث الإصدار
git add src/migrations/ package.json package-lock.json
git commit -m "chore: payload 3.88.0"
# 5. في الإنتاج: إيقاف التطبيق، تطبيق الترحيلات، إعادة التشغيل
docker compose run --rm app npx payload migrate
docker compose up -d appلا تعدِّل يدوياً الملفات المولَّدة في src/migrations/: يتحقق Payload منها بالـ hash. ملف معدَّل يُفشِل migrate برسالة Error: Migration file has been modified since it was created.
عندما يرفض `sharp` التحميل
هذا أكثر عائق شائع عند النشر على VPS، ويأخذ ثلاثة أشكال متميزة. الأكثر إرباكاً هو Unsupported CPU: prebuilt binaries for linux-x64 require v2 microarchitecture: يصدر من معالج الجهاز، لا من كودك أو تبعياتك. الثنائيات المُجمَّعة مسبقاً تستهدف مجموعة تعليمات لا تتوفر في المعالجات الأقدم — هذا معيار اختيار VPS، لا خطأ يُصلَح. الثاني Could not load the "sharp" module using the linux-x64 runtime، يشير عادةً إلى node_modules مبني على منصة مختلفة — أعِد التثبيت على الجهاز الهدف. الثالث خاص بـ Docker متعدد المراحل: sharp مُثبَّت في مرحلة البناء فقط غائب عن مرحلة التشغيل — ثبِّته صراحةً في المرحلة النهائية.
الحل: الأخطاء الشائعة في الإنتاج
نفاد ذاكرة البناء — Killed أو JavaScript heap out of memory.
يحدث أثناء npm run build حين تقل الذاكرة المتاحة عن 3 GB. Node.js محدود بـ ~1.8 GB افتراضياً. مرِّر NODE_OPTIONS=--max-old-space-size=3072 قبل البناء، أو ابنِ الصورة على جهاز أقوى وادفعها إلى registry.
PostgreSQL — Error: connect ECONNREFUSED 127.0.0.1:5432.
التطبيق يحاول الاتصال قبل جاهزية PostgreSQL، أو URL الاتصال يشير إلى localhost بدلاً من اسم خدمة Docker (postgres). تحقق من استخدام DATABASE_URI لاسم الخدمة ووجود healthcheck (انظر الخطوة 3).
لوحة الإدارة غير متاحة في الإنتاج — /admin يُعيد 404 أو حلقة إعادة توجيه.
سببان رئيسيان: serverURL غير مُعرَّف أو غير مطابق للـ URL الفعلي، أو ترويسة X-Forwarded-Proto: https غائبة من Nginx. أضف proxy_set_header X-Forwarded-Proto $scheme; للـ vhost وتحقق من تطابق serverURL مع الأصل العام دون شرطة مائلة نهائية.
CORS — Access-Control-Allow-Origin غائبة عن الـ API.
Payload يقرأ cors من payload.config.ts. في الإنتاج، اذكر الأصول المسموحة صراحةً: cors: { origins: ['https://example.com'] }. cors: { origins: [serverURL] } هو الإعداد الآمن الأدنى.
Error: Migration file has been modified since it was created.
ملف في src/migrations/ عُدِّل يدوياً. استعِد الأصلي من Git، ولِّد ملف ترحيل جديداً إذا تغيّر المخطط، وأعِد التطبيق بالترتيب.
Payload CMS مقابل Strapi: أي CMS headless تستضيفه ذاتياً؟
مرّر الجدول أفقيًا
| المعيار | Payload CMS | Strapi |
|---|---|---|
| نهج الإعداد | Code-first بـ TypeScript مُصنَّف بـ Git | GUI-first عبر Content-Type Builder |
| تكامل الواجهة الأمامية | أصيل في Next.js (runtime واحد) | منفصل، واجهة أمامية ونظام CMS منفصلان |
| قواعد البيانات المدعومة | MongoDB وPostgreSQL | PostgreSQL وMySQL وSQLite |
| Typing | TypeScript أصيل من طرف إلى طرف | أنواع مولَّدة، تكامل أقل إحكاماً |
| الوصول للبيانات من الخادم | Local API دون استدعاءات HTTP | API REST/GraphQL عبر HTTP |
| نمذجة المحتوى | في الكود، من قِبل المطورين | في الواجهة، متاح لغير المطورين |
| RAM اللازمة للبناء | عالية (بناء Next.js) | عالية (بناء React admin) |
| إدارة الترحيلات | ملفات مُصنَّفة، `payload migrate` | ترحيلات تلقائية عبر Strapi CLI |
| مفاتيح API الأصيلة | نعم، لكل مجموعة مع التجزئة | نعم، عبر رموز API |
| خطافات المجموعة | أصيلة ومكتوبة بـ TypeScript | lifecycle hooks عبر middleware |
| مثالي لـ | فرق المطورين، مشاريع Next.js المكتوبة | الفرق المختلطة، النمذجة المرئية |
استغل Local API الخاصة بـ Payload في مكونات الخادم Next.js: بدلاً من استدعاء API الخاصة بك عبر fetch، استورد getPayload واستعلم قاعدة البيانات مباشرةً (payload.find({ collection: 'posts' })). تلغي رحلة HTTP وتكسب في زمن الاستجابة والأمان. للوسائط، ثبِّت إضافة @payloadcms/storage-s3 لتخزين الرفوعات خارج الـ VPS، وأتمت نسخاً احتياطية يومية لقاعدة البيانات عبر cron. أخيراً، فعِّل سير عمل المسودة/المعاينة الأصيل في Payload لتقديم خطوة معاينة للمحررين قبل النشر.