Aller au contenu

Migrer un site Webflow vers WordPress sur cPanel

Apprenez à migrer un site Webflow vers WordPress sur un serveur cPanel en exportant chaque collection du CMS en CSV et en l'important avec WP-CLI. Vous copierez chaque image hors du CDN de Webflow, garderez ou redirigerez vos anciennes URL et déplacerez le DNS sans briser le courriel.

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.
  • curl et dig sur 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 .htaccess de 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.

Diagramme qui associe chaque type de contenu Webflow à sa voie vers WordPress : les collections du CMS exportées en CSV et importées avec WP-CLI, les images copiées du CDN de Webflow vers la médiathèque, les pages statiques refaites dans l'éditeur de blocs, les formulaires refaits avec une extension, les Interactions refaites ou abandonnées, et l'export de code optionnel utilisé seulement comme référence parce qu'il n'inclut pas les éléments du CMS

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.

Séquence de bascule d'une migration Webflow vers WordPress : inventorier le sitemap et exporter les CSV, importer dans WordPress sur un nom d'hôte temporaire, copier les images hors du CDN de Webflow, refaire pages, formulaires et redirections, copier les enregistrements de courriel avant de déplacer le DNS, puis retirer le domaine de Webflow et résilier le forfait seulement quand la validation montre zéro référence au CDN

Étape par étape

  1. Inventoriez le site en ligne. Webflow génère /sitemap.xml quand 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 -c

    Résultat attendu : un total comme 84, puis une ligne par collection, par exemple 52 blog et 9 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.

  2. 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.csv

    Ré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.

  3. 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.com vers 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_structure

    Résultat attendu : Success: WordPress installed successfully., puis Success: Rewrite structure set., puis /blog/%postname%/. Remplacez blog par le slug de la collection de votre blogue, relevé à l’étape 1. Ainsi, /blog/spring-open-house sur Webflow et /blog/spring-open-house/ sur WordPress deviennent la même page. WordPress ajoute la barre oblique finale avec sa propre 301.

  4. 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 $col pour 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=count

    Résultat attendu : une ligne created par catégorie, puis Success: Imported 52 posts., puis 52. Le total devrait correspondre à la ligne du blogue de l’étape 1. L’option --user=admin compte : 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.

  5. 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.log

    Ré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.log

    Résultat attendu : 0. Chaque < /dev/null empêche une commande WP-CLI de lire le reste de la liste comme sa propre entrée. wp search-replace est plus sûr qu’un REPLACE SQL brut, parce qu’il garde valides les données PHP sérialisées, où chaque chaîne enregistre sa propre longueur.

  6. 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.
  7. 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,url

    Résultat attendu : des URL comme https://new.example.com/blog/category/events/. Ajoutez le bloc dans ~/public_html/.htaccess au-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 WordPress

    Utilisez 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 marqueurs BEGIN/END, puisque WordPress réécrit cette section quand on enregistre les permaliens. Testez-le :

    curl -sI https://new.example.com/categories/events | head -3

    Résultat attendu : HTTP/1.1 301 Moved Permanently et un en-tête Location: 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.

  8. 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.com

    Résultat attendu : un CNAME www qui 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.net

    Ré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 www vers 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 siteurl

    Ré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-400 pour voir la valeur brute, ajustez le preg_replace du script, supprimez l’importation avec wp post delete $(wp post list --post_type=post --field=ID --posts_per_page=-1) --force et 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 key pendant l’importation. Un nom de colonne dans $col ne correspond pas exactement à la ligne d’en-tête, majuscules et espaces compris. Comparez-le avec le head -1 de 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. Lancez wp rewrite flush --hard et confirmez que # BEGIN WordPress apparaî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.