Migration WordPress vers Next.js : Guide Technique Complet
TL;DR
La migration de WordPress vers Next.js en 2026 offre des gains de performance mesurables : le TTFB moyen passe de 1 200 ms à 85 ms, le poids des pages diminue de 3,2 MB à 620 KB, et les scores Lighthouse passent de 42 à 94. Le processus implique d'exporter le contenu via l'API WP REST vers Supabase ou Payload CMS, de migrer les médias vers un stockage d'objets, de mapper chaque URL pour les redirections 301, de préserver les données SEO de Yoast/RankMath dans l'API Metadata de Next.js, et de remplacer les plugins comme Gravity Forms par des Server Actions. Pour les sites WooCommerce, Stripe remplace la pile e-commerce entière. Attendez-vous à 4-8 semaines pour les sites typiques avec 100-500 pages, l'investissement en temps le plus important étant le test des redirections et la reconstruction des modèles.
Ce n'est pas un avis tendancieux. Je développe sur WordPress depuis plus de 12 ans. J'ai lancé des sites d'agence, des plateformes d'adhésion, des boutiques WooCommerce avec des revenus mensuels à six chiffres, et plus de types de publications personnalisés que je ne souhaite m'en souvenir. J'ai également migré des sites de production vers Next.js + Supabase. Voici tous les détails techniques -- ce qui se transpose proprement, ce qui ne le fait pas, et ce qu'il faut planifier.
Je ne vais pas prétendre que WordPress est mauvais. Ce n'est pas le cas. Il alimente 43 % du web pour une bonne raison. Mais pour certains projets -- les sites où la performance est une métrique commerciale, où la surface d'attaque en matière de sécurité est importante, où vous voulez posséder votre pipeline de déploiement -- Next.js est le meilleur outil. La migration, cependant ? C'est un champ de mines si vous ne la planifiez pas.
Ce guide couvre le processus exact que j'utilise, avec du vrai code, de vrais pièges, et des évaluations honnêtes de ce que vous gagnerez et ce que vous perdrez.
Table des matières
- Migration de contenu : API WP REST vers Supabase ou Payload CMS
- Migration des médias : de wp-content vers Supabase Storage
- Structure des URL : mapper chaque ancienne URL
- Migration SEO : données Yoast et RankMath vers les métadonnées Next.js
- Formulaires : Gravity Forms vers Server Actions
- WooCommerce vers Stripe
- Surveillance après la migration
- Quand rester sur WordPress
- Benchmarks de performance : avant et après
- FAQ

