هجرة WordPress إلى Next.js: دليل تقني شامل
ملخص تنفيذي
الهجرة من WordPress إلى Next.js في 2026 توفر مكاسب أداء قابلة للقياس: ينخفض TTFB من 1200 ميلي ثانية إلى 85 ميلي ثانية، يتقلص وزن الصفحة من 3.2 ميجابايت إلى 620 كيلوبايت، وتقفز درجات Lighthouse من 42 إلى 94. تتضمن العملية تصدير المحتوى عبر WP REST API إلى Supabase أو Payload CMS، ونقل الوسائط إلى تخزين الكائنات، وربط كل عنوان URL لإعادة التوجيه 301، والحفاظ على بيانات SEO من Yoast/RankMath في Next.js Metadata API، واستبدال المكونات الإضافية مثل Gravity Forms بـ Server Actions. بالنسبة لمتاجر WooCommerce، يحل Stripe محل مكدس التجارة بالكامل. توقع 4-8 أسابيع لمواقع نموذجية تحتوي على 100-500 صفحة، مع أكبر استثمار وقت في اختبار إعادة التوجيه وإعادة بناء القوالب.
هذا ليس وجهة نظر مثيرة. لقد كنت أبني على WordPress لمدة تزيد عن 12 عاماً. لقد أطلقت مواقع الوكالات، منصات العضوية، متاجر WooCommerce بإيرادات شهرية بستة أرقام، والعديد من أنواع المنشورات المخصصة أكثر مما أستطيع تذكره. كما قمت بنقل مواقع الإنتاج إلى Next.js + Supabase. إليك كل التفصيل التقني -- ما الذي يتوافق بشكل نظيف، وما لا يتوافق، وما يجب التخطيط له.
لن أتظاهر بأن WordPress سيء. إنه ليس كذلك. إنه يدعم 43% من الويب لسبب وجيه. لكن بالنسبة لمشاريع معينة -- المواقع التي تكون فيها الأداء مقياساً تجارياً، والمواقع التي تكون فيها سطح مساحة الأمان مهماً، والمواقع التي تريد أن تمتلك خط أنابيب النشر الخاص بك -- فإن Next.js هو الأداة الأفضل. الهجرة، مع ذلك؟ إنها حقل ألغام إذا لم تخطط لها.
يغطي هذا الدليل العملية الدقيقة التي أستخدمها، مع رمز حقيقي، مشاكل حقيقية، وتقييمات صادقة لما ستكسبه وما ستفقده.
جدول المحتويات
- نقل المحتوى: WP REST API إلى Supabase أو Payload CMS
- نقل الوسائط: من wp-content إلى Supabase Storage
- هيكل عنوان URL: ربط كل عنوان URL قديم
- هجرة SEO: بيانات Yoast و RankMath إلى Next.js Metadata
- النماذج: Gravity Forms إلى Server Actions
- WooCommerce إلى Stripe
- المراقبة بعد الهجرة
- متى تبقى على WordPress
- معايير الأداء: قبل وبعد
- الأسئلة الشائعة

