Mantenere vivi quattordici anni di URL
Quando ho ricostruito questo sito su Astro, i contenuti si sono spostati e gli URL sono cambiati. È il momento in cui la maggior parte delle migrazioni rompe silenziosamente il web: un post che ha vissuto allo stesso indirizzo per un decennio adesso restituisce un 404, e ogni link che vi puntava — dai risultati di ricerca, dai blogroll di altre persone, da un segnalibro che qualcuno ha salvato nel 2015 — muore con lui.
Tim Berners-Lee scrisse “Cool URIs don’t change” nel 1998, e aveva ragione allora come adesso. Perciò, prima di cancellare qualsiasi cosa, ho definito il contratto: ogni URL che il vecchio sito WordPress avesse mai servito avrebbe continuato a risolversi, o verso la sua nuova casa o verso un posto sensato. Questo post spiega come quel contratto viene fatto rispettare — e, cosa altrettanto importante, come ho dimostrato che regge.
La forma dei vecchi URL
WordPress qui usava permalink datati. Ogni post viveva a:
/some-post-slug/YYYY/MM/DD/
Quattordici anni di URL così. Alcuni li ho tenuti — i dieci migliori sono
finiti in un archivio datato — e altri li ho scartati,
perché uno snippet del 2013 sull’alterazione di un hook_views_query in Drupal
non è qualcosa che qualcuno abbia bisogno di ricevere come consiglio attuale.
Entrambi i gruppi dovevano comunque risolversi; semplicemente si risolvono verso
posti diversi.
Poi c’erano le pagine di sezione: /company/, /projects/, /blog/,
/feed/, gli archivi per categoria e per autore. Tutte indicizzate, tutte
linkate, tutte sul punto di sparire.
I redirect appartengono all’edge
Un sito statico non ha un’applicazione al momento della richiesta — non c’è un
processo PHP che ispezioni l’URL ed emetta un redirect. Questa si rivela una
caratteristica, non un problema. Su Upsun i redirect si dichiarano nel router,
in .upsun/config.yaml, e vengono gestiti all’edge prima che qualsiasi
richiesta raggiunga l’app. Sono semplicemente configurazione:
routes:
"https://www.{default}/":
redirects:
expires: 1y
paths:
'^/company/?$':
to: "https://www.{default}/about/"
regexp: true
'^/feed/?$':
to: "https://www.{default}/rss.xml"
regexp: true
Le pagine di sezione sono righe singole come queste. Quella interessante sono i post, perché sono troppi per elencarli a mano.
Una sola regex per i post mantenuti
Ogni post mantenuto segue la stessa forma, quindi una sola regola li mappa tutti:
'^/([a-z0-9-]+)/[0-9]{4}/[0-9]{2}/[0-9]{2}/?$':
to: "https://www.{default}/writing/archive/$1/"
regexp: true
Il gruppo di cattura afferra lo slug; la data viene riconosciuta e scartata;
$1 inserisce lo slug nel suo nuovo percorso d’archivio.
/coping-with-technical-debt/2012/04/03/ diventa
/writing/archive/coping-with-technical-debt/, e così fanno gli altri nove,
senza bisogno di una voce per ciascuno.
Il motivo per cui questa regola è sicura — per cui non può accidentalmente
inghiottire un URL nuovo — è la data. Ogni nuovo percorso del sito è fatto di
segmenti di parole: /writing/, /work/, /about/. Nessuno di essi può
combaciare con .../YYYY/MM/DD/, perché nessuno contiene una sequenza di
quattro-poi-due-poi-due cifre. La forma dei vecchi URL è, per costruzione,
disgiunta da quella nuova.
L’ordine conta: prima i post scartati
I post scartati sono un problema, perché combaciano anch’essi con quella regola
generica — e li manderebbe a /writing/archive/their-slug/, che non esiste.
Quindi servono una regola precedente e più specifica che li intercetti per prima
e li mandi invece all’indice dell’archivio:
'^/(curl-http-request|qualtrics-module|drush-multi-site-and-sites-php|…)/[0-9]{4}/[0-9]{2}/[0-9]{2}/?$':
to: "https://www.{default}/writing/archive/"
regexp: true
Le regole di redirect vengono valutate in ordine, quindi questa alternanza sta sopra la catch-all. Un post scartato combacia qui e si ferma; un post mantenuto prosegue fino alla regola generica sottostante. Specifico prima del generico — la lezione più antica del routing, e la più facile da dimenticare finché qualcosa non restituisce un 404.
Renderli permanenti
Per impostazione predefinita questi redirect sono temporanei — un 302. È sbagliato per una migrazione: un 302 dice ai motori di ricerca “questo spostamento potrebbe non durare, tenete indicizzato il vecchio URL.” Quello che volevo era un 301, che dice “questo è permanente, trasferite l’autorità.” Una riga per regola:
'^/company/?$':
to: "https://www.{default}/about/"
regexp: true
code: 301
Quattordici anni di link equity accumulata fluiscono verso i nuovi URL invece di evaporare.
Dimostrarlo
Una tabella di redirect che non hai testato è un’ipotesi. Ciò che ha reso la verifica meccanica è stata una decisione che avevo preso nel contenuto stesso dell’archivio: ogni post migrato porta con sé l’URL a cui viveva prima, nel suo front matter.
originalUrl: "/coping-with-technical-debt/2012/04/03/"
Quel campo è una traccia di controllo. Con esso, verificare l’intero contratto è
un ciclo su ogni vecchio URL, che asserisce che ognuno restituisce un 301
verso il posto giusto:
curl -sI "https://www.artetecha.com/coping-with-technical-debt/2012/04/03/"
# HTTP/2 301
# location: https://www.artetecha.com/writing/archive/coping-with-technical-debt/
Eseguilo su ogni originalUrl, più le pagine di sezione e gli slug scartati, e
o tutto è un 301 verso un 200 vivo — oppure hai trovato la regola che hai
sbagliato prima che lo facessero i tuoi lettori.
Il CMS era la parte che tutti notano. La tabella di redirect è la parte che decide se l’ultimo decennio di presenza di questo sito sul web sopravvive al cambiamento. Sono cinquanta righe di YAML. Ne è valsa la pena ognuna.