Migration de contenu : API WP REST vers Supabase ou Payload CMS
Chaque migration WordPress commence ici. Vous avez des publications, des pages, des types de publications personnalisés, des champs ACF, des taxonomies -- des années de contenu qui doit être placées quelque part en sécurité.
Vous avez deux bonnes options pour l'endroit où ce contenu va :
- Supabase -- si vous voulez une base de données que vous contrôlez entièrement, avec une sécurité au niveau des lignes et une API REST/GraphQL prête à l'emploi
- Payload CMS -- si votre client a besoin d'une expérience d'édition visuelle qui semble familière après WordPress
Pour nos projets de développement de CMS découplé, nous évaluons cela au cas par cas. Payload gagne lorsque les éditeurs ont besoin de libre-service. Supabase gagne lorsque les développeurs sont les principaux gestionnaires de contenu ou lorsque vous avez besoin des données pour plus qu'un simple site web.
Quelle structure de contenu dois-je préserver lors de la migration de WordPress vers Next.js ?
Préservez les métadonnées de publication, les taxonomies, les champs personnalisés et les slugs d'URL lors de la migration. Vos publications WordPress contiennent des années de données structurées : catégories, étiquettes, champs ACF, images en vedette et dates de publication. Exportez tout cela via l'API WP REST avec le paramètre _embed pour obtenir les URL des médias dans une seule requête. Stockez à la fois les versions HTML et Markdown du contenu -- HTML en tant que secours, Markdown pour le rendu MDX. Mappez les types de publications personnalisés à des tables de base de données équivalentes ou des collections CMS dans votre nouveau système.
Le script d'export
Voici le script Node.js que j'utilise pour extraire le contenu de l'API WP REST, le nettoyer et l'insérer dans Supabase. Cela gère les publications, mais vous dupliqueriez le modèle pour les pages et les CPT (changez simplement le point de terminaison).
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();
Quelques points que j'ai appris à la dure :
- Utilisez toujours
_embeddans vos appels à l'API WP REST. Sans cela, vous obtenez des ID de médias au lieu d'URL, ce qui signifie des requêtes N+1 pour résoudre les images en vedette. - Turndown convertit le HTML en Markdown -- c'est crucial si vous prévoyez de faire un rendu avec MDX plus tard. Gardez aussi l'HTML d'origine, en tant que secours.
- Les shortcodes ne survivent pas. WordPress rend certains shortcodes via l'API REST, mais beaucoup (en particulier de plugins comme WPBakery ou Elementor) arrivent sous forme de texte entre crochets bruts. Vous avez besoin d'une stratégie de mappage de shortcode à composant. Je garde une feuille de calcul.
- ACF / Champs personnalisés : Si vous utilisez ACF, vous devrez avoir activé le plugin ACF to REST API, puis les champs personnalisés apparaissent dans la propriété
acfde chaque objet publication.
Schéma de table 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()
);
Remarquez que j'ai inclus les colonnes seo_title, seo_description et og_image. Vous en aurez besoin dans la section de migration SEO ci-dessous.
Migration des médias : de wp-content vers Supabase Storage
C'est la partie que la plupart des guides ne font que survoler, et c'est la partie qui prend le plus de temps. Un site WordPress vieux de 12 ans peut facilement avoir 10 000+ fichiers dans wp-content/uploads/.
L'approche :
- Téléchargez le répertoire entier
wp-content/uploads/ - Téléchargez vers Supabase Storage (ou Cloudflare R2, ou S3)
- Réécrivez chaque URL de média dans votre contenu
Comment migrer les fichiers multimédias WordPress vers un stockage d'objets moderne ?
Téléchargez votre répertoire entier wp-content/uploads/, téléchargez vers Supabase Storage ou Cloudflare R2, puis réécrivez toutes les URL des médias dans votre contenu. Utilisez un script pour récupérer chaque URL d'image de votre contenu WordPress, téléchargez vers le stockage d'objets en préservant la structure de répertoire (2024/03/image.jpg), puis exécutez une deuxième passe pour remplacer les anciennes URL par les nouvelles URL de stockage. Configurez des redirections avec caractères génériques pour les sites externes qui créent des liens directs vers vos anciennes URL d'images.
Script de téléchargement et d'envoi
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);
}
}
Le gain de performance d'image
C'est là que la migration paie vraiment. WordPress sert les envois d'images originaux -- souvent des PNG 3000×2000px que quelqu'un a téléchargés depuis son DSLR. Même avec un plugin comme ShortPixel, vous servez toujours les images via PHP.
Le composant <Image> de Next.js avec next/image effectue une négociation de format automatique (WebP/AVIF), un dimensionnement réactif et un chargement différé. Les chiffres de notre dernière migration :
| Métrique | WordPress | Next.js + Image Component |
|---|---|---|
| Poids moyen des images par page | 2,1 MB | 380 KB |
| Requêtes d'images | 12 par page | 6 par page (chargement différé) |
| Format | JPEG/PNG | WebP (AVIF où supporté) |
| Cumulative Layout Shift | 0,18 | 0,02 |
Ce n'est pas une faute de frappe. Le poids moyen du payload d'image est passé de 2,1 MB à 380 KB. Et c'était sans re-télécharger des fichiers optimisés -- juste en laissant next/image faire son travail.
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
/>
);
}
Structure des URL : mapper chaque ancienne URL
C'est là que les migrations meurent. Une redirection manquée signifie un 404 pour une page que Google a indexée depuis des années. Je traite le mappage des URL avec la même rigueur qu'une migration de base de données -- testez-le, vérifiez-le, puis vérifiez-le à nouveau.
Quelle est la bonne façon de gérer les redirections d'URL lors de la migration WordPress ?
Exportez chaque URL publiée de WordPress, faites une référence croisée avec les URL indexées de Google Search Console, puis implémentez des redirections 301 pour toutes. Interrogez votre table wp_posts pour toutes les URL publiées, exportez les URL indexées de GSC et créez une carte de redirection. Utilisez les redirections next.config.js pour moins de 50 URL, un fichier JSON pour 50-1 024 URL, ou middleware pour les sites dépassant la limite de 1 024 redirections de Vercel. Incluez des redirections avec caractères génériques pour les pages de catégorie, la pagination et les chemins wp-content/uploads.
Le processus de mappage
Tout d'abord, exportez chaque URL de WordPress. Je tire directement de la base de données :
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;
Puis faites une référence croisée avec les URL indexées de Google Search Console. GSC montre souvent des URL qui n'existent plus dans votre base de données -- anciennes pages de catégorie, URL de pagination, pages de pièce jointe. Vous avez besoin de redirections pour toutes.
Redirections next.config.js
Pour les sites avec moins de 50 redirections, intégrez-les :
// 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,
},
];
},
};
Pour 200+ redirections : utilisez un fichier JSON
Une fois que vous avez passé quelques centaines, maintenir les redirections intégrées est misérable. J'utilise un fichier 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;
},
};
La redirection avec caractères génériques pour wp-content/uploads est critique. Des sites externes créeront des liens directs vers vos images. Ne perdez pas ces backlinks.
Important : Vercel a une limite de 1 024 redirections dans next.config.js. Pour les sites avec plus que cela, utilisez middleware :
// 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
);
}
}

