← Blog

WordPress

WordPress block-theme migratie: 14 valkuilen uit 9.400 posts

Veertien bugs die we tegenkwamen toen we een Belgische uitgever met 9.400 posts overzetten van een 2018 page-builder naar WordPress 6.7 block themes, geordend op wat de Site Editor het stilst sloopte.

Jacob Molkenboer· Oprichter · A Brand New Company· 13 nov 2024· 9 min
Open leren logboek met messing sleutel op crème kaart, inktkussen, stempel, groen lint, lakzegel op ivoren linnen bureau.

Het is 23:14 op het kantoor in Antwerpen. De 9.400 gearchiveerde posts van de Belgische uitgever zijn net binnen. De Site Editor meldt nul fouten. De helft van de afbeeldingen in de artikelen is weg. De pull quotes binnen de redactionele features zijn weg. De byline-blokken zijn weg. De CSS valideert, WP-CLI is tevreden, de database rolde schoon over. De posts zien er prima uit in de editor. Op de frontend missen ze ongeveer 40 procent van hun inhoud.

Dat is de ervaring van een block-theme rewrite op schaal. De Site Editor gooit geen errors. Hij herschrijft. En levert dan uit. We hebben tien weken besteed aan het porten van die uitgever van een ACF-en-shortcode page-builder uit 2018 naar een custom WordPress 6.7 block theme. Hieronder de veertien valkuilen die het hardst beten, geordend op hoe stil de editor ermee omging. Valkuil één komt in geen enkel logbestand voor.

Wat de Site Editor stilletjes herschreef

1. Custom HTML-blokken met één losse tag

De oude theme leunde op Custom HTML-blokken voor callouts, pull quotes en bijschriften bij galerijen. Bij ongeveer 1.100 ervan zat een losse closing tag, een niet-omsloten <br>, of een span die nooit opende. De Site Editor parseert elk Custom HTML-blok door de block validator bij opslaan en bij renderen. Faalt de markup, dan wordt het blok stilletjes herschreven naar de best mogelijke reconstructie. Geen melding, geen waarschuwing in de admin. De inspector ziet er ongewijzigd uit. De gerenderde HTML is anders.

We vingen dit door de gerenderde post-output voor en na de migratie te diffen met een WP-CLI-script dat van elke post de uiteindelijke HTML ophaalde en woordtellingen vergeleek. Ongeveer 7 procent van de posts verloor op deze manier meer dan 20 woorden. De fix: opnieuw importeren via wp post update met opgeschoonde HTML, daarna die blokken markeren als Classic blocks zodat de validator ze bij de volgende save niet meer aanraakt.

2. theme.json schemaversie-drift

De oude theme van de uitgever shipte theme.json met "version": 2. WordPress 6.6 introduceerde "version": 3, en 6.7 maakte dat de default voor nieuwe themes. Meng v2-settings keys (settings.typography.fontSizes) in een v3-bestand en de editor parseert wat hij snapt en dropt de rest. De drops zijn stil.

De duidelijkste tell: een font size die je in theme.json instelt, verschijnt in de editor-toolbar maar werkt niet op de frontend. Of een color palette rendert in de inspector-picker, maar de bijbehorende class landt nooit op het blok. De Global Settings and Styles handbook documenteert de v3-vorm. Pin je versie expliciet en hou één schema per theme aan.

3. Classic blocks die shortcodes omsluiten

Kan de importer oude content niet mappen op een blok, dan wordt het in een Classic block gewikkeld. Classic blocks renderen door the_content heen en voeren shortcodes uit. Klinkt prima. Het probleem: shortcodes die geregistreerd waren door de functions.php van de oude theme zijn weg. Dus [old_gallery ids="1,2,3"] rendert als letterlijke tekst binnen een paragraph tag. De Site Editor flagt de onopgeloste shortcode niet.

We schreven een one-pass script dat post_content doorzocht op elke onopgeloste shortcode tag en een CSV produceerde gemapt op het nieuwe blok. Een tweede pass verving elke entry. Sla deze stap over en je ship letterlijke [old_gallery]-strings binnen live artikelen.

4. Template parts verplaatst van /parts/ naar /patterns/

WordPress 6.6 begon patterns te promoten boven template parts voor niet-structurele herbruikbare blokken. 6.7 toont patterns in het primaire pattern-paneel van de Site Editor en behandelt /parts/ als legacy. Importeer je een theme die bijvoorbeeld een 'promo strip' als template part definieert, dan accepteert de Site Editor dat, maar laat het niet zien in de pattern picker voor de gebruiker. Editors kunnen het niet invoegen. Ze gaan ervan uit dat het niet bestaat en bouwen het inline opnieuw. Je eindigt met drie bijna identieke promo strips verspreid over de site.

De fix is mechanisch: verplaats /parts/promo-strip.html naar /patterns/promo-strip.php, voeg de Pattern Name-docblock toe en verwijder het verweesde part. WP-CLI helpt voor batch renames.

5. Synced pattern-ID's die veranderen bij import

