Desenvolupament web
Un fantasma a la home i 19.000 imatges a la cua
Dos frentes el mateix dia: un hydration mismatch sense traça a la home i un script per pujar 19.000 fitxers de WordPress a Payload. El que va fallar i com ho vam resoldre.

Un error que només sortia a producció
Després dels últims canvis, la home va començar a petar de forma intermitent. En local, res. En producció, React queixant-se amb un Objects are not valid as a React child. Sense traça útil: hidratació. Quan el servidor renderitza una cosa i el client una altra, l’error surt així, i el component culpable quasi mai no és el que diu el missatge.
El que va caure, en ordre
Primer, km i lectures: després de la fase 2.8 poden arribar buits, i un toLocaleString sobre null arrossegava la pàgina sencera. Guard i punt:
km: d.km != null ? d.km.toLocaleString("ca-ES", { maximumFractionDigits: 1 }) : ""
Després, el culpable de veritat: un text de l’AiPanel. L’apòstrof tipogràfic de Diga'ns, escapat dins d’una cadena, no es parsejava igual a servidor i client. La frase, reescrita:
- Diga'ns les dates i com t'anesa, ...
+ Omple el formulari, i et proposem un pla ...
I per tancar, bisect a mà: disable de <Hero />, <AccountButton />, <BottomNav />, <ConsentBanner />, l’script d’Umami i el <Header /> sencer, un commit per component. Cap no era. Tot revertit en bloc. Amb el text corregit i els guards posats, el fantasma no ha tornat.
19.000 fitxers a la cua
A la tarda, l’altra cara: el script T3.3 per migrar els mèdia de WordPress cap a Payload. Indexa tots els attachments (imatges i PDF), els puja a /api/media amb 8 workers en paral·lel i guarda el mapping wp_id → media_id en un JSON, perquè es pugui re-executar sense duplicar res.
Primera volta, tres errors.
El mime era mentida. upload_media enviava tot amb image/jpeg hardcoded. PNG, GIF, WebP i PDF sortien amb el tipus equivocat. Ara s’infereix per fitxer.
El prefix no era el que semblava. La detecció de taules es quedava el primer *_posts que trobava, però amb plugins tipus actionscheduler_ la primera no és la del core. Ara exigeix que al costat hi hagi X_postmeta.
La cursa de la sessió. Per pujar multipart cal treure el Content-Type: application/json de la sessió. Ho fèiem mutant la sessió compartida, i amb 8 workers això és una race: Payload responia 400 Invalid JSON a l’atzar. La solució, passar el header buit només en aquella crida:
r = self.session.post(url, files=files, timeout=60, headers={"Content-Type": ""})
Per validar el correctiu sense moure tota la biblioteca, flag nou: --only-mime image/png --limit 500.
Què queda
La home, estable. Els guards, posats. El script de mèdia llest per a la passada completa: 19.000 fitxers que ja tenen on anar.