Questo sito è generato come esportazione statica di Next.js e pubblicato su un hosting condiviso (Altervista, dopo un primo tentativo su un altro provider). Il procedimento sembra semplice — build, zip, carica — ma tra la prima build e il sito effettivamente online ci sono stati tre problemi reali, non banali da diagnosticare. Li riporto qui con lo stesso metodo delle altre guide: sintomo, diagnosi, causa, correzione.
Il bug che bloccava ogni caricamento: cartelle con $ nel nome
Sintomo
Il pannello dell'hosting rifiutava l'estrazione dello zip con un generico «si è verificato un errore nell'estrazione dei file». Passando a un caricamento via FTP, l'errore diventava più preciso:
Copia file lato remoto fallita.
Impossibile cambiare la directory in /homelab/accesso-remoto-vpn/__next.homelab/$d$slug/__PAGE__.txt: Permesso negatoDiagnosi
Il primo sospetto — uno zip malformato — si è rivelato sbagliato: ricreato con percorsi corretti (/ invece di \, l'errore tipico di Compress-Archive di PowerShell su Windows), l'estrazione falliva comunque. Il messaggio FTP però indicava con precisione il colpevole: un tentativo di cd dentro una cartella chiamata $d$slug.
find out/ -name '*$*' | wc -l
# 6969 cartelle, una per ogni rotta dinamica del sito (articoli, progetti, schede homelab, tag, categorie), tutte con lo stesso pattern: __next.<sezione>/$d$slug/__PAGE__.txt. Sono file generati da Next.js per il prefetch lato client dei link: quando il visitatore passa il mouse su un collegamento, il browser scarica in anticipo questi payload per rendere la navigazione istantanea invece di un caricamento di pagina intera.
Causa
L'hosting (sia l'estrattore zip del pannello sia il server FTP) rifiuta di creare o attraversare percorsi che contengono il carattere $, quasi certamente per prudenza verso script lato server che potrebbero interpretarlo come l'inizio di una variabile di shell.
Correzione
I file di prefetch non sono indispensabili: se mancano, il router di Next.js fa semplicemente una navigazione normale invece di quella istantanea, senza errori visibili per chi visita il sito. La soluzione è stata rimuoverli dalla build prima di pubblicarla, in modo automatico e ripetibile a ogni build:
function removeDollarPaths(dir: string): number {
let removed = 0;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.name.includes("$")) {
fs.rmSync(full, { recursive: true, force: true });
removed += 1;
} else if (entry.isDirectory()) {
removed += removeDollarPaths(full);
}
}
return removed;
}Risultato: 543 file prima della pulizia, 474 dopo, zero caricamenti falliti da allora.
Nota
Il sintomo iniziale (errore generico in fase di estrazione zip) e quello successivo (permesso negato via FTP) erano la stessa causa vista da due strumenti diversi. Cambiare metodo di caricamento ha aiutato a ottenere un messaggio di errore più preciso, non a risolvere il problema da solo.
Il limite non scritto: niente header di sicurezza
Il sito genera un .htaccess con le intestazioni di sicurezza standard (Content-Security-Policy, X-Frame-Options, Strict-Transport-Security, ecc.), verificate anche dal server di anteprima locale. Una volta online, però, nessuna di queste intestazioni arrivava al browser: solo Server, Content-Type, Vary, niente altro.
Le altre direttive dello stesso file (ErrorDocument, il redirect HTTPS, il tipo MIME forzato sull'immagine Open Graph) funzionavano regolarmente, quindi il file veniva letto. Il sospetto — un modulo Apache assente — è stato confermato dalla documentazione ufficiale dell'hosting: l'elenco delle direttive .htaccess supportate include ErrorDocument, DirectoryIndex, RewriteEngine/RewriteRule/RewriteCond, Redirect* e AddHandler. La direttiva Header (il modulo mod_headers, da cui dipendono tutte le intestazioni di sicurezza) non compare: non è un'opzione disattivata da qualche parte, è semplicemente assente per tutti gli utenti di questo hosting.
Non esiste un modo per aggirarlo lato server. Le alternative valutate:
- un meta tag
<meta http-equiv="Content-Security-Policy">copre solo una parte della policy (nienteX-Frame-Options,HSTSoPermissions-Policy, che esistono solo come intestazioni HTTP vere); - un proxy come Cloudflare davanti al dominio, con un Worker che aggiunge le intestazioni mancanti all'edge, resterebbe l'unica soluzione completa.
Per ora il limite è solo documentato, non nascosto: è un compromesso consapevole di un hosting gratuito, non un errore di configurazione.
Un bug di impaginazione: tabelle che spezzavano le parole a metà
Le pagine con tabelle dense (le specifiche hardware dei nodi, ad esempio) mostravano un problema curioso: parole come «NVIDIA» finivano spezzate a metà riga («NVIDI» / «A»), invece di andare a capo in modo pulito.
La causa era una combinazione di due regole CSS: la tabella era vincolata a width: 100% del contenitore, e il testo delle celle aveva overflow-wrap: anywhere (ereditato dalle regole generali del testo, pensate per evitare che URL molto lunghe sfondassero il layout). Con molte colonne strette, il browser preferiva spezzare le parole pur di restare nella larghezza assegnata, invece di andare a capo tra una parola e l'altra.
La correzione: overflow-wrap: normal sulle celle (va a capo solo tra le parole, come un paragrafo normale) e, per le tabelle più dense, una struttura con meno colonne (i nodi in colonna, gli attributi in riga, invece del contrario) così il contenuto ha più spazio orizzontale senza bisogno di scorrimento.
La lezione
I tre problemi condividono lo stesso schema: un sintomo generico (errore di estrazione, intestazioni assenti, testo malformato), una causa non ovvia legata a un limite specifico dell'ambiente di destinazione, e una correzione che va resa permanente nel processo di build — non un intervento manuale da ripetere a ogni pubblicazione. Il controllo check:launch e lo script postbuild.ts di questo sito esistono proprio per questo: trasformare un problema risolto una volta in un problema che non si ripresenta.