بناء خط أنابيب CI/CD متكامل باستخدام GitHub Actions لتطبيقات Next.js

في كل مرة ترفع كودك وتدعو الله إنه ما يخرّب الإنتاج... أنت تقامر. خطوط CI/CD تنهي هالمقامرة.
في هذا الدرس، ستبني خط أنابيب GitHub Actions كامل لتطبيق Next.js. في النهاية، كل عملية push ستفحص كودك تلقائياً، تشغّل اختبارات الوحدات، تنفّذ اختبارات E2E مع Playwright، وتنشر على Vercel — بس إذا كل شي نجح.
لا QA يدوي. لا "يشتغل عندي." ثقة مع كل commit.
ماذا ستبني
خط أنابيب CI/CD متعدد المراحل:
- فحص الكود بـ ESLint مع كل push
- فحص الأنواع بمترجم TypeScript
- اختبارات الوحدات بـ Vitest
- اختبارات E2E بـ Playwright
- نشر على Vercel فقط عند نجاح كل الفحوصات
- تخزين مؤقت للتبعيات لتسريع البناء
- تحديثات حالة كتعليقات على طلبات الدمج
المتطلبات
قبل البدء، تأكد من وجود:
- مشروع Next.js 14+ (يُفضّل App Router)
- مستودع GitHub لمشروعك
- حساب Vercel مرتبط بالمشروع
- Node.js 20+ مثبّت محلياً
- معرفة أساسية بصيغة YAML
💡 ليس عندك مشروع Next.js؟ شغّل
npx create-next-app@latest my-app --typescript --tailwind --eslint --appلإنشاء واحد.
الخطوة 1: إعداد هيكل المشروع
أولاً، نتأكد من وجود أدوات الاختبار الصحيحة.
# تثبيت تبعيات الاختبار
npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/jest-dom
# تثبيت Playwright لاختبارات E2E
npm install -D @playwright/test
npx playwright install --with-deps chromiumأنشئ ملف إعداد Vitest:
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
setupFiles: ['./tests/setup.ts'],
include: ['**/*.test.{ts,tsx}'],
coverage: {
reporter: ['text', 'json-summary', 'html'],
exclude: ['node_modules/', '.next/', 'tests/e2e/'],
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './'),
},
},
})أنشئ ملف إعداد الاختبارات:
// tests/setup.ts
import '@testing-library/jest-dom'وإعداد Playwright:
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI ? 'github' : 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'npm run build && npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
})الخطوة 2: كتابة اختبارات تجريبية
قبل ربط CI/CD، ننشئ اختبارات سيشغّلها خط الأنابيب.
اختبار وحدات
// tests/unit/home.test.tsx
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import Home from '@/app/page'
describe('الصفحة الرئيسية', () => {
it('يعرض العنوان الرئيسي', () => {
render(<Home />)
const heading = screen.getByRole('heading', { level: 1 })
expect(heading).toBeInTheDocument()
})
it('يحتوي على رابط البدء', () => {
render(<Home />)
const link = screen.getByRole('link', { name: /get started/i })
expect(link).toBeInTheDocument()
})
})اختبار E2E
// tests/e2e/navigation.spec.ts
import { test, expect } from '@playwright/test'
test.describe('التنقل', () => {
test('الصفحة الرئيسية تحمّل وتعرض بشكل صحيح', async ({ page }) => {
await page.goto('/')
await expect(page).toHaveTitle(/Next.js/)
await expect(page.locator('h1')).toBeVisible()
})
test('الصفحة تحتوي على تسلسل عناوين صحيح', async ({ page }) => {
await page.goto('/')
const headings = await page.locator('h1, h2, h3').all()
expect(headings.length).toBeGreaterThan(0)
})
})تحديث سكربتات package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"type-check": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
}
}الخطوة 3: إنشاء سير عمل GitHub Actions
هنا يحصل السحر. أنشئ ملف سير العمل:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: '20'
PNPM_VERSION: '9'
jobs:
# ─── المرحلة 1: جودة الكود ──────────────────────────
lint:
name: 🧹 فحص الكود والأنواع
runs-on: ubuntu-latest
steps:
- name: جلب الكود
uses: actions/checkout@v4
- name: إعداد pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: إعداد Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: تثبيت التبعيات
run: pnpm install --frozen-lockfile
- name: تشغيل ESLint
run: pnpm lint
- name: فحص أنواع TypeScript
run: pnpm type-check
# ─── المرحلة 2: اختبارات الوحدات ─────────────────────
unit-tests:
name: 🧪 اختبارات الوحدات
runs-on: ubuntu-latest
needs: lint
steps:
- name: جلب الكود
uses: actions/checkout@v4
- name: إعداد pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: إعداد Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: تثبيت التبعيات
run: pnpm install --frozen-lockfile
- name: تشغيل اختبارات الوحدات مع التغطية
run: pnpm test:coverage
- name: رفع تقرير التغطية
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: coverage/
retention-days: 7
# ─── المرحلة 3: اختبارات E2E ─────────────────────────
e2e-tests:
name: 🎭 اختبارات E2E
runs-on: ubuntu-latest
needs: lint
steps:
- name: جلب الكود
uses: actions/checkout@v4
- name: إعداد pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: إعداد Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: تثبيت التبعيات
run: pnpm install --frozen-lockfile
- name: تثبيت متصفحات Playwright
run: pnpm exec playwright install --with-deps chromium
- name: بناء التطبيق
run: pnpm build
- name: تشغيل اختبارات E2E
run: pnpm test:e2e
- name: رفع تقرير Playwright
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
# ─── المرحلة 4: النشر ─────────────────────────────────
deploy:
name: 🚀 النشر على Vercel
runs-on: ubuntu-latest
needs: [unit-tests, e2e-tests]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment:
name: production
url: ${{ steps.deploy.outputs.url }}
steps:
- name: جلب الكود
uses: actions/checkout@v4
- name: إعداد pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: إعداد Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: تثبيت التبعيات
run: pnpm install --frozen-lockfile
- name: تثبيت Vercel CLI
run: pnpm add -g vercel
- name: سحب بيئة Vercel
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- name: البناء مع Vercel
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
- name: النشر على Vercel
id: deploy
run: |
url=$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }})
echo "url=$url" >> $GITHUB_OUTPUTتفصيل كل مرحلة:
| المرحلة | المشغّل | ماذا تفعل | تمنع النشر؟ |
|---|---|---|---|
| فحص الكود | كل push و PR | ESLint + فحص TypeScript | نعم |
| اختبارات الوحدات | بعد نجاح الفحص | Vitest + تقرير التغطية | نعم |
| اختبارات E2E | بعد نجاح الفحص (بالتوازي) | اختبارات Playwright | نعم |
| النشر | بعد نجاح كل شي، main فقط | نشر إنتاج Vercel | — |
🚀 تحتاج مساعدة في إعداد CI/CD لمشروعك؟ نقطة تبني تطبيقات ويب احترافية مع خطوط اختبار ونشر تلقائية مدمجة من البداية.
الخطوة 4: إعداد أسرار GitHub
سير العمل يحتاج وصول لـ Vercel. إليك كيفية إعداد الأسرار:
- اذهب لمستودعك على GitHub → Settings → Secrets and variables → Actions
- أضف هذه الأسرار:
| السر | كيف تحصل عليه |
|---|---|
VERCEL_TOKEN | لوحة Vercel → أنشئ رمز |
VERCEL_ORG_ID | شغّل vercel link محلياً → تحقق من .vercel/project.json |
VERCEL_PROJECT_ID | نفس الملف أعلاه |
# طريقة سريعة للحصول على معرّفات Vercel
vercel link
cat .vercel/project.jsonستجد شيء مثل:
{
"orgId": "team_xxxxxxxxxxxx",
"projectId": "prj_xxxxxxxxxxxx"
}انسخ هذه القيم إلى أسرار GitHub.
الخطوة 5: إضافة تعليقات حالة على PR
اجعل خط الأنابيب صديق للمطورين بنشر نتائج الاختبار مباشرة على طلبات الدمج:
# ─── تعليق PR بالنتائج ────────────────────────────
pr-comment:
name: 📝 حالة PR
runs-on: ubuntu-latest
needs: [unit-tests, e2e-tests]
if: github.event_name == 'pull_request'
permissions:
pull-requests: write
steps:
- name: تنزيل تقرير التغطية
uses: actions/download-artifact@v4
with:
name: coverage-report
path: coverage/
- name: التعليق على PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let coverageSummary = 'تقرير التغطية غير متوفر';
try {
const coverage = JSON.parse(
fs.readFileSync('coverage/coverage-summary.json', 'utf8')
);
const total = coverage.total;
coverageSummary = `
| المقياس | التغطية |
|---------|---------|
| العبارات | ${total.statements.pct}% |
| الفروع | ${total.branches.pct}% |
| الدوال | ${total.functions.pct}% |
| الأسطر | ${total.lines.pct}% |`;
} catch (e) {
console.log('تعذر قراءة التغطية:', e.message);
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## ✅ نجح خط CI
### تغطية الاختبارات
${coverageSummary}
### ملخص خط الأنابيب
- 🧹 فحص الكود والأنواع: ✅
- 🧪 اختبارات الوحدات: ✅
- 🎭 اختبارات E2E: ✅
> _مؤتمت بواسطة GitHub Actions_`
});الخطوة 6: إضافة قواعد حماية الفرع
خط الأنابيب عديم الفائدة إذا المطورين يقدرون يتجاوزوه. اقفله:
- اذهب إلى Settings → Branches → Add rule
- نمط اسم الفرع:
main - فعّل:
- ✅ طلب Pull Request قبل الدمج
- ✅ طلب نجاح فحوصات الحالة قبل الدمج
- ✅ طلب تحديث الفرع قبل الدمج
- أضف فحوصات الحالة المطلوبة:
🧹 فحص الكود والأنواع🧪 اختبارات الوحدات🎭 اختبارات E2E
الآن لا أحد يقدر يرفع مباشرة على main بدون نجاح كل الفحوصات.
الخطوة 7: تحسين الأداء بالتخزين المؤقت
سير العمل أعلاه يستخدم تخزين pnpm عبر actions/setup-node. لكن لمتصفحات Playwright وبناء Next.js، أضف تخزين مؤقت صريح:
# أضف لمهمة e2e-tests قبل "تثبيت متصفحات Playwright"
- name: تخزين متصفحات Playwright مؤقتاً
uses: actions/cache@v4
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: تثبيت متصفحات Playwright
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium
# أضف لأي مهمة تشغل `next build`
- name: تخزين بناء Next.js مؤقتاً
uses: actions/cache@v4
with:
path: .next/cache
key: nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}
restore-keys: |
nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-مع التخزين المؤقت، خط الأنابيب ينزل من ~4 دقائق إلى أقل من دقيقتين في التشغيلات اللاحقة.
الخطوة 8: نشر حسب البيئة
لمشروع حقيقي، تريد نشر معاينة على PRs ونشر إنتاج على main:
# ─── نشر المعاينة (PRs) ──────────────────────────
deploy-preview:
name: 🔍 نشر معاينة
runs-on: ubuntu-latest
needs: [unit-tests, e2e-tests]
if: github.event_name == 'pull_request'
environment:
name: preview
url: ${{ steps.deploy.outputs.url }}
steps:
- name: جلب الكود
uses: actions/checkout@v4
- name: إعداد pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: إعداد Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: تثبيت التبعيات
run: pnpm install --frozen-lockfile
- name: تثبيت Vercel CLI
run: pnpm add -g vercel
- name: نشر المعاينة
id: deploy
run: |
vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
vercel build --token=${{ secrets.VERCEL_TOKEN }}
url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
echo "url=$url" >> $GITHUB_OUTPUT
- name: تعليق رابط المعاينة على PR
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `🔍 **تم نشر المعاينة:** ${{ steps.deploy.outputs.url }}`
});الخطوة 9: مراقبة صحة خط الأنابيب
أضف شارة حالة سير العمل في README:
[](https://github.com/YOUR_ORG/YOUR_REPO/actions/workflows/ci.yml)وأضف إشعارات عند الفشل:
# ─── إشعار عند الفشل ──────────────────────────────
notify:
name: 🔔 إشعار
runs-on: ubuntu-latest
needs: [lint, unit-tests, e2e-tests, deploy]
if: failure()
steps:
- name: إرسال إشعار Slack
uses: slackapi/slack-github-action@v2
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"text": "❌ فشل خط CI/CD",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "❌ *فشل خط الأنابيب* على `${{ github.ref_name }}`\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|عرض التشغيل>"
}
}
]
}💡 تبي خط اختبار ونشر متين بدون ما تبنيه بنفسك؟ فريق ضمان الجودة في نقطة يعدّ خطوط CI/CD احترافية مع مراقبة، عشان فريقك ينشر بثقة.
أخطاء شائعة وحلولها
1. "نفاد مساحة القرص" على GitHub runners
بناء Next.js يمكن يكون كبير. حرّر مساحة:
- name: تحرير مساحة القرص
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /usr/local/lib/android2. اختبارات E2E تنتهي بمهلة
زد مهلة Playwright وأضف محاولات إعادة:
// playwright.config.ts
export default defineConfig({
timeout: 60_000,
expect: { timeout: 10_000 },
retries: process.env.CI ? 2 : 0,
})3. اختبارات غير مستقرة تمنع النشر
استخدم toPass في Playwright للفحوصات التي تحتاج وقت:
await expect(async () => {
const response = await page.request.get('/api/health')
expect(response.status()).toBe(200)
}).toPass({ timeout: 30_000 })4. تثبيت التبعيات بطيء
دائماً استخدم --frozen-lockfile وتخزين pnpm:
- name: إعداد pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: إعداد Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'مخطط خط الأنابيب الكامل
Push / PR
│
▼
┌──────────────────┐
│ 🧹 فحص الكود │
│ والأنواع │
└────────┬─────────┘
│
┌────┴────┐
▼ ▼
┌─────────┐ ┌─────────┐
│🧪 وحدات│ │🎭 E2E │
└───┬─────┘ └───┬─────┘
│ │
└─────┬─────┘
│
▼
┌──────────┐ ┌───────────┐
│ PR؟ │──نعم──▶│🔍 معاينة │
└────┬─────┘ └───────────┘
│لا (main)
▼
┌───────────┐
│🚀 نشر │
│الإنتاج │
└───────────┘
الخلاصة
إليك ما بنيته:
| المكوّن | الأداة | الغرض |
|---|---|---|
| فحص الكود | ESLint + TypeScript | كشف الأخطاء قبل التشغيل |
| اختبارات الوحدات | Vitest + Testing Library | اختبار المكونات بمعزل |
| اختبارات E2E | Playwright | اختبار تدفقات المستخدم الحقيقية |
| CI/CD | GitHub Actions | أتمتة كل شيء |
| النشر | Vercel CLI | نشر بدون توقف |
| التخزين المؤقت | GitHub Actions Cache | خطوط أسرع بـ 50%+ |
| حماية الفروع | إعدادات GitHub | فرض بوابات الجودة |
خط الأنابيب الكامل يعمل في أقل من 3 دقائق مع التخزين المؤقت. كل PR يحصل على نشر معاينة. كل دمج على main يطلق نشر الإنتاج — بس بعد نجاح كل الاختبارات.
توقف عن النشر اليدوي. توقف عن الدعاء إن كودك يشتغل. أتمته.
الخطوات التالية
- أضف اختبار الانحدار البصري بلقطات شاشة Playwright
- أعدّ ترحيل قواعد البيانات في خط الأنابيب للتطبيقات كاملة المكدس
- أضف Lighthouse CI لمراقبة الأداء مع كل PR
- طبّق أعلام الميزات للإطلاق التدريجي بعد النشر
أفضل خط CI/CD هو اللي تعدّه مرة وتثق فيه للأبد. الآن عندك واحد.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

دليل 2026 لنشر Laravel + Vue: من Docker إلى GitHub Actions وCoolify
دليل عملي شامل لبناء خط نشر CI/CD احترافي لتطبيق Laravel وVue باستخدام Docker وGitHub Actions وCoolify مع أفضل ممارسات الأمان والمراقبة والتراجع الآمن.

الدليل التفصيلي لتثبيت وهيكلة تطبيقك في Next.js لأداء أمثل
الدليل التفصيلي لتثبيت وهيكلة تطبيقك في Next.js لأداء أمثل: عزز تطبيق Next.js الخاص بك باستخدام هذا الدليل الشامل حول التثبيت وأفضل الممارسات لهيكلة مشروعك لتحقيق الأداء الأمثل.

بناء تطبيق ذكاء اصطناعي محادثي مع Next.js
تعلم كيفية بناء تطبيق ويب يتيح محادثات صوتية في الوقت الفعلي مع وكلاء الذكاء الاصطناعي باستخدام Next.js وElevenLabs.