Synced patterns (de rebrand van reusable blocks) leven in het wp_block custom post type. Elke instance verwijst naar een ref-ID. Importeer je de XML-dump in de nieuwe install, dan worden die ID's opnieuw toegekend. De block-markup zegt nog steeds "ref":4821. Post 4821 in de nieuwe database is een draft-artikel, geen synced pattern. Het blok rendert als een lege placeholder.

De importer waarschuwt niet. Vind ze met wp post list --post_type=wp_block, bouw een oud-naar-nieuw ID-map en voer dan een search-replace uit op post_content voor elke "ref":\d+-verwijzing. WP-CLI search-replace handelt geserialiseerd PHP correct af. De gewone REPLACE() van MySQL niet.

wp post list --post_type=wp_block --fields=ID,post_title --format=csv > new-blocks.csv
wp search-replace '"ref":42' '"ref":918' wp_posts --include-columns=post_content --dry-run

Wat stilletjes wegviel

6. Navigation block-menu-items opgeslagen als wp_navigation posts

Het Navigation block schrijft zijn menustructuur niet naar wp_options zoals classic menus deden. Het maakt een wp_navigation-post aan en slaat menu-items op als inner blocks van die post. Komt die navigation-post niet over bij de import (of komt hij over met een nieuwe ID), dan laadt het Navigation block in je header-template leeg. De header toont het logo en een leegte.

Dit beet ons twee keer. Eén keer omdat de WXR-export wp_navigation CPT-entries standaard oversloeg. Eén keer omdat de nieuwe install zijn eigen automatisch aangemaakte navigation-post had die de ID-race won. Exporteer navigation-posts altijd expliciet. Zet altijd de ref-attribute van het Navigation block na de import.

7. Block hooks die injecteren waar je niet om vroeg

WordPress 6.4 introduceerde block hooks, en 6.5 breidde ze uit. Ze hangen blokken automatisch aan andere blokken op basis van metadata. Erf je een theme of plugin die hooks registreert, dan honoreert de Site Editor ze zonder dat het in de visuele tree zichtbaar wordt.

We vonden een 'related posts'-block van een externe plugin dat aan elke core/post-content hing. Het renderde prima, maar vuurde ook zes extra database queries per pagina af. We schakelden de hook uit in functions.php met het hooked_block_types-filter en wonnen ongeveer 90ms per request terug.

8. apiVersion 3 custom blocks en de iframed editor

Vanaf WordPress 6.3 draait de post-editor canvas standaard in een iframe voor elke block theme. Custom blocks gebouwd tegen apiVersion 2 laden hun scripts nog in het parent-document, niet in de iframe. Dus het edit-script van je block ziet de DOM van de iframe nooit en rendert leeg in de editor. Op de frontend rendert het prima, en daarom glipt dit langs de QA.

Bump "apiVersion": 3 in block.json, registreer edit-assets via wp_enqueue_block_assets en test binnen de iframe. Oude apiVersion 2-blokken die werkten in 6.2 lijken kapot in 6.5 zonder console-error in het parent window.

9. Featured image sizes overruled door theme.json

Stel settings.layout.contentSize en settings.layout.wideSize in en je merkt misschien dat het Post Featured Image block de expliciete width en height-attributen die je in de template hebt gezet negeert. Het blok beperkt zichzelf tot de max-width van de layout. We hadden editors die 2400px hero-images uploadden en ze terugzagen op 720px, omdat het template part binnen een constrained group leefde.

Wikkel featured image blocks in een aligned (full of wide) group, of zet useRootPaddingAwareAlignments bewust. De inspector van de Site Editor laat niet zien dat de constraint twee niveaus hoger wordt toegepast.

10. Polylang language switcher gedupliceerd per vertaling

De uitgever draait Nederlands en Frans. Het language switcher block van Polylang zit in het header template part. Polylang behandelt elke vertaalde post als een aparte post. De Site Editor cachte de gerenderde template part-output per post. De NL-versie van de switcher bewerken muteerde de gecachte output die op bepaalde CDN-configuraties aan FR-bezoekers werd geserveerd.

De fix is om de switcher dynamisch te renderen (een PHP-blok) in plaats van als statisch blok binnen een template part, en de template part-cache te flushen na elke switcher-edit. De Polylang-docs behandelen het dynamische blok. De Site Editor waarschuwt niet dat je language-aware output statisch cachet.

Wat dagen na launch brak

11. wp_global_styles revisions-tabel die opzwelt

Elke keer dat een editor Global Styles opent in de Site Editor en iets aanpast, schrijft WordPress een revisie naar de wp_global_styles-post en een rij in wp_posts. Na drie weken had de uitgever 1.840 revisies van zijn global styles-post. De Site Editor deed er 14 seconden over om Global Styles te laden, omdat hij alle revisies inlaadt om het history-panel te vullen.

WordPress core ruimt global styles-revisies niet automatisch op. Plan een wekelijkse wp post delete voor revisies ouder dan 30 dagen, of zet WP_POST_REVISIONS op een eindig getal in wp-config.php. De default van unlimited gaat je ooit bijten.