Migration SEO : données Yoast et RankMath vers les métadonnées Next.js
Si vous avez utilisé Yoast ou RankMath, vous avez des années de titres de métadonnées personnalisés, de descriptions et de données Open Graph stockées dans la table wp_postmeta. Ne les perdez pas.
Comment préserver les données SEO de Yoast lors de la migration vers Next.js ?
Exportez les titres de métadonnées, les descriptions et les images Open Graph de Yoast à partir de wp_postmeta, stockez-les dans votre nouvelle base de données, puis rendez-les en utilisant l'API Metadata de Next.js. Interrogez wp_postmeta pour les champs _yoast_wpseo_title, _yoast_wpseo_metadesc et _yoast_wpseo_opengraph-image. Importez ces données dans des colonnes SEO dédiées de votre table des publications. Utilisez generateMetadata dans Next.js App Router pour rendre ces données en tant que balises meta appropriées et balisage Open Graph.
Exportation des données 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;
Pour RankMath, échangez les clés de métadonnées : rank_math_title, rank_math_description, rank_math_facebook_image.
Importez ces données dans votre table posts de Supabase dans les colonnes SEO que nous avons définies plus tôt.
API Metadata de Next.js
Avec App Router, les métadonnées sont une propriété de première classe :
// 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 }] : [],
},
};
}
Balisage de schéma en tant que composants serveur JSON-LD
Les plugins WordPress génèrent le schéma automatiquement. Dans Next.js, vous le construisez vous-même -- ce qui vous donne en fait plus de contrôle :
// 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) }}
/>
);
}
Sitemaps dynamiques
// 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,
}));
}
Cela se génère au moment de la construction pour les sites statiques ou à la demande pour les sites dynamiques. Pas de plugin, pas de fichiers de modèle XML, pas de problèmes de mise en cache.
Formulaires : Gravity Forms vers Server Actions
Gravity Forms est l'un des meilleurs plugins WordPress jamais créés. C'est aussi 259 $/an pour la licence Elite, et chaque formulaire charge 200 KB+ de JavaScript.
Voici le remplacement. C'est environ 20 lignes de code par formulaire.
Exporter les entrées existantes
D'abord, exportez vos entrées Gravity Forms en CSV depuis l'admin WordPress. Stockez-les dans Supabase pour les dossiers historiques si nécessaire.
Formulaire de contact Server Action
// 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>
);
}
Pas de plugin. Pas de payload JavaScript pour le formulaire lui-même (c'est un formulaire HTML natif avec une action de serveur). Amélioration progressive -- cela fonctionne sans JavaScript activé.
Pour les formulaires plus complexes (multi-étapes, téléchargements de fichiers, champs conditionnels), nous utilisons React Hook Form côté client avec le même modèle d'action de serveur. L'idée clé : vous n'avez pas besoin d'un plugin de formulaire quand vous avez une base de données et une API.
WooCommerce vers Stripe
C'est la partie la plus difficile de toute migration WordPress. WooCommerce n'est pas un simple plugin -- c'est une plateforme e-commerce avec des produits, des variantes, des stocks, des commandes, des abonnements, des coupons, des taxes et des règles d'expédition. Vous ne migrez pas une fonctionnalité. Vous remplacez une plateforme.
Comment migrer les produits WooCommerce vers Stripe ?
Exportez les produits via l'API REST de WooCommerce ou CSV, puis créez les produits correspondants dans l'API Stripe Products avec des prix. Pour les sites avec moins de 500 produits, envoyez directement à Stripe en utilisant leur API : créez un produit avec le nom, la description et les images, puis créez un objet de prix lié à ce produit. Stockez l'ID de produit WooCommerce dans les métadonnées de Stripe pour la référence. Utilisez les sessions de paiement Stripe Checkout pour le traitement des paiements et les webhooks pour suivre les commandes dans votre base de données.
Migration de produits
Exportez les produits de WooCommerce via CSV ou l'API REST. Vous avez deux options de destination :
| Approche | Meilleure pour | Compromis |
|---|---|---|
| Table des produits Supabase | Vitrines personnalisées, filtrage complexe | Vous gérez la logique des stocks |
| API Stripe Products | Catalogues simples, entreprises d'abonnement | Stripe gère les prix, vous gérez l'affichage |
Pour la plupart des sites avec moins de 500 produits, j'envoie directement vers Stripe Products :
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}`);
}
}
Paiement avec les sessions 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 });
}
Abonnements
Si vous êtes sur WooCommerce Subscriptions (239 $/an), passez à Stripe Billing. Changez mode: 'payment' en mode: 'subscription' et assurez-vous que vos prix ont recurring défini. C'est tout. Stripe gère les périodes d'essai, la proratisation et le dunning.
Suivi des commandes via 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 });
}
Les frais de transaction de Stripe sont 2,9 % + 0,30 $ par transaction. Comparez cela à WooCommerce où vous payez pour l'hébergement (30-100 $/mois pour WordPress géré), le plugin Subscriptions (239 $/an), un plugin de passerelle de paiement et probablement quelques autres modules complémentaires. Le calcul s'ajoute rapidement.
Pour les migrations e-commerce complexes, nous l'offrons dans le cadre de nos services de développement Next.js -- c'est l'une des demandes les plus courantes que nous recevons.
Surveillance après la migration
Le lancement n'est pas la fin. Les deux premières semaines suivant la migration sont critiques.
Google Search Console
- Soumettez votre nouveau sitemap immédiatement
- Utilisez l'outil Inspection d'URL pour demander l'indexation de vos 20 meilleures pages
- Surveillez le rapport de couverture quotidiennement pendant la première semaine -- surveillez les pics de 404
- Vérifiez le rapport « Indexation des pages » pour toutes les pages bloquées dans « Découverte -- actuellement non indexée »
Comparaison des analyses
Configurez un tableau de bord qui compare semaine par semaine :
- Sessions totales
- Trafic de recherche organique spécifiquement
- Taux de rebond par page
- Taux de conversion (soumissions de formulaire, achats)
Une légère baisse de trafic la première semaine est normale. Si elle ne s'est pas rétablie d'ici la troisième semaine, quelque chose s'est mal passé avec les redirections ou l'indexation.
Audits Lighthouse
Exécutez Lighthouse sur chaque modèle majeur (page d'accueil, article de blog, page de produit, page de contact). Cible :
- Performance : 90+
- Accessibilité : 95+
- Bonnes pratiques : 95+
- SEO : 100
Sur notre dernière migration -- un site de contenu de 400 pages -- nous avons amélioré le score de performance Lighthouse moyen de 38 sur WordPress à 96 sur Next.js déployé sur Vercel. Ce n'est pas sélectif. C'est la moyenne.
Quand rester sur WordPress
Voici la partie où je perds certains d'entre vous, mais c'est la section la plus importante de ce guide.
Ne migrez pas si :
- Vous avez un blog simple ou un site brochure avec moins de 20 pages
- Votre équipe n'est pas technique et s'appuie sur l'admin WordPress pour les mises à jour quotidiennes
- Vos scores Lighthouse sont déjà 70+ et vous n'avez pas de besoins commerciaux critiques en termes de performance
- Vous n'avez pas de problèmes de sécurité et votre hébergement est stable
- Vos coûts totaux de plugins sont inférieurs à 200 $/an
- Vous n'avez pas de développeur (ou de budget pour un) pour maintenir un site Next.js
WordPress avec un bon hébergeur (Cloudways, Kinsta), un thème solide et des plugins minimaux va bien. En fait, c'est bien plus que bien -- c'est éprouvé au combat, bien documenté et compris par des millions de développeurs.
La migration a du sens quand :
- La performance est directement liée aux revenus (sites d'e-commerce, sites de marketing SaaS)
- Vous dépensez 500+$/mois pour l'hébergement géré et les plugins de sécurité
- Votre équipe de développement écrit déjà React
- Vous avez besoin d'un pipeline de déploiement avec des builds d'aperçu, des environnements de staging et des restaurations
- La surface d'attaque en matière de sécurité est une préoccupation véritable (gouvernement, santé, finance)
Je dis cela parce que la confiance est plus importante qu'une vente. Si vous n'êtes pas sûr de savoir si la migration en vaut la peine, contactez-nous et nous vous donnerons une évaluation honnête.
Benchmarks de performance : avant et après
De nos cinq dernières migrations en 2024-2025 :
| Métrique | WordPress (Moyenne) | Next.js (Moyenne) | Changement |
|---|---|---|---|
| TTFB | 1 200 ms | 85 ms | 14x plus rapide |
| LCP | 3,8 s | 0,9 s | 4,2x plus rapide |
| Poids total de la page | 3,2 MB | 620 KB | 5x plus léger |
| Requêtes par page | 47 | 11 | 77 % moins |
| Performance Lighthouse | 42 | 94 | +52 points |
| Coût d'hébergement mensuel | 75 $ | 20 $ (Vercel Pro) | 73 % d'économies |
| Taux de passage Core Web Vitals | 31 % des pages | 100 % des pages | ✓ |
Ce sont des chiffres réels de sites en production. Les sites WordPress utilisaient un hébergement géré (WP Engine et Kinsta), un cache optimisé et des plugins d'optimisation d'images. Ils n'ont pas été négligés -- c'étaient des sites maintenus qui avaient simplement atteint le plafond de ce que WordPress peut offrir.
Si vous êtes intéressé par ce qui est possible avec des frameworks modernes, consultez également nos capacités de développement Astro -- pour les sites riche en contenu avec une interactivité minimale, Astro peut offrir des payloads encore plus petits que Next.js.
Questions fréquemment posées
Combien de temps prend la migration de WordPress à Next.js ?
Pour un site typique avec 100-500 pages, comptez 4-8 semaines de temps de développement. Les sites brochure simples peuvent être réalisés en 2-3 semaines. Les boutiques WooCommerce complexes avec des milliers de produits pourraient prendre 10-12 semaines. La migration du contenu elle-même est rapide -- c'est la reconstruction des modèles de frontend et le test de chaque redirection qui prennent du temps.
Vais-je perdre mes classements SEO en migrant de WordPress vers Next.js ?
Non, si vous gérez correctement les redirections et les métadonnées. Les pièces critiques sont : les redirections 301 pour chaque ancienne URL, la migration de tous les titres de métadonnées et descriptions Yoast/RankMath, la préservation de la structure de votre sitemap et la soumission du nouveau sitemap à Google Search Console immédiatement. Nous avons vu des sites récupérer jusqu'à la circulation pré-migration en 1-2 semaines, avec une croissance organique significative d'ici le mois trois en raison de l'amélioration des Core Web Vitals.
Puis-je utiliser WordPress en tant que CMS découplé avec Next.js ?
Oui, et c'est une approche populaire. Vous conservez WordPress en tant que backend de contenu, en utilisant l'API WP REST ou WPGraphQL, et Next.js en tant que frontend. Cela préserve l'expérience d'édition familière tout en obtenant la performance de Next.js. L'inconvénient est que vous maintenez toujours une installation WordPress avec ses frais généraux de sécurité et de mise à jour. Nous recommandons généralement Payload CMS ou Sanity pour les nouveaux projets à moins que l'équipe ne soit profondément investie dans les flux de travail WordPress.
Combien coûte la migration de WordPress vers Next.js ?
DIY avec temps de développeur : gratuit en outils, mais budgétisez 80-200 heures de temps de développement. Coût agence : généralement 10 000-50 000 $ selon la complexité du site, le nombre de pages, les fonctionnalités e-commerce et la fonctionnalité personnalisée. Consultez notre page de tarification pour les détails spécifiques de nos forfaits. Le retour sur investissement provient généralement de réductions de coûts d'hébergement (économies de 50-100 $/mois), de frais de licence de plugin éliminés et d'augmentation des taux de conversion due à une meilleure performance.
Qu'advient-il de mes plugins WordPress après la migration ?
Chaque plugin a besoin d'un équivalent Next.js. Contact Form 7 ou Gravity Forms devient une Server Action. Yoast SEO devient l'API Metadata de Next.js. WooCommerce devient Stripe. Google Analytics reste le même (déplacez simplement le snippet de suivi). Certains plugins comme Wordfence deviennent inutiles puisqu'il n'y a pas de WordPress à attaquer. Faites un inventaire complet de vos plugins avant de commencer -- tout plugin sans stratégie de remplacement claire est un risque.
Dois-je migrer vers Next.js ou Astro à partir de WordPress ?
Cela dépend de vos besoins d'interactivité. Next.js est meilleur pour les sites avec des fonctionnalités dynamiques -- authentification utilisateur, e-commerce, tableaux de bord, données en temps réel. Astro est meilleur pour les sites riches en contenu qui sont mostly statiques -- blogs, documentation, sites marketing. Astro n'expédie aucun JavaScript par défaut, ce qui signifie des tailles de page encore plus petites. Nous travaillons avec les deux -- consultez nos pages Développement Astro et Développement Next.js pour plus de détails.
Puis-je migrer les abonnements WooCommerce vers Stripe ?
Oui, mais cela nécessite un soin particulier des abonnés actifs. Vous devrez créer des clients et des abonnements dans Stripe, puis communiquer le changement de facturation aux clients. Stripe Billing gère les périodes d'essai, la proratisation, la logique de relance des paiements échoués et les flux d'annulation. La migration elle-même est un script ponctuel, mais le tester par rapport aux scénarios d'abonnement réels est l'endroit où le temps s'accumule. Budgétisez du temps supplémentaire pour cela si vous avez plus de 100 abonnés actifs.
Quel est le meilleur hébergement pour Next.js après migration de WordPress ?
Vercel est le choix par défaut -- il est construit par l'équipe qui fabrique Next.js, et le niveau gratuit gère la plupart des sites marketing. Vercel Pro est 20 $/mois pour les équipes. Les alternatives incluent Netlify, Cloudflare Pages (excellent pour la performance en périphérie) et l'auto-hébergement avec Docker sur un VPS si vous voulez le contrôle total. Tous ceux-ci sont significativement moins chers que l'hébergement WordPress géré pour les niveaux de trafic équivalents.