Ce guide vous montre comment déménager un site web de Webflow vers WordPress autohébergé, sur un serveur cPanel/WHM. Webflow offre deux exports partiels et aucun export complet. Le contenu de votre CMS (billets de blogue et autres éléments de collection) sort sous forme d’un fichier CSV par collection, et le reste du site doit être refait. La réponse courte est d’importer chaque CSV avec un petit script WP-CLI et de copier chaque image hors des serveurs de Webflow. Refaites ensuite les pages et les formulaires, et gardez vos URL de blogue identiques pour éviter la plupart des redirections. Vous résiliez le forfait de site Webflow en dernier, une fois qu’une validation prouve que plus rien ne se charge depuis Webflow.
Ce que vous allez accomplir
- Exporter chaque collection du CMS Webflow en CSV et savoir quelles colonnes comptent.
- Importer billets, catégories et dates dans WordPress avec WP-CLI, l’outil en ligne de commande de WordPress, pendant que Webflow continue de servir les visiteurs.
- Copier chaque image du CDN de Webflow dans votre propre médiathèque.
- Garder les mêmes URL de blogue, et ajouter des redirections 301 seulement pour les chemins qui changent.
- Déplacer le DNS dans un ordre qui garde le courriel en marche, puis résilier Webflow en toute confiance.
Prérequis
- Un accès propriétaire ou administrateur au site Webflow, avec accès au panneau CMS du Designer. Ce que vous pouvez exporter dépend de votre forfait Webflow, alors vérifiez ce que votre compte offre avant de fixer une date.
- Un accès au DNS du domaine, chez le registraire ou l’hébergeur DNS. Webflow n’héberge pas le courriel, donc vos boîtes vivent déjà chez un autre fournisseur.
- Un compte cPanel sur un serveur que vous contrôlez, ou un accès root à WHM pour en créer un.
- Un accès SSH avec WP-CLI. Vérifiez avec
wp --info. curletdigsur votre propre poste. Les deux sont livrés avec macOS et la plupart des distributions Linux ; sous Windows, utilisez WSL ou Git Bash.- Au moins deux semaines avant le prochain renouvellement Webflow. L’étape des images exige que l’ancien site reste en ligne.
- Les serveurs cPanel tournent sous Apache ou LiteSpeed. Les deux lisent
.htaccessde la même façon, donc les règles de redirection ci-dessous fonctionnent dans les deux cas.
Comment ça fonctionne
Un site Webflow contient deux sortes de contenu, et elles sortent par des portes différentes.
Les collections du CMS sont le contenu structuré de Webflow, comme Blog Posts, Categories ou Team Members. Chaque collection s’exporte dans son propre fichier CSV. Chaque ligne est un élément, avec des colonnes système comme Name, Slug et Published On, plus une colonne par champ que vous avez défini. Les champs de texte enrichi arrivent en HTML, que WordPress peut stocker directement comme contenu de billet. Les champs de référence, qui relient un élément à un élément d’une autre collection, s’exportent en général sous forme du slug de l’élément lié. Les champs à références multiples contiennent plusieurs slugs dans une même cellule, séparés par des points-virgules.
Les pages statiques comme l’accueil, À propos ou Tarifs ne sont dans aucun CSV. L’export de code de Webflow (une archive zip de HTML, CSS et JavaScript, offerte avec certains forfaits payants) les contient, mais pas les éléments de vos collections. Les pages de collection de cette archive sont des gabarits vides. Servez-vous de l’archive comme référence pour le texte et la structure, pas comme quelque chose à importer.
Les images vivent sur le CDN de Webflow (réseau de diffusion de contenu, soit les serveurs qui stockent et servent vos fichiers téléversés). Les sites plus anciens utilisent uploads-ssl.webflow.com ; les plus récents, cdn.prod.website-files.com. Le CSV ne contient que des liens vers ces fichiers. Après l’importation, vos pages WordPress chargent encore les images depuis Webflow tant que vous ne les copiez pas.
Les formulaires et les Interactions ne se déplacent pas du tout. Les formulaires Webflow envoient les soumissions à Webflow, et les Interactions (le système d’animation de Webflow) dépendent du JavaScript de Webflow. Vous refaites les formulaires avec une extension WordPress et ne recréez que les animations qui comptent.
Webflow sert les éléments de collection à /<collection-slug>/<item-slug>, par exemple /blog/spring-open-house. Si vous donnez la même forme aux permaliens WordPress, vos URL de blogue restent inchangées.
Étape par étape
-
Inventoriez le site en ligne. Webflow génère
/sitemap.xmlquand le sitemap automatique est activé dans les paramètres SEO, ce qui est le réglage par défaut.curl -s https://example.com/sitemap.xml \ | grep -o '<loc>[^<]*</loc>' | sed 's|</*loc>||g' \ | sort -u > ~/wf-urls.txt wc -l < ~/wf-urls.txt sed -E 's#https://[^/]+/([^/]+)/.+#\1#;t;d' ~/wf-urls.txt | sort | uniq -cRésultat attendu : un total comme
84, puis une ligne par collection, par exemple52 bloget9 team. Ce sont les slugs de vos collections. Si le sitemap est vide, activez le sitemap automatique dans les paramètres SEO de Webflow et publiez. Pendant que vous êtes dans les paramètres du site, copiez les redirections 301 existantes de l’onglet Publishing dans un fichier texte, parce qu’aucun export ne les transporte. -
Exportez chaque collection en CSV. Dans le Designer, ouvrez le panneau CMS, sélectionnez une collection et choisissez Export. Répétez pour chaque collection, y compris les petites comme Categories. Copiez les fichiers sur le serveur avec des noms courts, puis lisez la ligne d’en-tête :
# sur votre propre poste scp blog-posts.csv categories.csv exmpl@server.example.net:~/ # sur le serveur head -1 ~/blog-posts.csvRésultat attendu : un en-tête comme
Name,Slug,Collection ID,Item ID,Archived,Draft,Created On,Updated On,Published On,Post Body,Post Summary,Main Image,Category. Les noms de vos champs personnalisés seront différents. Notez le nom exact des colonnes de contenu, d’image et de catégorie, parce que le script d’importation s’en sert. N’ouvrez pas ces fichiers pour les réenregistrer dans un tableur, ce qui peut briser le HTML des cellules de texte enrichi. -
Créez le compte cPanel et installez WordPress sur un nom d’hôte temporaire. Dans WHM, utilisez Account Functions > Create a New Account. Faites pointer un sous-domaine comme
new.example.comvers le nouveau serveur avec un enregistrement A. Créez la base de données vide et son utilisateur dans l’outil Manage My Databases de cPanel, puis lancez :cd ~/public_html wp core download wp config create --dbname=exmpl_wp --dbuser=exmpl_wp --dbpass='REPLACE_ME' wp core install --url=https://new.example.com --title='Example Studio' \ --admin_user=admin --admin_email=you@example.com --skip-email wp rewrite structure '/blog/%postname%/' --hard wp option get permalink_structureRésultat attendu :
Success: WordPress installed successfully., puisSuccess: Rewrite structure set., puis/blog/%postname%/. Remplacezblogpar le slug de la collection de votre blogue, relevé à l’étape 1. Ainsi,/blog/spring-open-housesur Webflow et/blog/spring-open-house/sur WordPress deviennent la même page. WordPress ajoute la barre oblique finale avec sa propre 301. -
Importez les catégories, puis les billets. Enregistrez ceci sous
~/wf-terms.php. Le script crée une catégorie WordPress par ligne en gardant le slug de Webflow :<?php $fh = fopen( $args[0], 'r' ); $head = fgetcsv( $fh ); $head[0] = preg_replace( '/^\xEF\xBB\xBF/', '', $head[0] ); $h = array_flip( $head ); while ( ( $r = fgetcsv( $fh ) ) !== false ) { $t = wp_insert_term( $r[ $h['Name'] ], 'category', array( 'slug' => $r[ $h['Slug'] ] ) ); WP_CLI::log( ( is_wp_error( $t ) ? 'skipped ' : 'created ' ) . $r[ $h['Slug'] ] ); }Enregistrez ceci sous
~/wf-posts.php. Modifiez les noms de$colpour qu’ils correspondent à votre ligne d’en-tête de l’étape 2 :<?php $col = array( 'body' => 'Post Body', 'image' => 'Main Image', 'terms' => 'Category' ); $fh = fopen( $args[0], 'r' ); $head = fgetcsv( $fh ); $head[0] = preg_replace( '/^\xEF\xBB\xBF/', '', $head[0] ); $h = array_flip( $head ); $n = 0; while ( ( $r = fgetcsv( $fh ) ) !== false ) { if ( $r[ $h['Draft'] ] === 'true' || $r[ $h['Archived'] ] === 'true' ) { continue; } $ts = strtotime( preg_replace( '/\s*\(.*\)$/', '', $r[ $h['Published On'] ] ) ); $id = wp_insert_post( array( 'post_type' => 'post', 'post_status' => 'publish', 'post_title' => $r[ $h['Name'] ], 'post_name' => $r[ $h['Slug'] ], 'post_content' => $r[ $h[ $col['body'] ] ], 'post_date' => $ts ? date( 'Y-m-d H:i:s', $ts ) : current_time( 'mysql' ), ), true ); if ( is_wp_error( $id ) ) { WP_CLI::warning( $r[ $h['Slug'] ] . ': ' . $id->get_error_message() ); continue; } if ( $r[ $h[ $col['image'] ] ] !== '' ) { update_post_meta( $id, '_wf_main_image', $r[ $h[ $col['image'] ] ] ); } if ( $r[ $h[ $col['terms'] ] ] !== '' ) { wp_set_object_terms( $id, array_map( 'trim', explode( ';', $r[ $h[ $col['terms'] ] ] ) ), 'category' ); } $n++; } WP_CLI::success( "Imported $n posts." );Lancez les deux en tant qu’administrateur :
wp eval-file ~/wf-terms.php ~/categories.csv --user=admin wp eval-file ~/wf-posts.php ~/blog-posts.csv --user=admin wp post list --post_type=post --format=countRésultat attendu : une ligne
createdpar catégorie, puisSuccess: Imported 52 posts., puis52. Le total devrait correspondre à la ligne du blogue de l’étape 1. L’option--user=admincompte : sans utilisateur, WordPress filtre le HTML et retire les intégrations comme les iframes YouTube. La ligne de date enlève l’étiquette de fuseau horaire entre parenthèses que Webflow peut ajouter, et utilise la date du jour si la date reste illisible. Les éléments en brouillon et archivés sont ignorés. -
Copiez chaque image hors du CDN de Webflow. Commencez par les images mises en avant, que le script a enregistrées en métadonnées :
for id in $(wp post list --post_type=post --meta_key=_wf_main_image --field=ID --posts_per_page=-1); do url=$(wp post meta get "$id" _wf_main_image < /dev/null) wp media import "$url" --post_id="$id" --featured_image --porcelain < /dev/null || echo "FAILED $id $url" done 2>&1 | tee ~/wf-featured.log grep -c FAILED ~/wf-featured.logRésultat attendu : une colonne d’identifiants de pièces jointes, puis
0. Passez ensuite aux images insérées dans le texte enrichi :wp db query "SELECT post_content FROM wp_posts WHERE post_content LIKE '%webflow.com%' OR post_content LIKE '%website-files.com%';" --skip-column-names \ | grep -oE 'https://(uploads-ssl\.webflow\.com|cdn\.prod\.website-files\.com)/[^"?) ]+' \ | sort -u > ~/wf-images.txt while read -r old; do id=$(wp media import "$old" --porcelain < /dev/null) || { echo "FAILED $old"; continue; } new=$(wp eval "echo wp_get_attachment_url($id);" < /dev/null) wp search-replace "$old" "$new" wp_posts --include-columns=post_content --quiet < /dev/null done < ~/wf-images.txt 2>&1 | tee ~/wf-images.log grep -c FAILED ~/wf-images.logRésultat attendu :
0. Chaque< /dev/nullempêche une commande WP-CLI de lire le reste de la liste comme sa propre entrée.wp search-replaceest plus sûr qu’unREPLACESQL brut, parce qu’il garde valides les données PHP sérialisées, où chaque chaîne enregistre sa propre longueur. -
Refaites ce que le CSV ne transporte pas. Traitez les éléments dans l’ordre de leur valeur pour l’entreprise :
- Les pages statiques. Refaites l’accueil, À propos et les autres dans l’éditeur de blocs. L’export de code, si votre forfait l’offre, est pratique pour copier le texte et les attributs alt.
- Les autres collections. Importez Team Members ou Case Studies avec une copie de
wf-posts.php, en remplaçant'post_type' => 'post'par'page'ou par un type de contenu personnalisé que votre thème enregistre. - Les formulaires. Recréez chaque formulaire avec une extension de formulaires et envoyez une soumission de test. Exportez les anciennes soumissions depuis les paramètres de formulaires de Webflow si votre forfait les y affiche, et gardez le CSV en archive.
- Les Interactions. Dressez la liste des animations que les visiteurs remarquent vraiment. Recréez celles-là en CSS ou avec les options de votre thème, et laissez tomber le reste.
-
Redirigez seulement les chemins qui changent. Vérifiez où WordPress place maintenant les archives de catégories, puis écrivez les règles pour les anciens chemins Webflow :
wp term list category --fields=slug,urlRésultat attendu : des URL comme
https://new.example.com/blog/category/events/. Ajoutez le bloc dans~/public_html/.htaccessau-dessus de# BEGIN WordPress:# --- Webflow legacy paths: keep above the WordPress block --- RedirectMatch 301 ^/categories/([^/]+)/?$ https://example.com/blog/category/$1/ Redirect 301 /about-us https://example.com/about/ # --- end Webflow legacy paths --- # BEGIN WordPressUtilisez vos propres slugs de collection et collez les redirections copiées depuis Webflow à l’étape 1. Le bloc se trouve au-dessus des règles WordPress parce que leur règle fourre-tout envoie chaque requête inconnue à
index.php. Gardez-le en dehors des marqueursBEGIN/END, puisque WordPress réécrit cette section quand on enregistre les permaliens. Testez-le :curl -sI https://new.example.com/categories/events | head -3Résultat attendu :
HTTP/1.1 301 Moved Permanentlyet un en-têteLocation:avec la nouvelle adresse. Notre guide pour préserver le référencement lors d’une migration WordPress couvre le volet SEO plus en profondeur. -
Déplacez le DNS : le courriel d’abord, le site ensuite. Notez l’état actuel :
dig +short NS example.com dig +short CNAME www.example.com dig +short MX example.com dig +short TXT example.comRésultat attendu : un CNAME
wwwqui pointe vers un nom d’hôte Webflow, et des enregistrements MX pour votre fournisseur de courriel, comme Google Workspace ou Microsoft 365. La veille, abaissez le TTL (durée de vie, soit le temps pendant lequel les résolveurs gardent un enregistrement en cache) de ces enregistrements à 300 secondes. Si vous déplacez les serveurs de noms vers le serveur cPanel, recréez d’abord chaque enregistrement MX et TXT dans le Zone Editor de cPanel, y compris SPF et DKIM. Confirmez ensuite que le nouveau serveur répond de la même façon :dig +short MX example.com @ns1.server.example.netRésultat attendu : la même liste MX que plus haut. Seulement à ce moment, changez les serveurs de noms, ou modifiez les enregistrements du site chez votre hébergeur DNS actuel. Faites pointer l’enregistrement A du domaine racine vers l’adresse IP de votre serveur, et
wwwvers le domaine racine. Notre liste de préparation DNS détaille la baisse du TTL. Terminez en réécrivant le nom d’hôte temporaire :wp search-replace 'new.example.com' 'example.com' --all-tables wp option get siteurlRésultat attendu : un nombre de remplacements, puis
https://example.com. Une fois le nouveau site en ligne, retirez le domaine personnalisé dans les paramètres Publishing de Webflow, pour que Webflow cesse d’essayer de le servir.
Valider que ça fonctionne
Confirmez que plus rien ne se charge depuis Webflow, ni dans la base de données ni dans la page :
wp db query "SELECT COUNT(*) FROM wp_posts WHERE post_content LIKE '%website-files.com%' OR post_content LIKE '%uploads-ssl.webflow.com%';" --skip-column-names
wp db query "SELECT COUNT(*) FROM wp_postmeta WHERE meta_key <> '_wf_main_image' AND (meta_value LIKE '%website-files.com%' OR meta_value LIKE '%uploads-ssl.webflow.com%');" --skip-column-names
curl -s https://example.com/ | grep -cE 'website-files\.com|webflow\.com'
Résultat attendu : 0 pour les trois. Tout autre nombre est une image ou un script qui cassera à la fin du forfait.
Vérifiez que chaque ancienne URL répond maintenant par un 200 ou un 301, jamais par un 404 :
while read -r u; do
printf '%s %s\n' "$(curl -s -o /dev/null -w '%{http_code}' "$u")" "$u"
done < ~/wf-urls.txt | grep -vE '^(200|301)'
Résultat attendu : aucune sortie. Chaque ligne qui apparaît est une page qu’il reste à refaire. Envoyez un message de test depuis un compte externe à chaque boîte courriel, et soumettez une fois chaque formulaire refait. Nos tests de fumée post-migration couvrent le reste du premier jour. Résiliez le forfait de site Webflow seulement quand tout cela passe.
Dépannage
- Tous les billets portent la date du jour. Le format de date de Webflow n’a pas été reconnu. Lancez
head -2 ~/blog-posts.csv | cut -c1-400pour voir la valeur brute, ajustez lepreg_replacedu script, supprimez l’importation avecwp post delete $(wp post list --post_type=post --field=ID --posts_per_page=-1) --forceet relancez. - Les vidéos intégrées ont disparu des billets. Le script a tourné sans
--user=admin, donc WordPress a filtré les iframes. Supprimez les billets importés de la même façon et relancez avec l’option. PHP Warning: Undefined array keypendant l’importation. Un nom de colonne dans$colne correspond pas exactement à la ligne d’en-tête, majuscules et espaces compris. Comparez-le avec lehead -1de l’étape 2.wp media importéchoue avec « Sorry, you are not allowed to upload this file type. » Le fichier est probablement en AVIF ou en SVG. WordPress accepte l’AVIF depuis la version 6.5, et le SVG exige une extension qui assainit les téléversements. Sinon, téléchargez le fichier, convertissez-le en WebP ou en PNG et importez la copie locale.- Les billets donnent un 404 sur le nouvel hôte. Les règles de permaliens ne se sont jamais rendues dans
.htaccess. Lancezwp rewrite flush --hardet confirmez que# BEGIN WordPressapparaît maintenant dans le fichier.
Quand ce n’est pas la bonne approche
Si votre site n’a pas de contenu CMS et que sa valeur tient au design et aux animations, WordPress peut être un recul. Avec un forfait qui offre l’export de code, vous pouvez héberger cette archive statique directement sur un compte cPanel, à condition de remplacer les formulaires, qui ne fonctionnent pas hors de Webflow.
Si vous vendez avec Webflow Ecommerce, traitez la boutique comme un projet à part. Les produits peuvent s’exporter en CSV, mais pas les clients, les commandes ni la configuration des paiements.
Si vous comparez des constructeurs de sites, notre guide de migration Squarespace montre une plateforme qui offre un export au format WordPress. La référence WP-CLI pour eval-file et media import liste chaque option utilisée ici.
Un WordPress qui rapporte vraiment mérite un opérateur — Stack Harbor opère cette pile sous WordPress Entreprise ; réservez une consultation d’infrastructure.