Next.js: تعزيز الأمان وترحيل الإصدارات
آخر تحديث: 2026-08-26
الأمان وترحيل الإصدارات هما خطا الدفاع للتطبيقات على المستوى الإنتاجي — الأول يمنع الهجمات الخارجية، بينما الثاني يضمن بقاء مجموعة التقنيات خالية من الديون التقنية.
1. ما ستتعلمه
- تنفيذ حماية XSS ورموز CSRF ورؤوس أمان HTTP (next.config.js headers)
- منع تسرب متغيرات البيئة (اتفاقية NEXT_PUBLIC_* / .env*.local في .gitignore)
- استخدام npm audit و Snyk لإجراء تدقيقات أمان التبعيات
- إتقان ترحيل v14→v15: المعالجة غير المتزامنة لـ params و cookies و headers
- فهم ترحيل v15→v16: proxy.ts يحل محل Middleware، وTurbopack كافتراضي، وترحيل "use cache"
2. قصة حقيقية لمهندس أمان
(1) نقطة الألم: هجمات XSS تؤدي إلى تسرب بيانات المستخدمين
بعد ثلاثة أشهر من إطلاق فريق Bob لمنصة TaskFlow SaaS في البرازيل، تلقوا تقرير أمان:
"قام المهاجم بحقن وسم
<script>في حقل 'اسم المشروع' ونجح في سرقة ملفات تعريف ارتباط الجلسة من أكثر من 200 مستخدم. كشف التحقيق أن حقل 'اسم المشروع' في صفحة Dashboard لم يتم ترميزه بشكل صحيح أثناء الإخراج."
حدد تدقيق الأمان سلسلة من المشكلات:
| الثغرة | التأثير | الخطورة |
|---|---|---|
| XSS (منعكس) | اختطاف الجلسة، سرقة البيانات | 🔴 شديدة |
| غياب رمز CSRF | تزوير الطلبات عبر المواقع | 🟠 عالية |
| غياب رؤوس أمان HTTP | Clickjacking، MIME sniffing | 🟡 متوسطة |
| إساءة استخدام NEXT_PUBLIC_ | مفتاح API مكشوف في حزمة JS | 🔴 شديدة |
| ثغرة تبعية | lodash — CVE معروف، غير مُصحح | 🟠 عالية |
في الوقت نفسه، يتراكم الدين التقني — المشروع مبني على Next.js 14، ولكن Next.js 16 صدر قبل ستة أشهر، والفريق قلق بشأن مخاطر الأمان وخسائر الأداء المرتبطة بتشغيل إصدار قديم.
(2) حل تعزيز الأمان + ترحيل الإصدارات
يتعامل Bob مع الأمان على خطوتين:
# الخطوة 1: إصلاح ثغرات الأمان فورًا
npm audit fix
# إضافة رأس أمان إلى next.config.js
# إضافة رمز CSRF إلى جميع النماذج
# الخطوة 2: ترحيل الإصدارات (14→15→16)
npm install next@16 react@19 react-dom@19
(3) النتائج
| البعد | قبل التعزيز | بعد التعزيز |
|---|---|---|
| درجة الأمان (Mozilla Observatory) | D (45/100) | A+ (100/100) |
| CVEs المعروفة | 12 | 0 |
| الإصدار | Next.js 14.2 | Next.js 16.2 LTS |
| وقت البناء | 45s | 12s (Turbopack) |
| الأداء (Lighthouse) | 72 | 92 |
3. إعداد رؤوس أمان HTTP
يمكن لدالة headers في next.config.js حقن رؤوس استجابة HTTP، متجاوزة سياسات الأمان مثل CSP و HSTS و X-Frame-Options.
graph TB
A[User Request] --> B[Next.js Applications]
B --> C[headers Function Handling]
C --> D[Inject the safety head]
D --> E[Browser Security Policy Enabled]
E --> F[CSP Block XSS]
E --> G[HSTS Mandatory HTTPS]
E --> H[X-Frame-Options Prevent Click Hijacking]
style C fill:#cce5ff
style E fill:#d4edda
| رأس الأمان | الغرض | مثال للقيمة |
|---|---|---|
Content-Security-Policy |
منع XSS وحقن البيانات | default-src 'self' |
Strict-Transport-Security |
فرض HTTPS | max-age=31536000; includeSubDomains |
X-Frame-Options |
منع Clickjacking | SAMEORIGIN |
X-Content-Type-Options |
منع MIME sniffing | nosniff |
Referrer-Policy |
التحكم في معلومات المُحيل | strict-origin-when-cross-origin |
Permissions-Policy |
التحكم في أذونات واجهة برمجة المتصفح | camera=(), microphone=() |
(1) إعداد رؤوس الأمان في next.config.js
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
async headers() {
return [
{
// تطبيق على جميع المسارات
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.googletagmanager.com https://app.posthog.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' blob: data: https://*.taskflow.io https://avatars.githubusercontent.com",
"font-src 'self' data:",
"connect-src 'self' https://api.taskflow.io https://app.posthog.com https://o450000000.ingest.sentry.io",
"frame-ancestors 'none'",
"form-action 'self'",
"base-uri 'self'",
"object-src 'none'"
].join('; ')
},
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: [
'camera=()',
'microphone=()',
'geolocation=()',
'interest-cohort=()'
].join(', ')
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
}
]
},
{
// قيود أمان إضافية على مسارات API
source: '/api/(.*)',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Cache-Control',
value: 'no-store, no-cache, must-revalidate'
}
]
}
]
}
}
module.exports = nextConfig
(2) حماية رمز CSRF
// src/components/Form.tsx
'use client'
import { useFormState } from 'react-dom'
import { createTask } from '@/actions/task'
export function TaskForm() {
const [state, formAction] = useFormState(createTask, { error: null })
return (
<form action={formAction}>
{/* حقل مخفي لرمز CSRF */}
<input type="hidden" name="csrf_token" value={getCsrfToken()} />
<input
type="text"
name="title"
placeholder="Task title"
className="border p-2 rounded"
/>
<button
type="submit"
className="bg-blue-600 text-white px-4 py-2 rounded"
>
Create Task
</button>
{state.error && (
<p className="text-red-600 mt-2">{state.error}</p>
)}
</form>
)
}
▶ مثال: التحقق من الرؤوس
# التحقق من رؤوس الأمان
curl -I https://taskflow.io
# الناتج المتوقع
HTTP/2 200
content-security-policy: default-src 'self'; script-src 'self' 'unsafe-eval'...
strict-transport-security: max-age=31536000; includeSubDomains
x-frame-options: DENY
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(), microphone=(), geolocation=()
Output:
{"status":"ok","data":{}}
4. منع تسرب متغيرات البيئة
المتغيرات ذات البادئة NEXT_PUBLIC_ ستُجمع في حزمة JS من جانب العميل — أي أسرار توضع تحت هذه البادئة ستُكشف للمتصفح.
graph TB
A[.env.local] --> B{Environment Variable Prefix}
B -->|NEXT_PUBLIC_*| C[Inline to JS bundle]
B -->|Other| D[Server-side only]
C --> E[Browser DevTools As can be seen]
D --> F[Safety]
style C fill:#f8d7da
style D fill:#d4edda
style E fill:#f8d7da
| بادئة المتغير | نطاق الاستخدام | مخاطر الأمان | السيناريوهات المناسبة |
|---|---|---|---|
NEXT_PUBLIC_ |
العميل + الخادم | 🔴 عالية (مكشوفة في JS) | مفتاح API العام، اسم المشروع |
| بدون بادئة | الخادم فقط | 🟢 آمنة | كلمات مرور قاعدة البيانات، مفاتيح API |
process.env.* |
تُحقن أثناء البناء | 🟡 متوسطة (حسب بيئة البناء) | التكوين أثناء البناء |
(1) قائمة فحص أمان متغيرات البيئة
# ✅ آمن: استخدام متغيرات بدون بادئة
DATABASE_URL=postgresql://user:pass@host:5432/db
AUTH_SECRET=super-secret-key-here
STRIPE_SECRET_KEY=sk_live_xxx
SENTRY_DSN=https://xxx@sentry.io/123
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
# ✅ صحيح: NEXT_PUBLIC_ للمفاتيح العامة
NEXT_PUBLIC_POSTHOG_KEY=phc_publicKey
NEXT_PUBLIC_SENTRY_DSN=https://public@sentry.io/123
NEXT_PUBLIC_APP_URL=https://taskflow.io
# ❌ خطر: لا تخزن المفاتيح تحت NEXT_PUBLIC_
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_xxx # مكشوفة في المتصفح!
NEXT_PUBLIC_DATABASE_URL=postgresql://... # اتصال قاعدة البيانات مكشوف!
(2) إعداد .gitignore
# .gitignore — أمان متغيرات البيئة
.env
.env*.local
.env.development.local
.env.test.local
.env.production.local
# لا تنسَ .env.example (هذا ملف قالب)
!.env.example
(3) سكريبت التحقق من متغيرات البيئة
// src/lib/env-validation.ts
// التأكد من عدم إمكانية الوصول إلى متغيرات البيئة الحساسة من جانب العميل
function validateEnv() {
if (typeof window !== 'undefined') {
const nextPublicVars = Object.keys(process.env)
.filter(key => key.startsWith('NEXT_PUBLIC_'))
// التحقق من الكشف العرضي للمتغيرات الخاصة
const sensitiveSuffixes = ['SECRET', 'KEY', 'PASSWORD', 'TOKEN', 'PRIVATE']
for (const key of nextPublicVars) {
const suffix = key.replace('NEXT_PUBLIC_', '')
if (sensitiveSuffixes.some(s => suffix.toUpperCase().includes(s))) {
console.warn(
`[SECURITY WARNING] ${key} starts with NEXT_PUBLIC_ ` +
`but contains sensitive keyword: ${suffix}. ` +
`This will be exposed in the client bundle!`
)
}
}
}
}
validateEnv()
▶ مثال: فحص المتغيرات المكشوفة في الحزمة
# التحقق بعد البناء مما إذا كان JS العميل يحتوي على مفتاح
npm run build
# البحث عن كلمات حساسة في نواتج البناء
grep -r "sk_live_" .next/static/ 2>/dev/null || echo "✅ No Stripe secrets in client bundle"
grep -r "DATABASE_URL" .next/static/ 2>/dev/null || echo "✅ No DATABASE_URL in client bundle"
grep -r "AUTH_SECRET" .next/static/ 2>/dev/null || echo "✅ No AUTH_SECRET in client bundle"
Output:
▲ Next.js 16.0.0
Creating an optimized production build ...
✓ Compiled successfully
✓ Linting and checking validity of types
✓ Collecting page data
✓ Generating static pages (5/5)
✓ Finalizing page optimization
Route (app) Size First Load JS
┌ ○ / 5.1 kB 89 kB
├ ○ /about 2.3 kB 86 kB
└ λ /api/items 0 B 84 kB
○ (Static) prerendered as static content
λ (Dynamic) server-rendered on demand
✅ No Stripe secrets in client bundle
✅ No DATABASE_URL in client bundle
✅ No AUTH_SECRET in client bundle
5. تدقيق أمان التبعيات
(1) npm audit
# تدقيق أساسي
npm audit
# تصفية حسب مستوى الخطورة
npm audit --audit-level=high
# إصلاح تلقائي (قد يكسر التوافق)
npm audit fix
# إصلاح إصدارات patch فقط
npm audit fix --target=patch
# إنشاء تقرير JSON
npm audit --json > security-report.json
(2) دمج Snyk
# تثبيت Snyk CLI
npm install -g snyk
# المصادقة
snyk auth
# اختبار المشروع
snyk test
# مراقبة مستمرة
snyk monitor
# فحص صورة Docker
snyk container test taskflow:latest --file=Dockerfile
# .github/workflows/security.yml
name: Security Audit
on:
push:
branches: [main]
schedule:
- cron: '0 6 * * 1' # كل يوم اثنين الساعة 6 صباحًا
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run npm audit
run: npx audit-ci --high --report-type summary
continue-on-error: true
- name: Snyk Security Scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: snyk.sarif
| الأداة | نطاق الكشف | دمج CI | الإصلاحات الموصى بها |
|---|---|---|---|
| npm audit | تبعيات Node.js | ✅ مدمج | npm audit fix |
| Snyk | التبعيات + Docker + IaC | ✅ Action | إصلاح تلقائي عبر PR |
| GitHub Dependabot | إصدارات التبعيات | ✅ مدمج | ترقية تلقائية عبر PR |
| CodeQL | ثغرات الكود المصدري | ✅ Action | إصلاح على مستوى الكود |
6. دليل ترحيل v14 إلى v15
أكبر تغيير في Next.js 15 هو المعالجة غير المتزامنة لـ params و cookies و headers — هذا هو التغيير الأكثر إرباكًا.
graph TB
subgraph "v14 Synchronize API(Obsolete)"
A[params.id]
B[cookies().get()]
C[headers().get()]
end
subgraph "v15 Asynchronous API(Must)"
D[await params]
E[await cookies()]
F[await headers()]
end
G[generateMetadata It is also necessary to await]
H[Change all dynamic routes to async]
D --> G
E --> G
F --> H
style A fill:#f8d7da
style B fill:#f8d7da
style C fill:#f8d7da
style D fill:#d4edda
style E fill:#d4edda
style F fill:#d4edda
(1) قائمة فحص الترحيل
| التغيير | v14 (قديم) | v15 (جديد) | الملفات المتأثرة |
|---|---|---|---|
| الدعم | params.id (متزامن) |
await params (غير متزامن) |
page.tsx، layout.tsx |
| searchParams | searchParams.page (متزامن) |
await searchParams (غير متزامن) |
page.tsx |
| cookies | cookies().get() (متزامن) |
await cookies() (غير متزامن) |
layout.tsx، middleware.ts |
| headers | headers().get() (متزامن) |
await headers() (غير متزامن) |
layout.tsx، page.tsx |
| generateMetadata | params متزامن |
params غير متزامن |
page.tsx |
| React | React 18 | React 19 | عام |
| useFormState | react-dom |
أُعيد تسميته إلى useActionState |
مكون النموذج |
(2) مثال على ترحيل المتزامن إلى غير المتزامن
// ❌ v14 (قديم) — params متزامن
export default function ProjectPage({
params
}: {
params: { id: string }
}) {
return <div>Project {params.id}</div>
}
// ✅ v15 (جديد) — params غير متزامن
export default async function ProjectPage({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
return <div>Project {id}</div>
}
// ❌ v14 — generateMetadata متزامن
export function generateMetadata({ params }: { params: { id: string } }) {
return { title: `Project ${params.id}` }
}
// ✅ v15 — generateMetadata غير متزامن
export async function generateMetadata({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
return { title: `Project ${id}` }
}
▶ مثال: المعالجة غير المتزامنة لـ cookies و headers
Output:
Renders the generateMetadata component UI.
// ❌ v14 — استرجاع متزامن لـ cookies
import { cookies } from 'next/headers'
export function Layout({ children }: { children: React.ReactNode }) {
const token = cookies().get('session_token')
return <div>{children}</div>
}
// ✅ v15 — استرجاع غير متزامن لـ cookies
import { cookies } from 'next/headers'
export default async function Layout({
children
}: {
children: React.ReactNode
}) {
const cookieStore = await cookies()
const token = cookieStore.get('session_token')
return <div>{children}</div>
}
Output:
Renders the Layout component UI.
7. دليل ترحيل v15 إلى v16
يقدم Next.js 16 ثلاثة تغييرات رئيسية: proxy.ts يحل محل middleware، وTurbopack كافتراضي، وترحيل use cache.
graph LR
A[v15] --> B{v16 Migration}
B --> C[proxy.ts Replace middleware<br/>Configurable Routing Rules]
B --> D[Turbopack Default<br/>Remove webpack Layout]
B --> E[use cache Migration<br/>fetch cache → cacheTag]
C --> F[More Declarative Configuration]
D --> G[Build Faster 10x]
E --> H[More Granular Cache Control]
style A fill:#f8d7da
style C fill:#d4edda
style D fill:#d4edda
style E fill:#d4edda
| التغيير | v15 (قديم) | v16 (جديد) | إجراء الترحيل |
|---|---|---|---|
| اعتراض المسار | middleware.ts (وظيفي) |
proxy.ts (تكويني) |
استخراج منطق middleware إلى proxy.ts |
| أدوات البناء | webpack (افتراضي) | Turbopack (افتراضي) | إزالة تكوين webpack المخصص |
| نموذج التخزين المؤقت | fetch تخزين تلقائي |
use cache تعليمات |
نقل تخزين البيانات إلى cacheTag |
| React | React 19 RC | React 19 مستقر | ترقية الإصدار |
| PPR | experimental.ppr |
إصدار مستقر | إزالة بادئة experimental. |
(1) إعداد proxy.ts (واجهة برمجة جديدة في v16)
// src/proxy.ts — v16 يحل محل middleware.ts
// ============================================
// proxy.ts هو تكوين قواعد توجيه تصريحي
// ============================================
import { defineProxy } from 'next/proxy'
export default defineProxy({
// قواعد مطابقة المسار
matcher: [
// المسارات التي تتطلب مصادقة
{ pathname: '/dashboard/:path*', requireAuth: true },
{ pathname: '/projects/:path*', requireAuth: true },
{ pathname: '/settings/:path*', requireAuth: true },
// قواعد إعادة التوجيه
{ pathname: '/old-blog/:slug*', redirect: '/blog/:slug*' },
// تخطي الموارد الثابتة
{ pathname: '/_next/:path*', bypass: true },
{ pathname: '/favicon.ico', bypass: true }
],
// فحص المصادقة
async auth(request) {
const session = await getSession(request)
if (!session) {
return new Response(null, {
status: 307,
headers: { Location: '/login' }
})
}
return null // سماح
},
// حقن رؤوس الطلب
async headers(request) {
return {
'x-request-id': crypto.randomUUID(),
'x-user-locale': getPreferredLocale(request)
}
}
})
// دوال مساعدة
async function getSession(request: Request) {
const cookie = request.headers.get('cookie') || ''
const token = cookie.split('session_token=')?.[1]?.split(';')?.[0]
if (!token) return null
return { userId: 'user_123', role: 'admin' }
}
function getPreferredLocale(request: Request) {
const acceptLang = request.headers.get('accept-language') || 'en'
return acceptLang.split(',')[0].split('-')[0]
}
(2) ترحيل use cache
// ❌ v15 — استخدام fetch مع cache
async function getProjects() {
const res = await fetch('https://api.example.com/projects', {
next: { revalidate: 300 }
})
return res.json()
}
// ✅ v16 — استخدام use cache + cacheTag
import { cacheTag, cacheLife } from 'next/cache'
async function getProjects() {
'use cache'
cacheTag('projects')
cacheLife('hours')
const res = await fetch('https://api.example.com/projects')
return res.json()
}
(3) تكوين Turbopack الافتراضي
// next.config.js — نسخة مبسطة لـ v16
/** @type {import('next').NextConfig} */
const nextConfig = {
// v16 Turbopack هو الافتراضي، لا حاجة للتكوين
// إزالة التكوين التالي الخاص بـ v15:
// swcMinify: true, // Turbopack افتراضي
// compiler: { ... }, // مدمج بالفعل
// webpack: (config) => config, // إزالة webpack المخصص
output: 'standalone',
// v16 PPR مستقر
ppr: true,
// الاحتفاظ بالتخصيصات الضرورية
serverExternalPackages: ['@prisma/client'],
images: {
formats: ['image/avif', 'image/webp']
}
}
module.exports = nextConfig
▶ مثال: مقارنة النظام قبل وبعد الترحيل الكامل
Output:
Next.js configuration applied. Changes take effect on server restart.
// === v14 الكود الأصلي ===
// page.tsx
export default function Page({ params }: { params: { id: string } }) {
return <div>{params.id}</div>
}
// layout.tsx
import { cookies } from 'next/headers'
export default function Layout({ children }: { children: React.ReactNode }) {
const theme = cookies().get('theme')
return <div className={theme?.value}>{children}</div>
}
// middleware.ts
import { NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
if (!request.cookies.has('token')) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
// === v16 بعد الترحيل ===
// page.tsx
export default async function Page({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
return <div>{id}</div>
}
// layout.tsx
import { cookies } from 'next/headers'
export default async function Layout({
children
}: {
children: React.ReactNode
}) {
const cookieStore = await cookies()
const theme = cookieStore.get('theme')
return <div className={theme?.value}>{children}</div>
}
// proxy.ts (يحل محل middleware.ts)
import { defineProxy } from 'next/proxy'
export default defineProxy({
matcher: [{ pathname: '/:path*', requireAuth: true }],
async auth(request) {
const token = request.headers.get('cookie')?.includes('token')
if (!token) return Response.redirect('/login')
return null
}
})
Output:
Renders the ▶ Example: Comparison of the system before and after a complete migration component UI as described in the section.
8. مثال كامل: سير عمل تعزيز الأمان + ترحيل الإصدارات
# ============================================
# security-migration.sh
# الميزات: سكريبت كامل لتعزيز الأمان + ترحيل الإصدارات
# ============================================
#!/bin/bash
set -euo pipefail
echo "=== TaskFlow Security & Migration ==="
# المرحلة 1: تعزيز الأمان
echo ""
echo "--- Phase 1: Security Hardening ---"
# 1.1 تدقيق تبعيات وقت التشغيل
echo "Running npm audit..."
npm audit --audit-level=high
npm audit fix --target=minor || true
# 1.2 التحقق من الرؤوس
echo "Generating security headers report..."
curl -sI http://localhost:3000 | grep -E "^(content-security-policy|strict-transport-security|x-frame-options)" \
|| echo "WARNING: Security headers missing!"
# 1.3 فحص تسرب NEXT_PUBLIC
echo "Checking for leaked secrets..."
grep -r "NEXT_PUBLIC_.*SECRET\|NEXT_PUBLIC_.*KEY\|NEXT_PUBLIC_.*PASSWORD" \
.env* 2>/dev/null && echo "WARNING: Secrets in NEXT_PUBLIC!" || echo "✅ No leaked secrets"
# 1.4 التحقق من حماية XSS
echo "Verifying XSS protection..."
# اختبار ترميز الإدخال
echo "<script>alert('xss')</script>" | node -e "
const { escape } = require('querystring')
process.stdin.on('data', d => console.log('Encoded:', escape(d.toString())))
"
# المرحلة 2: ترحيل الإصدارات
echo ""
echo "--- Phase 2: Version Migration ---"
# 2.1 التحقق من الإصدار الحالي
CURRENT_VERSION=$(node -e "console.log(require('./node_modules/next/package.json').version)")
echo "Current Next.js version: v$CURRENT_VERSION"
# 2.2 نسخ احتياطي
echo "Backing up current node_modules..."
mv node_modules node_modules_v${CURRENT_VERSION}.bak
mv package-lock.json package-lock_v${CURRENT_VERSION}.json.bak
# 2.3 ترقية
echo "Upgrading to Next.js 16..."
npm install next@latest react@latest react-dom@latest @types/react@latest @types/react-dom@latest
# 2.4 تشغيل codemod
echo "Running codemods..."
npx @next/codemod@latest built-in-next-font .
npx @next/codemod@latest metadata-to-viewport-export .
npx @next/codemod@latest async-request-params .
# 2.5 تحويل middleware إلى proxy.ts
if [ -f src/middleware.ts ]; then
echo "Converting middleware.ts to proxy.ts..."
mv src/middleware.ts src/middleware_v15.bak.ts
fi
# 2.6 البناء والتحقق
echo "Building with Next.js 16..."
npm run build
# 2.7 تشغيل الاختبارات الكاملة
echo "Running test suite..."
npm test
npm run test:e2e
echo ""
echo "=== Migration Complete ==="
echo "Next.js $(node -e "console.log(require('./node_modules/next/package.json').version)")"
echo "React $(node -e "console.log(require('./node_modules/react/package.json').version)")"
echo "Build: ✅"
echo "Tests: ✅"
echo "Security: ✅"
// src/app/security/report/page.tsx
// ============================================
// صفحة تقرير الأمان
// ============================================
import { headers } from 'next/headers'
import { execSync } from 'child_process'
async function checkSecurityHeaders() {
const h = await headers()
const requiredHeaders = [
'content-security-policy',
'strict-transport-security',
'x-frame-options',
'x-content-type-options',
'referrer-policy',
'permissions-policy'
]
const results = requiredHeaders.map(name => ({
name,
present: h.has(name),
value: h.get(name) || ''
}))
const score = results.filter(r => r.present).length / results.length * 100
return { results, score }
}
async function checkDependencies() {
try {
const auditOutput = execSync('npm audit --json --audit-level=high', {
encoding: 'utf-8',
timeout: 30000
}).toString()
const audit = JSON.parse(auditOutput)
return {
vulnerabilities: audit.metadata?.vulnerabilities || { high: 0, critical: 0 },
total: audit.metadata?.totalDependencies || 0
}
} catch {
return { vulnerabilities: { high: 0, critical: 0 }, total: 0 }
}
}
const gradeMap: Record<number, string> = {
100: 'A+', 90: 'A', 80: 'B', 70: 'C', 60: 'D', 0: 'F'
}
function getGrade(score: number): string {
for (const [threshold, grade] of Object.entries(gradeMap)) {
if (score >= Number(threshold)) return grade
}
return 'F'
}
export default async function SecurityReportPage() {
const [securityCheck, deps] = await Promise.all([
checkSecurityHeaders(),
checkDependencies()
])
return (
<div className="p-6 space-y-6">
<h1 className="text-2xl font-bold">Security Report</h1>
{/* تقييم أمان HTTP */}
<div className="bg-white rounded-lg shadow p-4">
<h2 className="text-lg font-semibold mb-4">
HTTP Security Headers
<span className="ml-2 px-2 py-1 text-sm rounded bg-blue-100">
Grade: {getGrade(securityCheck.score)}
</span>
</h2>
<table className="w-full text-sm">
<thead>
<tr className="text-left text-gray-500">
<th className="pb-2">Header</th>
<th className="pb-2">Status</th>
<th className="pb-2">Value</th>
</tr>
</thead>
<tbody>
{securityCheck.results.map(r => (
<tr key={r.name} className="border-t">
<td className="py-2 font-mono">{r.name}</td>
<td className="py-2">
{r.present ? '✅' : '❌'}
</td>
<td className="py-2 text-xs text-gray-500 truncate max-w-md">
{r.value || '—'}
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* أمان التبعيات */}
<div className="bg-white rounded-lg shadow p-4">
<h2 className="text-lg font-semibold mb-4">Dependency Audit</h2>
<p>Total packages: {deps.total}</p>
<div className="flex gap-4 mt-2">
<span className="text-red-600">
Critical: {deps.vulnerabilities.critical}
</span>
<span className="text-orange-600">
High: {deps.vulnerabilities.high}
</span>
</div>
</div>
{/* فحص متغيرات البيئة */}
<div className="bg-white rounded-lg shadow p-4">
<h2 className="text-lg font-semibold mb-4">Environment Variables</h2>
<p className="text-green-600">✅ No NEXT_PUBLIC_ secrets detected</p>
</div>
</div>
)
}
❓ أسئلة شائعة
next.config.js، استخدم رأس Content-Security-Policy-Report-Only مع report-uri أو report-to لإرسال التقارير إلى Sentry أو نقطة نهاية مخصصة.params؟generateMetadata و layout.tsx و page.tsx. إذا كان مشروعك يحتوي على عدد كبير من الصفحات التي تستخدم params.id، فستحتاج إلى فحصها واحدة تلو الأخرى أثناء الترحيل. نوصي بتشغيل npx @next/codemod@latest async-request-params للترحيل التلقائي أولاً، ثم فحص الحالات غير المغطاة يدويًا.proxy.ts و middleware.ts الأصلي متكافئان وظيفيًا؟proxy.ts يستخدم تكوينًا تصريحيًا (يركز على "ما القواعد")، بينما middleware.ts يستخدم دوال أمرية (تركز على "كيفية التنفيذ"). proxy.ts يغطي 90% من سيناريوهات middleware (المصادقة، إعادة التوجيه، حقن الرؤوس)، ولكن لمنطق إعادة كتابة الطلبات المعقد (مثل إعادة كتابة مسارات i18n)، ستظل بحاجة إلى استخدام middleware.ts (لا يزال مدعومًا في v16).next.config.js باستخدام experimental.turbo.rules. يخطط فريق Vercel لتحقيق التوافق الكامل مع تكوينات Webpack في v16.3.use cache وتخزين fetch السابق؟fetch تلقائي وضمني (افتراضي force-cache)، ولا يمكن التحكم فيه بدقة. use cache هو توجيه تخزين صريح على مستوى المكون يدعم cacheTag (انتهاء الصلاحية حسب الوسم) و cacheLife (سياسات دورة الحياة). مسار الترحيل: (1) يمكن الاحتفاظ بتخزين fetch البسيط؛ (2) السيناريوهات التي تتطلب إبطالًا عند الطلب يجب أن تنتقل إلى use cache + cacheTag؛ (3) الكود الجديد يجب أن يستخدم use cache مباشرة.git log -p -S 'sk_live_')؛ (3) إذا كان السر قد تعرض تحت NEXT_PUBLIC_، افترض أن جميع الزوار السابقين قد يكونون قد وصلوا إليه وقم بتدويره؛ (4) أضف github-secret-scanning و .env* إلى .gitignore لمنع تسرب آخر.📖 ملخص
- رؤوس أمان HTTP (CSP، HSTS، X-Frame-Options، إلخ) تُكوّن في دالة
headersفيnext.config.jsوتعمل كخط دفاع أول ضد XSS و clickjacking - المتغيرات ذات البادئة
NEXT_PUBLIC_مكشوفة لـ JavaScript من جانب العميل؛ لا تستخدمها أبدًا لتخزين المفاتيح..env*.localيجب أن تكون في.gitignore npm auditو Snyk يقومان بفحوصات دورية لثغرات التبعيات ويدمجان فحوصات تدقيق الأمان في CI- أبرز نقاط ترحيل v14→v15:
params/cookies/headersتحولت جميعها إلى غير متزامنة؛useFormStateأُعيد تسميته إلىuseActionState - التغييرات الرئيسية في ترحيل v15→v16:
proxy.tsالتكوين التصريحي يحل محلmiddleware.tsالوظيفي؛ Turbopack يصبح أداة البناء الافتراضية؛ توجيهاتuse cacheتحل محل تخزينfetchالضمني - تعزيز الأمان وترحيل الإصدارات يجب أن يتم على مراحل: أولاً، إصلاح الثغرات عالية الخطورة؛ ثم ترقية الإصدار؛ كل خطوة يجب أن تخضع لاختبار وتحقق شاملين
📝 تمارين
-
تمرين أساسي (⭐): قم بإعداد رؤوس الأمان الستة (CSP، HSTS، X-Frame-Options، X-Content-Type-Options، Referrer-Policy، Permissions-Policy) في
next.config.js، ثم استخدمcurl -Iأو Mozilla Observatory للتحقق من أن التكوين نشط. -
تمرين متقدم (⭐⭐): قم بترحيل مشروع يستخدم Next.js 14 إلى v16: (1) شغّل
npx @next/codemod async-request-paramsلإجراء ترحيل تلقائي؛ (2) أصلح يدويًا أي استدعاءات متزامنة غير مغطاة لـcookies()وheaders()؛ (3) حولmiddleware.tsإلىproxy.ts(حماية مسار المصادقة)؛ (4) أزل تكوينwebpackوتحقق من نجاح بناء Turbopack. -
تحدي (⭐⭐⭐): نفذ خط أنابيب CI/CD آمنًا كاملاً: (1) أضف حاجز
npm auditإلى GitHub Actions (امنع الدمج بمستوى خطورة "high" أو أعلى)؛ (2) ادمج فحوصات Snyk وعلّق تلقائيًا على تقارير الثغرات في طلبات السحب؛ (3) نفذ سكريبت التحقق من رؤوس الأمان بعد البناء وابحث في نواتج البناء عن تسريباتNEXT_PUBLIC_؛ (4) شغّل تدقيق أمان كامل أسبوعيًا تلقائيًا وأنشئ تقرير HTML.