404 sulle schede prodotto dopo il passaggio a PrestaShop 9
Il sintomo
Dopo il passaggio a PrestaShop 9, le schede prodotto raggiunte dai vecchi indirizzi, quelli con il nome della categoria davanti all’ID (/categoria/12-nome-prodotto.html), rispondono 404. Gli indirizzi senza categoria (/12-nome-prodotto.html) funzionano e il negozio si naviga normalmente. Il danno arriva da fuori: i link di altri siti, i segnalibri e le pagine già indicizzate da Google puntano ancora ai vecchi indirizzi, e alla prossima visita trovano una pagina di errore al posto del prodotto.
Cosa sembrava e non era
Il primo sospetto, davanti a un 404 dopo una migrazione, è di solito l’.htaccess: regole di riscrittura perse, mod_rewrite spento. Qui non c’entra: la richiesta arriva a PrestaShop, e la pagina che si vede è la pagina «non trovato» del tema, generata dal negozio.
Il secondo sospetto è il nome del prodotto: se il link_rewrite è cambiato, l’indirizzo non corrisponde più. Ma in quel caso PrestaShop non risponde 404. Con l’ID giusto e il nome sbagliato reindirizza con un 301 all’indirizzo corretto, purché il reindirizzamento canonico sia attivo con l’opzione «301 Moved Permanently» (PS_CANONICAL_REDIRECT = 2). Il 404 compare solo quando l’indirizzo ha una forma che nessuna regola riconosce.
La causa vera
PrestaShop costruisce e riconosce gli indirizzi con una regola per ogni tipo di pagina. Per i prodotti la regola predefinita nelle versioni 1.7 e 8 è questa (la forma di 1.7.1.2, 1.7.8.10 e 8.1):
{category:/}{id}{-:id_product_attribute}-{rewrite}{-:ean13}.html
cioè il nome della categoria predefinita del prodotto, poi l’ID, l’eventuale combinazione, il nome e l’eventuale EAN13. Nella 9.0 la regola predefinita, nel file classes/Dispatcher.php, è diventata:
{id}{-:id_product_attribute}-{rewrite}.html
Niente categoria, niente EAN13. L’abbiamo verificato confrontando i sorgenti di 1.7.1.2, 1.7.8.10 e 8.1 (regola con la categoria) con quelli di 9.0.0 e 9.1.4 (regola senza). È l’unica regola predefinita che cambia tra 8.1 e 9.0, e la modifica è nel changelog ufficiale della 9.0 (n. 37467, «SEO delete category in URL product scheme during installation»).
La regola predefinita vale però solo finché nessuno ne ha salvata una. Il Dispatcher carica prima le regole predefinite e poi le sovrascrive con quelle salvate nella tabella ps_configuration, con chiavi come PS_ROUTE_product_rule e PS_ROUTE_category_rule: sono i valori della pagina SEO e URL del back office. Quelle chiavi compaiono quando qualcuno salva quella pagina: in un negozio che non l’ha mai salvata di solito mancano. Nel nostro negozio, che sta sulla 9.1, non ce n’è nemmeno una. Un negozio che non l’ha mai salvata usa la regola predefinita della versione in esecuzione: cambiando versione cambiano gli indirizzi, senza che nessuno abbia toccato nulla.
Il progetto PrestaShop lo ha riconosciuto come un problema serio di SEO. La segnalazione n. 40063 («Legacy Category/Product URLs Return 404 With New Default Routing») è stata chiusa il 26 novembre 2025 dalla correzione nel modulo di aggiornamento ufficiale, la PR n. 1535 («Persist current product route when upgrading to 9.0.0»), che salva la regola in uso quando non c’è. La correzione protegge chi aggiorna con quel modulo. Non protegge chi monta un’installazione nuova di PrestaShop 9 e ci importa i dati del vecchio negozio, che è la migrazione descritta nella segnalazione (da 1.6.1.4 a 9.0.1, con installazione pulita e importazione): lì lo schema è quello nuovo dal primo giorno.
La prova, fatta il 9 ottobre 2026 su PrestaShop 9.1 senza regole salvate (una copia di prova del nostro negozio, più richieste di sola lettura sul negozio stesso), con il prodotto 2 e la sua categoria predefinita compliance-ue:
/2-eu-withdrawal-button.html: 200./compliance-ue/2-eu-withdrawal-button.html: 404./2-un-altro-nome.html(ID giusto, nome sbagliato): 301 verso/2-eu-withdrawal-button.html.
La correzione
Ci sono due strade. La A riporta lo schema vecchio e funziona anche se il sito con gli indirizzi nuovi è già online. La B tiene lo schema nuovo, più corto, e reindirizza quelli vecchi.
Strada A: tornare allo schema vecchio.
- Nel back office: Parametri Negozio, Traffico & SEO, SEO & URLs, sezione «Schema degli URL», campo «Percorso ai prodotti». Nel codice, quel campo del form è collegato alla chiave
PS_ROUTE_product_rule. - Inserire la regola che il vecchio negozio usava. Se non era mai stata modificata, è quella predefinita fino alla 8.x:
{category:/}{id}{-:id_product_attribute}-{rewrite}{-:ean13}.html. Se era personalizzata, va rimessa quella. - Salvare e provare un prodotto.
Abbiamo provato l’effetto scrivendo quella chiave sulla copia di prova (non dal form, ma è la chiave che il form scrive). Il vecchio indirizzo con la categoria torna a rispondere 200. L’indirizzo senza categoria, che nel frattempo qualcuno potrebbe aver già linkato, diventa un 301 verso quello con la categoria, non un 404. Anche una categoria sbagliata nell’indirizzo viene corretta con un 301.
Strada B: tenere lo schema nuovo e reindirizzare i vecchi indirizzi. In Apache, nel file .htaccess, prima del blocco compreso tra i commenti «~~start~~» e «~~end~~» di PrestaShop: ciò che sta fuori da quel blocco PrestaShop lo conserva quando rigenera il file (lo dice il commento stesso). Sostituire l’elenco delle lingue con i prefissi del proprio negozio:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^(it|en|fr|es|de|pl|nl)/[^/]+/([0-9]+(-[0-9]+)?-[^/]+\.html)$ https://%{HTTP_HOST}/$1/$2 [R=301,L]
RewriteCond %{REQUEST_URI} !^/(it|en|fr|es|de|pl|nl)/
RewriteRule ^[^/]+/([0-9]+(-[0-9]+)?-[^/]+\.html)$ https://%{HTTP_HOST}/$1 [R=301,L]
</IfModule>
La prima regola riscrive gli indirizzi con prefisso di lingua e categoria, la seconda quelli della lingua predefinita, che nel nostro negozio non ha prefisso. La condizione in mezzo evita di toccare gli indirizzi nuovi della forma /en/12-nome.html. Provate sulla copia di prova: /compliance-ue/2-eu-withdrawal-button.html e /en/compliance-ue/2-eu-withdrawal-button.html rispondono 301 verso gli indirizzi senza categoria, mentre /2-eu-withdrawal-button.html, /en/2-eu-withdrawal-button.html e una pagina CMS restano 200. Se il nome del prodotto nel frattempo è cambiato, il secondo salto lo fa PrestaShop con il reindirizzamento canonico.
Come verificare che tenga, e come evitarlo
- Prima di toccare qualcosa, leggere cosa c’è di salvato:
SELECT name, value FROM ps_configuration WHERE name LIKE 'PS_ROUTE%';(con il prefisso delle tabelle della propria installazione). Nessuna riga significa che il negozio usa le regole predefinite della versione installata. - Prendere un campione di vecchi indirizzi prodotto dalla vecchia sitemap o da Search Console e controllarne il codice di risposta con
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' INDIRIZZO. Vanno bene 200 oppure un 301 verso la scheda giusta. Mai 404. - Provare anche un prodotto con combinazioni, il cui indirizzo contiene l’ID della combinazione (
/12-3-nome.html), e ripetere la prova con il prefisso di ogni lingua. - Dopo ogni cambio di schema rigenerare la sitemap: è un file scritto dal modulo al momento della generazione, e contiene gli indirizzi dello schema in uso quel giorno.
- Prima di migrare, copiare la regola prodotto in uso dal campo «Percorso ai prodotti» e tenerla nel progetto. Se si monta un’installazione nuova e si importano i dati, impostare e salvare quella regola prima di aprire il sito: da quel momento è un valore salvato e non dipende più dal valore predefinito della versione.
- Se si aggiorna con il modulo di aggiornamento, provare prima su una copia e confrontare il campo «Percorso ai prodotti» prima e dopo, che è il controllo indicato anche nella PR della correzione.
Chi preferisce gestire i reindirizzamenti dal back office invece che in .htaccess può usare cll_seoredirect, il nostro modulo per i redirect 301/302 e il monitoraggio dei 404: accetta regole con espressioni regolari e sostituzione dei gruppi ($1), quindi la regola della strada B si scrive lì allo stesso modo. L’abbiamo provata sulla copia di prova. Il problema e le correzioni descritti qui valgono comunque con qualunque strumento.