نقل المحتوى: WP REST API إلى Supabase أو Payload CMS
تبدأ كل هجرة WordPress من هنا. لديك منشورات، صفحات، أنواع منشورات مخصصة، حقول ACF، تصنيفات -- سنوات من المحتوى يحتاج إلى الوصول إلى مكان آمن.
لديك خياران قويان لوجهة هذا المحتوى:
- Supabase -- إذا كنت تريد قاعدة بيانات تتحكم فيها بالكامل، مع أمان على مستوى الصفوف وواجهة برمجية REST/GraphQL مدمجة
- Payload CMS -- إذا كان عميلك بحاجة إلى تجربة تعديل مرئية تشعر بالألفة بعد WordPress
بالنسبة لمشاريع تطوير CMS بدون رأس، نقيم هذا على أساس كل عميل. يفوز Payload عندما يحتاج المحررون إلى الخدمة الذاتية. يفوز Supabase عندما يكون المطورون هم مديري المحتوى الأساسيين أو عندما تحتاج البيانات لأكثر من مجرد موقع ويب.
ما هيكل المحتوى الذي يجب الحفاظ عليه عند الهجرة من WordPress إلى Next.js؟
احفظ بيانات وصفية المنشور والتصنيفات والحقول المخصصة وشرطات عنوان URL أثناء الهجرة. تحتوي منشورات WordPress على سنوات من البيانات المنظمة: الفئات والعلامات وحقول ACF والصور المميزة وتواريخ النشر. صدر كل هذا عبر WP REST API مع معامل _embed للحصول على عناوين URL للوسائط في طلب واحد. قم بتخزين نسخ HTML و Markdown من المحتوى -- HTML كبديل، Markdown لعرض MDX. قم بربط أنواع المنشورات المخصصة بجداول قاعدة بيانات مكافئة أو مجموعات CMS في نظامك الجديد.
سكريبت التصدير
إليك سكريبت Node.js الذي أستخدمه لسحب المحتوى من WP REST API وتنظيفه وإدراجه في Supabase. يتعامل هذا مع المنشورات، لكنك ستكرر النمط لـ الصفحات والأنواع المخصصة (فقط غير نقطة النهاية).
import { createClient } from '@supabase/supabase-js';
import TurndownService from 'turndown';
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_KEY
);
const turndown = new TurndownService({
headingStyle: 'atx',
codeBlockStyle: 'fenced',
});
const WP_API = 'https://yoursite.com/wp-json/wp/v2';
async function fetchAllPosts() {
let page = 1;
let allPosts = [];
let hasMore = true;
while (hasMore) {
const res = await fetch(
`${WP_API}/posts?per_page=100&page=${page}&_embed`
);
if (!res.ok) break;
const posts = await res.json();
allPosts = allPosts.concat(posts);
const totalPages = parseInt(res.headers.get('X-WP-TotalPages'));
hasMore = page < totalPages;
page++;
}
return allPosts;
}
async function migrateContent() {
const posts = await fetchAllPosts();
console.log(`Fetched ${posts.length} posts from WordPress`);
const transformed = posts.map((post) => ({
wp_id: post.id,
title: post.title.rendered,
slug: post.slug,
content_html: post.content.rendered,
content_markdown: turndown.turndown(post.content.rendered),
excerpt: post.excerpt.rendered.replace(/<[^>]*>/g, '').trim(),
published_at: post.date,
status: post.status,
featured_image:
post._embedded?.['wp:featuredmedia']?.[0]?.source_url || null,
categories:
post._embedded?.['wp:term']?.[0]?.map((t) => t.name) || [],
tags:
post._embedded?.['wp:term']?.[1]?.map((t) => t.name) || [],
}));
const { data, error } = await supabase
.from('posts')
.upsert(transformed, { onConflict: 'wp_id' });
if (error) {
console.error('Migration failed:', error);
} else {
console.log(`Migrated ${transformed.length} posts to Supabase`);
}
}
migrateContent();
بعض الأشياء التي تعلمتها بالطريقة الصعبة:
- استخدم دائماً
_embedفي استدعاءات WP REST API. بدونها، تحصل على معرفات الوسائط بدلاً من عناوين URL، مما يعني طلبات N+1 لحل الصور المميزة. - يحول Turndown HTML إلى Markdown -- هذا حرج إذا كنت تخطط للعرض باستخدام MDX لاحقاً. احتفظ بـ HTML الأصلي أيضاً، كبديل.
- الاختصارات لا تنجو. يقدم WordPress بعض الاختصارات عبر REST API، لكن الكثير (خاصة من المكونات الإضافية مثل WPBakery أو Elementor) تأتي كنص قوس خام. تحتاج إلى استراتيجية رسم خرائط للاختصارات إلى المكونات. أحتفظ بجدول بيانات.
- ACF / الحقول المخصصة: إذا كنت تستخدم ACF، ستحتاج إلى تفعيل المكون الإضافي ACF to REST API، ثم تظهر الحقول المخصصة في خاصية
acfلكائن كل منشور.
مخطط جدول Supabase
CREATE TABLE posts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
wp_id INTEGER UNIQUE,
title TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
content_html TEXT,
content_markdown TEXT,
excerpt TEXT,
published_at TIMESTAMPTZ,
status TEXT DEFAULT 'publish',
featured_image TEXT,
categories TEXT[],
tags TEXT[],
seo_title TEXT,
seo_description TEXT,
og_image TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
لاحظ أنني أدرجت أعمدة seo_title و seo_description و og_image. ستحتاج إلى تلك في قسم هجرة SEO أدناه.
نقل الوسائط: من wp-content إلى Supabase Storage
هذا هو الجزء الذي يتجاهله معظم الأدلة، وهذا هو الجزء الذي يستغرق وقتاً أطول. يمكن أن يحتوي موقع WordPress الذي يبلغ عمره 12 سنة بسهولة على أكثر من 10000 ملف في wp-content/uploads/.
النهج:
- قم بتنزيل دليل
wp-content/uploads/بالكامل - التحميل إلى Supabase Storage (أو Cloudflare R2 أو S3)
- أعد كتابة كل عنوان URL للوسائط في المحتوى الخاص بك
كيف أنقل ملفات وسائط WordPress إلى تخزين الكائنات الحديث؟
قم بتنزيل دليل wp-content/uploads/ بالكامل، والتحميل إلى Supabase Storage أو Cloudflare R2، ثم أعد كتابة جميع عناوين URL للوسائط في المحتوى. استخدم سكريبت لجلب كل عنوان URL للصورة من محتوى WordPress، والتحميل إلى تخزين الكائنات مع الحفاظ على هيكل الدليل (2024/03/image.jpg)، ثم قم بتشغيل ممر ثاني لاستبدال عناوين URL القديمة بعناوين URL تخزين جديدة. قم بإعداد إعادة توجيه بطاقات البدل للمواقع الخارجية التي تربط مباشرة بعناوين URL الصور القديمة الخاصة بك.
سكريبت التنزيل والتحميل
import { createClient } from '@supabase/supabase-js';
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_KEY
);
const BUCKET = 'media';
async function migrateMedia(posts) {
const urlRegex =
/https?:\/\/yoursite\.com\/wp-content\/uploads\/[^\s"')]+/g;
for (const post of posts) {
const urls = post.content_html.match(urlRegex) || [];
for (const url of urls) {
try {
const res = await fetch(url);
const buffer = Buffer.from(await res.arrayBuffer());
// Preserve directory structure: 2024/03/image.jpg
const storagePath = url.replace(
/https?:\/\/yoursite\.com\/wp-content\/uploads\//,
''
);
const { error } = await supabase.storage
.from(BUCKET)
.upload(storagePath, buffer, {
contentType: res.headers.get('content-type'),
upsert: true,
});
if (error) console.error(`Failed: ${storagePath}`, error);
else console.log(`Uploaded: ${storagePath}`);
} catch (e) {
console.error(`Skipped: ${url}`, e.message);
}
}
}
}
async function rewriteUrls() {
const { data: posts } = await supabase.from('posts').select('*');
const supabaseBase = `${process.env.SUPABASE_URL}/storage/v1/object/public/${BUCKET}`;
for (const post of posts) {
const updated = post.content_html.replace(
/https?:\/\/yoursite\.com\/wp-content\/uploads\//g,
`${supabaseBase}/`
);
await supabase
.from('posts')
.update({
content_html: updated,
content_markdown: turndown.turndown(updated),
})
.eq('id', post.id);
}
}
كسب أداء الصورة
هنا حيث يدفع الهجرة حقاً. يقدم WordPress التحميلات الأصلية -- غالباً PNGs بحجم 3000×2000 بكسل التي حمّلها شخص ما من كاميرا DSLR الخاصة به. حتى مع مكون إضافي مثل ShortPixel، تظل تقدم الصور عبر PHP.
يقوم مكون Next.js <Image> مع next/image بمفاوضة تنسيق تلقائي (WebP/AVIF)، وحجم استجابة، والتحميل الكسول. الأرقام من آخر هجرة لنا:
| المقياس | WordPress | Next.js + Image Component |
|---|---|---|
| وزن صورة الصفحة المتوسط | 2.1 ميجابايت | 380 كيلوبايت |
| طلبات الصور | 12 لكل صفحة | 6 لكل صفحة (تحميل كسول) |
| التنسيق | JPEG/PNG | WebP (AVIF حيث يتم دعمه) |
| تحول التخطيط التراكمي | 0.18 | 0.02 |
هذا ليس خطأ في الكتابة. انخفض متوسط حمل الصورة من 2.1 ميجابايت إلى 380 كيلوبايت. وهذا كان بدون إعادة تحميل ملفات محسنة -- فقط اترك next/image يفعل شيئه.
import Image from 'next/image';
export function PostImage({ src, alt }: { src: string; alt: string }) {
return (
<Image
src={src}
alt={alt}
width={800}
height={450}
sizes="(max-width: 768px) 100vw, 800px"
quality={80}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..." // generate at build time
/>
);
}
هيكل عنوان URL: ربط كل عنوان URL قديم
هنا حيث تموت الهجرات. تعني خطأ واحد محمول 404 لصفحة فهرستها Google لسنوات. أتعامل مع رسم خرائط عنوان URL بنفس جدية هجرة قاعدة البيانات -- اختبرها، تحقق منها، ثم تحقق منها مرة أخرى.
ما هي الطريقة الصحيحة للتعامل مع إعادة التوجيه أثناء هجرة WordPress؟
صدر كل عنوان URL منشور من WordPress، وتحقق مقابل عناوين URL المفهرسة في Google Search Console، ثم قم بتنفيذ إعادات التوجيه 301 لجميعها. استعلم جدول wp_posts الخاص بك لجميع عناوين URL المنشورة، والصدر عناوين URL Google Search Console المفهرسة، وقم بإنشاء خريطة إعادة التوجيه. استخدم إعادات التوجيه next.config.js لأقل من 50 عنوان URL، ملف JSON لـ 50-1024 عناوين URL، أو برنامج وسيط للمواقع التي تتجاوز حد إعادة التوجيه 1024 من Vercel. قم بتضمين إعادات التوجيه بطاقات البدل لصفحات الفئة والترقيم وعناوين URL wp-content/uploads.
عملية الرسم
أولاً، صدر كل عنوان URL من WordPress. أسحب مباشرة من قاعدة البيانات:
SELECT
CONCAT('/', post_name, '/') AS old_url,
post_type,
post_status
FROM wp_posts
WHERE post_status = 'publish'
AND post_type IN ('post', 'page', 'product')
ORDER BY post_type, post_name;
ثم تحقق مقابل عناوين URL Google Search Console المفهرسة. غالباً ما يُظهر Google Search Console عناوين URL لا توجد في قاعدة البيانات -- صفحات فئة قديمة، عناوين URL ترقيم، صفحات مرفقة. تحتاج إلى إعادات توجيه لجميعها.
إعادات التوجيه next.config.js
بالنسبة للمواقع التي تحتوي على أقل من 50 إعادة توجيه، قم بترجمتها:
// next.config.js
module.exports = {
async redirects() {
return [
{
source: '/2019/03/old-post-slug/',
destination: '/blog/old-post-slug',
permanent: true,
},
{
source: '/category/:slug',
destination: '/blog/category/:slug',
permanent: true,
},
{
source: '/product/:slug',
destination: '/shop/:slug',
permanent: true,
},
];
},
};
لـ 200+ إعادات التوجيه: استخدم ملف JSON
بمجرد تجاوز بضع مئات، فإن الحفاظ على إعادات التوجيه المدمجة محموم. أستخدم ملف JSON:
// redirects.json
[
{
"source": "/2018/01/my-old-post/",
"destination": "/blog/my-old-post",
"permanent": true
},
{
"source": "/about-us/",
"destination": "/about",
"permanent": true
},
{
"source": "/wp-content/uploads/:path*",
"destination": "https://yourbucket.supabase.co/storage/v1/object/public/media/:path*",
"permanent": true
}
]
// next.config.js
const redirectsList = require('./redirects.json');
module.exports = {
async redirects() {
return redirectsList;
},
};
إعادة التوجيه بطاقة البدل لـ wp-content/uploads حرجة. ستكون هناك مواقع خارجية تربط مباشرة بصورك. لا تخسر تلك الروابط الخلفية.
مهم: Vercel لديه حد من 1024 إعادة توجيه في next.config.js. بالنسبة للمواقع التي تحتوي على أكثر من ذلك، استخدم برنامج وسيط:
// middleware.ts
import { NextResponse } from 'next/server';
import redirects from './redirects.json';
const redirectMap = new Map(
redirects.map((r) => [r.source, r])
);
export function middleware(request) {
const redirect = redirectMap.get(request.nextUrl.pathname);
if (redirect) {
return NextResponse.redirect(
new URL(redirect.destination, request.url),
redirect.permanent ? 308 : 307
);
}
}

هجرة SEO: بيانات Yoast و RankMath إلى Next.js Metadata
إذا كنت تستخدم Yoast أو RankMath، فلديك سنوات من عناوين Meta المخصصة والأوصاف وبيانات Open Graph المخزنة في جدول wp_postmeta. لا تفقدها.
كيف أحتفظ ببيانات Yoast SEO عند الهجرة إلى Next.js؟
صدر عناوين Yoast الفوقية والأوصاف وصور Open Graph من wp_postmeta، وخزنها في قاعدة البيانات الجديدة، ثم قم بعرضها باستخدام Next.js Metadata API. استعلم wp_postmeta لحقول _yoast_wpseo_title و _yoast_wpseo_metadesc و _yoast_wpseo_opengraph-image. استورد هذه البيانات في أعمدة SEO مخصصة في جدول المنشورات. استخدم generateMetadata في Next.js App Router لعرض هذه البيانات كعلامات meta مناسبة وترميز Open Graph.
تصدير بيانات SEO
SELECT
p.post_name AS slug,
MAX(CASE WHEN pm.meta_key = '_yoast_wpseo_title' THEN pm.meta_value END) AS seo_title,
MAX(CASE WHEN pm.meta_key = '_yoast_wpseo_metadesc' THEN pm.meta_value END) AS seo_description,
MAX(CASE WHEN pm.meta_key = '_yoast_wpseo_opengraph-image' THEN pm.meta_value END) AS og_image
FROM wp_posts p
JOIN wp_postmeta pm ON p.ID = pm.post_id
WHERE p.post_status = 'publish'
GROUP BY p.ID, p.post_name;
بالنسبة لـ RankMath، استبدل مفاتيح البيانات الوصفية: rank_math_title و rank_math_description و rank_math_facebook_image.
استورد هذه البيانات في جدول Supabase posts في أعمدة SEO التي حددناها سابقاً.
Next.js Metadata API
مع App Router، البيانات الوصفية هي مواطن من الدرجة الأولى:
// app/blog/[slug]/page.tsx
import { supabase } from '@/lib/supabase';
import { Metadata } from 'next';
export async function generateMetadata(
{ params }: { params: { slug: string } }
): Promise<Metadata> {
const { data: post } = await supabase
.from('posts')
.select('title, seo_title, seo_description, og_image')
.eq('slug', params.slug)
.single();
return {
title: post.seo_title || post.title,
description: post.seo_description,
openGraph: {
title: post.seo_title || post.title,
description: post.seo_description,
images: post.og_image ? [{ url: post.og_image }] : [],
},
};
}
ترميز المخطط كمكونات JSON-LD للخادم
يولد مكونات WordPress الإضافية ترميز المخطط تلقائياً. في Next.js، تبني نفسك -- مما يعطيك في الواقع المزيد من التحكم:
// components/ArticleSchema.tsx
export function ArticleSchema({ post }) {
const schema = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
datePublished: post.published_at,
dateModified: post.updated_at || post.published_at,
author: {
'@type': 'Organization',
name: 'Your Company',
},
image: post.og_image,
description: post.seo_description,
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
خرائط الموقع الديناميكية
// app/sitemap.ts
import { supabase } from '@/lib/supabase';
export default async function sitemap() {
const { data: posts } = await supabase
.from('posts')
.select('slug, published_at')
.eq('status', 'publish');
return posts.map((post) => ({
url: `https://yoursite.com/blog/${post.slug}`,
lastModified: post.published_at,
changeFrequency: 'monthly',
priority: 0.8,
}));
}
يتم إنشاء هذا في وقت البناء للمواقع الثابتة أو عند الطلب للمواقع الديناميكية. لا توجد مكونات إضافية، لا توجد ملفات قوالب XML، لا توجد مشاكل التخزين المؤقت.
النماذج: Gravity Forms إلى Server Actions
Gravity Forms هو أحد أفضل مكونات WordPress الإضافية على الإطلاق. إنه أيضاً $259/سنة لترخيص Elite، وكل نموذج يحمل 200KB+ من JavaScript.
هنا الاستبدال. إنه حوالي 20 سطر من الرمز لكل نموذج.
تصدير الإدخالات الموجودة
أولاً، صدر إدخالات Gravity Forms الخاصة بك بصيغة CSV من لوحة تحكم WordPress. قم بتخزينها في Supabase للسجلات التاريخية إذا لزم الأمر.
Server Action Contact Form
// app/contact/page.tsx
export default function ContactPage() {
async function submitForm(formData: FormData) {
'use server';
const { createClient } = await import('@supabase/supabase-js');
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!
);
const entry = {
name: formData.get('name') as string,
email: formData.get('email') as string,
message: formData.get('message') as string,
submitted_at: new Date().toISOString(),
};
// Validate
if (!entry.name || !entry.email || !entry.message) {
throw new Error('All fields required');
}
await supabase.from('form_submissions').insert(entry);
// Optional: send notification email via Resend
await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'forms@yoursite.com',
to: 'team@yoursite.com',
subject: `New contact: ${entry.name}`,
html: `<p>${entry.message}</p><p>From: ${entry.email}</p>`,
}),
});
}
return (
<form action={submitForm}>
<input name="name" type="text" required placeholder="Name" />
<input name="email" type="email" required placeholder="Email" />
<textarea name="message" required placeholder="Message" />
<button type="submit">Send</button>
</form>
);
}
لا مكون إضافي. لا توجد حمولة JavaScript للنموذج نفسه (إنه نموذج HTML أصلي مع إجراء خادم). التحسين التدريجي -- يعمل بدون تفعيل JavaScript.
بالنسبة للنماذج الأكثر تعقيداً (متعددة الخطوات، تحميلات الملفات، الحقول الشرطية)، نستخدم React Hook Form على جانب العميل مع نفس نمط إجراء الخادم. الفكرة الرئيسية: لا تحتاج إلى مكون إضافي للنموذج عندما يكون لديك قاعدة بيانات وواجهة برمجية.
WooCommerce إلى Stripe
هذا هو الجزء الأصعب من أي هجرة WordPress. WooCommerce ليست مجرد مكون إضافي -- إنها منصة التجارة الإلكترونية بالكامل مع منتجات وتباينات وجرد وطلبات واشتراكات وقسائم وقواعد ضرائب وشحن. أنت لا تنقل ميزة. أنت تستبدل منصة.
كيف أنقل منتجات WooCommerce إلى Stripe؟
صدر المنتجات عبر WooCommerce REST API أو CSV، ثم قم بإنشاء منتجات مطابقة في Stripe Products API مع الأسعار. بالنسبة للمواقع التي تحتوي على أقل من 500 منتج، ادفع مباشرة إلى Stripe باستخدام API الخاص بهم: أنشئ منتجاً باسم وصف وصور، ثم أنشئ كائن سعر مرتبط بهذا المنتج. قم بتخزين معرف منتج WooCommerce في بيانات Stripe metadata للرجوع إليها. استخدم جلسات Stripe Checkout للمعالجة وخطافات الويب لتتبع الطلبات في قاعدة البيانات.
هجرة المنتج
صدر المنتجات من WooCommerce عبر CSV أو REST API. لديك خياران لوجهة:
| النهج | الأفضل ل | المقايضات |
|---|---|---|
| جدول منتجات Supabase | واجهات المتجر المخصصة والتصفية المعقدة | تدير منطق الجرد |
| Stripe Products API | الكتالوجات البسيطة وشركات البرامج المشتركة | Stripe يدير التسعير، أنت تدير العرض |
بالنسبة لمعظم المواقع التي تحتوي على أقل من 500 منتج، أدفع مباشرة إلى منتجات Stripe:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
async function migrateProducts(wooProducts) {
for (const product of wooProducts) {
const stripeProduct = await stripe.products.create({
name: product.name,
description: product.short_description,
images: [product.images[0]?.src].filter(Boolean),
metadata: {
woo_id: String(product.id),
slug: product.slug,
sku: product.sku,
},
});
await stripe.prices.create({
product: stripeProduct.id,
unit_amount: Math.round(parseFloat(product.price) * 100),
currency: 'usd',
});
console.log(`Created: ${product.name} → ${stripeProduct.id}`);
}
}
الدفع باستخدام جلسات Stripe Checkout
// app/api/checkout/route.ts
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(request: Request) {
const { priceId, quantity = 1 } = await request.json();
const session = await stripe.checkout.sessions.create({
mode: 'payment',
payment_method_types: ['card'],
line_items: [{ price: priceId, quantity }],
success_url: `${process.env.NEXT_PUBLIC_URL}/order/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/shop`,
});
return NextResponse.json({ url: session.url });
}
الاشتراكات
إذا كنت على WooCommerce Subscriptions ($239/سنة)، تبديل إلى Stripe Billing. تغيير mode: 'payment' إلى mode: 'subscription' وتأكد من أن الأسعار الخاصة بك لديها recurring محددة. هذا كل شيء. يتعامل Stripe مع فترات التجربة والمقسمة ومنطق التحصيل.
تتبع الطلبات عبر Webhooks
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import Stripe from 'stripe';
import { supabase } from '@/lib/supabase';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(request: Request) {
const body = await request.text();
const sig = headers().get('stripe-signature')!;
const event = stripe.webhooks.constructEvent(
body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
);
if (event.type === 'checkout.session.completed') {
const session = event.data.object as Stripe.Checkout.Session;
await supabase.from('orders').insert({
stripe_session_id: session.id,
customer_email: session.customer_details?.email,
amount_total: session.amount_total,
status: 'completed',
});
}
return new Response('OK', { status: 200 });
}
رسوم معاملات Stripe هي 2.9% + 0.30 دولار لكل معاملة. قارن ذلك مع WooCommerce حيث تدفع للاستضافة ($30-100/شهر للـ WordPress المدار)، مكون Subscriptions الإضافي ($239/سنة)، مكون بوابة الدفع الإضافي، وربما بعض الإضافات الأخرى. تعمل الرياضيات بسرعة كبيرة.
بالنسبة لهجرات التجارة الإلكترونية المعقدة، نقدم هذا كجزء من خدمات تطوير Next.js -- إنها من أكثر الطلبات التي نتلقاها.
المراقبة بعد الهجرة
الإطلاق ليس النهاية. الأسبوعان الأولان بعد الهجرة حرجان.
Google Search Console
- قم بتقديم خريطة الموقع الجديدة على الفور
- استخدم أداة فحص عنوان URL لطلب فهرسة أعلى 20 صفحة
- راقب تقرير التغطية يومياً للأسبوع الأول -- راقب الارتفاعات في 404s
- تحقق من تقرير "فهرسة الصفحة" لأي صفحات عالقة في "تم اكتشافها -- حالياً لم تتم فهرستها"
مقارنة التحليلات
قم بإعداد لوحة معلومات تقارن أسبوع تلو الآخر:
- إجمالي الجلسات
- حركة البحث العضوي على وجه التحديد
- معدل الارتداد حسب الصفحة
- معدل التحويل (تقديم النموذج والمشتريات)
يعتبر انخفاض حركة المرور الصغيرة في الأسبوع الأول أمراً طبيعياً. إذا لم تتعافَ بحلول الأسبوع الثالث، فقد حدث خطأ ما مع إعادات التوجيه أو الفهرسة.
تدقيقات Lighthouse
قم بتشغيل Lighthouse على كل قالب رئيسي (الصفحة الرئيسية ومنشور المدونة وصفحة المنتج وصفحة الاتصال). الهدف:
- الأداء: 90+
- إمكانية الوصول: 95+
- أفضل الممارسات: 95+
- SEO: 100
في آخر هجرة لنا -- موقع محتوى به 400 صفحة -- انتقلنا من متوسط درجة أداء Lighthouse البالغة 38 على WordPress إلى 96 على Next.js المنشور على Vercel. هذا ليس عينة منتقاة. هذا هو المتوسط.
متى تبقى على WordPress
هنا حيث أفقد بعضكم، لكنها الجزء الأكثر أهمية في هذا الدليل.
لا تهاجر إذا:
- لديك مدونة بسيطة أو موقع كروت تحت 20 صفحة
- فريقك غير فني ويعتمد على لوحة تحكم WordPress للتحديثات اليومية
- نتائج Lighthouse الخاصة بك بالفعل 70+ وليس لديك احتياجات تجارية ذات أداء حرجة
- ليس لديك مشاكل أمان والاستضافة الخاصة بك مستقرة
- التكاليف الإجمالية للمكون الإضافي تقل عن $200/سنة
- ليس لديك مطور (أو ميزانية لواحد) لصيانة موقع Next.js
WordPress مع مضيف جيد (Cloudways و Kinsta) وموضوع قوي والحد الأدنى من المكونات الإضافية بخير. في الواقع، إنه أكثر من بخير -- إنه تم اختباره في المعركة وموثق جيداً وفهمه من قبل ملايين المطورين.
تحقق الهجرة عندما:
- ترتبط الأداء مباشرة بالإيرادات (الحفظ الإلكتروني ومواقع تسويق SaaS)
- تنفق 500 دولار+/شهر على الاستضافة المدارة ومكونات إضافية الأمان
- فريق التطوير الخاص بك يكتب بالفعل React
- تحتاج إلى خط أنابيب نشر مع إنشاءات معاينة وبيئات التدريج والتراجع
- مساحة سطح الأمان هي مصدر قلق حقيقي (الحكومة والرعاية الصحية والمالية)
أقول هذا لأن الثقة أهم من البيع. إذا كنت غير متأكد مما إذا كانت الهجرة تستحق العناء، تواصل معنا وسنعطيك تقييماً صادقاً.
معايير الأداء: قبل وبعد
من آخر خمس هجرات لنا في 2024-2025:
| المقياس | WordPress (المتوسط) | Next.js (المتوسط) | التغيير |
|---|---|---|---|
| TTFB | 1200ms | 85ms | أسرع 14 مرة |
| LCP | 3.8s | 0.9s | أسرع 4.2 مرات |
| إجمالي وزن الصفحة | 3.2 ميجابايت | 620 كيلوبايت | أخف 5 مرات |
| الطلبات لكل صفحة | 47 | 11 | 77% طلبات أقل |
| أداء Lighthouse | 42 | 94 | +52 نقطة |
| تكلفة الاستضافة الشهرية | $75 | $20 (Vercel Pro) | توفير 73% |
| معدل النجاح Core Web Vitals | 31% من الصفحات | 100% من الصفحات | ✓ |
هذه أرقام حقيقية من مواقع الإنتاج. كانت مواقع WordPress تعمل على استضافة مدارة (WP Engine و Kinsta)، تخزين مؤقت محسّن، ومكونات إضافية لتحسين الصور. لم تكن مهملة -- كانت مواقع تم الحفاظ عليها ببساطة قد وصلت إلى حد ما يمكن لـ WordPress توفيره.
إذا كنت مهتماً بما هو ممكن مع الأطر الحديثة، فتحقق أيضاً من قدرات تطوير Astro -- بالنسبة للمواقع التي تحتوي على محتوى ثقيل مع التفاعل الحد الأدنى، يمكن لـ Astro تسليم حمولات أصغر حتى من Next.js.
الأسئلة الشائعة
كم من الوقت تستغرق هجرة WordPress إلى Next.js؟
بالنسبة لموقع نموذجي به 100-500 صفحة، توقع 4-8 أسابيع من وقت التطوير. يمكن إنجاز مواقع كروت البسيطة في 2-3 أسابيع. قد تستغرق متاجر WooCommerce المعقدة التي تحتوي على آلاف المنتجات 10-12 أسبوعاً. الهجرة نفسها سريعة -- إعادة بناء قوالب الواجهة الأمامية واختبار كل إعادة توجيه هي التي تستغرق وقتاً.
هل سأفقد ترتيب SEO عند الهجرة من WordPress إلى Next.js؟
ليس إذا تعاملت مع إعادات التوجيه والبيانات الوصفية بشكل صحيح. القطع الحرجة هي: إعادات التوجيه 301 لكل عنوان URL قديم، والهجرة بجميع عناوين Yoast/RankMath الوصفية والأوصاف، والحفاظ على هيكل الخريطة، وتقديم خريطة الموقع الجديدة إلى Google Search Console على الفور. رأينا مواقع تتعافى إلى حركة مرور ما قبل الهجرة في غضون 1-2 أسبوع، مع نمو عضوي كبير بحلول الشهر الثالث بسبب Core Web Vitals المحسنة.
هل يمكنني استخدام WordPress كـ headless CMS مع Next.js؟
نعم، وهو نهج شائع. تحتفظ بـ WordPress كـ backend المحتوى، باستخدام WP REST API أو WPGraphQL، و Next.js كالواجهة الأمامية. يحافظ هذا على تجربة التعديل المألوفة مع الحصول على أداء Next.js. الجانب السلبي هو أنك لا تزال تحتفظ بتثبيت WordPress مع الأمان والتحديثات الخاصة به. نوصي عموماً بـ Payload CMS أو Sanity للمشاريع الجديدة ما لم يكون الفريق منغمساً بعمق في مسارات عمل WordPress.
كم تكلفة هجرة WordPress إلى Next.js؟
افعلها بنفسك مع وقت المطور: مجاني في الأدوات، لكن ميزانية 80-200 ساعة من وقت التطوير. تكلفة الوكالة: عادة $10000-$50000 حسب تعقيد الموقع وعدد الصفحات والميزات التجارة الإلكترونية والوظائف المخصصة. تحقق من صفحة التسعير للحصول على تفاصيل محددة حول الحزم الخاصة بنا. عادة ما يأتي العائد من انخفاض تكاليف الاستضافة (توفير $50-100/شهر)، والقضاء على رسوم ترخيص المكون الإضافي، ومعدلات التحويل المتزايدة من تحسين الأداء.
ماذا يحدث لمكونات WordPress الإضافية بعد الهجرة؟
كل مكون إضافي يحتاج إلى معادل Next.js. نموذج الاتصال 7 أو Gravity Forms يصبح إجراء خادم. Yoast SEO يصبح Next.js Metadata API. WooCommerce يصبح Stripe. Google Analytics يبقى نفسه (فقط انقل مقتطف التتبع). بعض المكونات الإضافية مثل Wordfence تصبح غير ضرورية نظراً لعدم وجود WordPress للهجوم. قم بإنشاء جرد كامل لمكونات الإضافة قبل البدء -- أي مكون إضافي بدون استراتيجية استبدال واضحة هو خطر.
هل يجب أن أهاجر إلى Next.js أو Astro من WordPress؟
يعتمد على احتياجات التفاعل الخاصة بك. Next.js أفضل للمواقع التي تحتوي على ميزات ديناميكية -- المصادقة وحفظ المستخدم والتجارة الإلكترونية والرسوم البيانية والبيانات في الوقت الفعلي. Astro أفضل للمواقع الغنية بالمحتوى التي تكون في الغالب ثابتة -- المدونات والتوثيق ومواقع التسويق. Astro تشحن صفر JavaScript بشكل افتراضي، مما يعني حتى حمولات صفحات أصغر. نعمل مع كليهما -- انظر تطوير Astro و تطوير Next.js للحصول على التفاصيل.
هل يمكنني نقل اشتراكات WooCommerce إلى Stripe؟
نعم، لكنها تتطلب معالجة حذرة للمشتركين النشطين. ستحتاج إلى إنشاء عملاء واشتراكات في Stripe، ثم إبلاغ العملاء بتغيير الفواتير. يتعامل Stripe Billing مع فترات التجربة والمقسمة ومنطق إعادة المحاولة الفاشل والتدفقات الملغاة. الهجرة نفسها سكريبت لمرة واحدة، لكن اختبارها مقابل سيناريوهات الاشتراك الحقيقية هو حيث يذهب الوقت. ميزانية وقت إضافية إذا كان لديك أكثر من 100 مشترك نشط.
ما أفضل استضافة لـ Next.js بعد الهجرة من WordPress؟
Vercel هو الخيار الافتراضي -- تم بناؤه بواسطة الفريق الذي يصنع Next.js، والطبقة المجانية تتعامل مع معظم مواقع التسويق. Vercel Pro هو $20/شهر للفرق. البدائل تشمل Netlify و Cloudflare Pages (ممتازة لأداء الحافة) والاستضافة الذاتية مع Docker على VPS إذا كنت تريد تحكماً كاملاً. كل هذه أرخص بكثير من استضافة WordPress المدارة لمستويات حركة مرور مكافئة.