12. Hardcoded wp-content URL's in geplakte markup

Page-builder content uit 2018 had hardcoded verwijzingen naar https://oldsite.be/wp-content/uploads/2019/.... De WXR-importer herschrijft attachment-URL's voor gekoppelde media, niet voor willekeurige tekstinhoud-URL's. Dus img src-attributen binnen Custom HTML-blokken wezen na de migratie nog steeds naar het oude domein.

De dag dat de DNS van het oude domein werd geredirect, braken die afbeeldingen. We draaiden wp search-replace 'oldsite.be/wp-content' 'newsite.be/wp-content' --include-columns=post_content,post_excerpt,meta_value als launch-day stap. Check ook de options-tabel: sommige plugins bewaren image-URL's daar.

13. De Reset to Default-knop van de Site Editor

Waarschuwing

Elke gebruiker met de Site Editor-capability kan op 'Reset to default' klikken bij een template en zo een aangepaste versie wissen. Er is geen tweestapsbevestiging. Er is geen per-template version history. De wijziging schrijft direct en blijft staan bij opslaan.

We verloren twee keer een custom single-post template voordat we de capability afsloten. Beperk edit_theme_options tot senior editors, en overweeg een plugin die Site Editor-edits blokkeert op productie. Volg template-wijzigingen via Git in de theme-bestanden, niet via de editor.

14. De cache plugin die negen uur lang de oude theme bleef serveren

De cache-laag (in dit geval WP Rocket, maar het patroon herhaalt zich met LiteSpeed Cache en W3 Total Cache) bewaart HTML gekoppeld aan URL. Themes switchen invalideert die cache niet. De uitgever ging live, de helft van het redactieteam zag de nieuwe theme, en de meeste uitgelogde lezers bleven de oude theme zien tot de cache-TTL verliep.

Flush elke cache-laag in deze volgorde bij een theme switch: object cache, dan page cache, dan CDN-cache. Verifieer met een uitgelogd incognito-request en een ongeauthenticeerde curl -I die de x-cache-header checkt. De Site Editor flusht geen downstream caches. Niets in de WordPress-admin gaat je vertellen dat de publieke site nog draait op de HTML van gisteren.

Patronen die we nu op elke block-theme migratie hanteren

De cheatsheet valt samen in een workflow. Diff gerenderde post-HTML voor en na, niet alleen database-tabellen. Lock je theme.json-versie expliciet. Resolve elke shortcode voor je de oude theme uitzet. Exporteer wp_navigation- en wp_block-posts expliciet. Bump custom blocks naar apiVersion 3. Map synced pattern-ID's van oud naar nieuw. Prune wp_global_styles-revisies op een schema. Flush elke cache-laag bij cutover. Niets hiervan staat in de officiële migratiegids. Alles heeft ons op enig moment een week post-launch firefighting gekost.

Toen we de nieuwe block theme bouwden voor die Belgische uitgever, was de lastigste bug valkuil negen: 800 gearchiveerde feature-artikelen met featured images die op mysterieuze wijze op 720px breed werden gecropt. We losten het op door het post-content template part te herschrijven zodat de featured image in een wide-aligned group werd verpakt, en daarna elke betroffen post opnieuw op te slaan via WP-CLI. Het werk was onderdeel van een bredere legacy migratie weg van een custom page-builder waar de uitgever overheen was gegroeid.

Zit je om middernacht naar de Site Editor te kijken die je import zonder errors accepteert, draai dan een content-length diff voor je gaat slapen. Die ene vijfminuten-audit vangt het grootste deel van valkuil één, drie en twaalf.

Kern

De WordPress Site Editor gooit geen errors bij kapotte imports; hij herschrijft ze stilletjes. Diff de gerenderde post-HTML voor en na elke block-theme migratie.

FAQ

Waarschuwt de WordPress Site Editor je als hij kapotte Custom HTML-blokken herschrijft?

Nee. Hij reconstrueert de markup stilletjes via de block validator en bewaart het resultaat. Diff de gerenderde post-HTML voor en na de migratie om het verlies te vangen.

Hoe migreer ik synced patterns zonder referenties te verliezen?

Exporteer het wp_block CPT expliciet, bouw na de import een oud-naar-nieuw ID-map en gebruik daarna WP-CLI search-replace om elke ref:<id>-token in post_content te herschrijven.

Wat is de veiligste manier om een custom block naar apiVersion 3 te bumpen?

Zet apiVersion 3 in block.json, enqueue de edit-assets via wp_enqueue_block_assets zodat ze in de iframe laden, en test het block daarna binnen de iframed editor van een block theme.

Waarom rendert mijn featured image op 720px na een migratie naar een block theme?

Het template part beperkt de content waarschijnlijk tot de contentSize van theme.json. Wikkel het Post Featured Image block in een wide- of full-aligned group om die constraint te ontwijken.

wordpressmigrationlegacy sitesphparchitecture

Iets bouwen?

Start een project