Hai un menu solido. Ai clienti piace. Funziona. Ma ogni volta che testi il tuo negozio con PageSpeed Insights, Google evidenzia un file specifico: lo script del tuo menu. L’etichetta dice “render-blocking JavaScript”, il file è 120KB, e sta aggiungendo 600 millisecondi al tuo LCP mobile. Sai che devi risolverlo, ma l’ultima volta che qualcuno ha suggerito di usare defer, il menu si è rotto. Metà dei dropdown hanno smesso di funzionare, il toggle mobile non ha fatto nulla, e hai fatto rollback entro un’ora.
Questo è un problema comune. Gli script che bloccano il rendering sono uno dei colli di bottiglia di performance più impattanti nei negozi Shopify, e il menu è un trasgressore frequente. Ma differire JavaScript non è semplicemente aggiungere un attributo a un tag script. Se lo fai male, il menu non funzionerà. Se lo fai bene, puoi togliere mezzo secondo dal tuo LCP mobile senza toccare una singola riga della logica del menu.
Questo articolo spiega cosa significa veramente il blocco del rendering, perché async e defer si comportano diversamente, e come caricare lo script del tuo menu senza rompere la funzionalità.
- Gli script che bloccano il rendering fermano tutto il rendering della pagina fino a quando non scaricano, analizzano ed eseguono.
deferscarica gli script in parallelo ma ritarda l'esecuzione fino al completamento dell'analisi dell'HTML.asyncscarica ed esegue immediatamente, il che può rompere i menu che dipendono dall'ordine del DOM.type="module"rimanda per impostazione predefinita e supporta le funzionalità moderne di JavaScript.- La maggior parte dei problemi al menu dopo il differimento è causata dall'orario di inizializzazione, non dall'attributo defer stesso.
Cosa Significa Veramente il Blocco del Rendering
Quando un browser incontra un tag <script> nell’HTML senza alcun attributo di caricamento, si ferma. Mette in pausa l’analisi dell’HTML, scarica lo script, lo analizza, lo esegue, e solo allora continua a costruire la pagina. Questo si chiama comportamento sincrono o che blocca il rendering.
La logica dietro questo comportamento è vecchia ma ragionevole: JavaScript può modificare il DOM usando document.write(), quindi il browser presume che ogni script potrebbe cambiare la struttura della pagina e quindi deve essere eseguito prima di procedere.
Ecco il problema. Il tuo script menu non usa document.write(). Non ha bisogno di essere eseguito prima che il resto della pagina sia analizzato. Ma il browser non lo sa, quindi blocca comunque.
Secondo la stessa documentazione di Chrome su come ottimizzare il Largest Contentful Paint, eliminare il JavaScript che blocca il rendering è uno dei miglioramenti di più alto impatto che puoi fare. Quando uno script nel head del documento blocca per 400 millisecondi, ogni elemento sottostante nel DOM — la tua immagine hero, il tuo titolo, la tua griglia di prodotti — attende 400 millisecondi di più per dipingere.
Le Tre Strategie di Caricamento: Default, Defer e Async
Ci sono tre modi in cui un browser può caricare uno script.
Default (Sincrono)
<script src="menu.js"></script>
Il browser interrompe l’analisi dell’HTML, scarica menu.js, lo analizza, lo esegue, quindi riprende. Questo blocca il rendering per l’intera durata.
Defer
<script src="menu.js" defer></script>
Il browser scarica menu.js in parallelo con l’analisi dell’HTML ma non lo esegue fino a quando l’intero documento HTML non è stato analizzato. Più script differiti vengono eseguiti nell’ordine in cui appaiono nell’HTML.
Async
<script src="menu.js" async></script>
Il browser scarica menu.js in parallelo con l’analisi dell’HTML e lo esegue non appena il download è completato, anche se l’analisi dell’HTML non è terminata. L’ordine di esecuzione è imprevedibile se più script usano async.
Ecco una tabella che riassume il comportamento:
| Attributo | Download | Esecuzione | Blocca Analisi HTML | Ordine di Esecuzione |
|---|---|---|---|---|
| Nessuno (default) | Blocca l’analisi | Immediata | Sì | Sequenziale |
defer |
Parallelo | Dopo l’analisi HTML | No | Sequenziale |
async |
Parallelo | Non appena pronto | Sì (durante l’esecuzione) | Imprevedibile |
Per la maggior parte dei menu di navigazione, defer è la scelta giusta. Mantiene gli script in ordine, il che è importante quando uno script dipende da un altro (ad esempio, uno script di menu che si basa su una libreria di utilità). E garantisce che il DOM sia pronto prima che lo script venga eseguito, il che è fondamentale per qualsiasi codice che interroga gli elementi.
Perché Defer Rompe Alcuni Menu (E Come Risolverlo)
Aggiungi defer al tag dello script del menu, ricarichi la pagina, e il menu non funziona. I dropdown non si aprono. Il toggle mobile non fa nulla. Rimuovi defer, e tutto funziona di nuovo. Cosa è successo?
Il problema è quasi sempre l’orario di inizializzazione. Ecco le tre cause più comuni.
Problema 1: Lo Script Viene Eseguito Prima che il JavaScript del Tema Sia Pronto
Molti temi Shopify hanno un oggetto JavaScript globale o una funzione di inizializzazione su cui altri script dipendono. Se il tuo script menu viene eseguito prima che quell’oggetto sia creato, fallisce.
Esempio:
// theme.js (caricato per primo)
window.Theme = { utils: { ... } };
// menu.js (caricato per secondo, si aspetta che Theme esista)
Theme.utils.initDropdown();
Se menu.js è differito ma theme.js non lo è, menu.js potrebbe essere eseguito prima che theme.js finisca di eseguire, e Theme sarà indefinito.
Soluzione: Differisci entrambi gli script, o assicurati che menu.js verifichi le dipendenze prima di inizializzare.
if (typeof Theme !== 'undefined') {
Theme.utils.initDropdown();
}
Problema 2: Gli Script Inline Vengono Eseguiti Prima degli Script Differiti
Gli script inline (JavaScript scritto direttamente nell’HTML, non caricato da un file esterno) vengono eseguiti nell’ordine del documento mentre vengono incontrati. Gli script differiti vengono eseguiti dopo che l’intero HTML è stato analizzato, il che significa dopo tutti gli script inline.
Se hai uno script inline che tenta di inizializzare il menu, e la libreria del menu è differita, l’inizializzazione fallirà.
Soluzione: Sposta la logica di inizializzazione nello script differito, o avvolgi l’inizializzazione inline in un listener DOMContentLoaded.
document.addEventListener('DOMContentLoaded', function() {
if (typeof MenuApp !== 'undefined') {
MenuApp.init();
}
});
Problema 3: Race Condition con Più Script Differiti
Se il tuo menu dipende da una libreria di utilità, e entrambi sono differiti, dovrebbero essere eseguiti nell’ordine in cui appaiono nell’HTML. Ma se la libreria di utilità è caricata in modo asincrono (usando async invece di defer), l’ordine di esecuzione diventa imprevedibile.
Soluzione: Usa defer per tutti gli script interdipendenti, non async. Se devi usare async, aggiungi controlli di dipendenza espliciti o usa import dinamici.
Type=”module”: L’Alternativa Moderna
Il JavaScript moderno supporta i moduli ES6, che hanno il caricamento differito integrato.
<script type="module" src="menu.js"></script>
Gli script con type="module" rimandano automaticamente. Scaricano in parallelo ed eseguono dopo il completamento dell’analisi dell’HTML, proprio come defer. Supportano anche import e export, il che rende la gestione delle dipendenze esplicita.
Se il tuo script menu è scritto come modulo, puoi importare le dipendenze direttamente:
import { initDropdown } from './dropdown.js';
initDropdown();
Questo elimina i problemi di timing che causano breakage con il tradizionale defer, perché il browser sa esattamente quali script dipendono da quali.
Lo svantaggio: i browser più vecchi (Internet Explorer, versioni molto vecchie di Safari) non supportano i moduli. Ma secondo Can I Use, oltre il 95% del traffico globale dei browser supporta i moduli ES6 a partire dal 2025. Per la maggior parte dei negozi Shopify, questo è sicuro.
Considerazioni Specifiche di Shopify
I temi Shopify caricano gli script in modi diversi a seconda dell’architettura del tema. Ecco come funziona il differimento nei modelli più comuni.
Script Tags in Theme.liquid
Se il tuo script menu è caricato in theme.liquid come questo:
{{ 'menu.js' | asset_url | script_tag }}
Il filtro script_tag non aggiunge defer per impostazione predefinita. Devi sostituirlo con un tag <script> manuale:
<script src="{{ 'menu.js' | asset_url }}" defer></script>
App Embed Blocks
Se il tuo menu è installato come app embed (comune per le app menu di terze parti), l’app controlla come viene caricato il suo script. Non puoi aggiungere defer tu stesso. Devi verificare con lo sviluppatore dell’app o passare a un’app menu che carica già gli script in modo asincrono.
Strumenti come Navi+ caricano il loro JavaScript con defer per impostazione predefinita, il che è uno dei motivi per cui hanno prestazioni migliori nei test LCP rispetto alle app menu legacy.
Section Rendering e Script Differiti
I temi Shopify 2.0 caricano le sezioni in modo dinamico. Se una sezione include uno script differito, e la sezione viene aggiunta alla pagina dopo il caricamento iniziale (ad esempio, tramite AJAX per quick view o infinite scroll), lo script potrebbe non essere eseguito come previsto.
Soluzione: Usa DOMContentLoaded solo per il caricamento iniziale della pagina. Per le sezioni aggiunte dinamicamente, attiva l’inizializzazione manualmente usando un evento personalizzato o un osservatore di mutazione.
Come Testare se Defer Funziona
Prima di distribuire il caricamento di script differito al tuo negozio live, testalo accuratamente. Ecco una checklist.
- Desktop Chrome: Apri il tuo negozio, apri DevTools, vai alla scheda Network, ricarica, e verifica che lo script del menu scarichi in parallelo con altre risorse e non blocchi il documento.
- Simulazione mobile: In DevTools, passa alla emulazione mobile (iPhone o Android), limita la rete a “Fast 3G”, e ricarica. Il menu dovrebbe comunque inizializzarsi correttamente.
- Cross-browser: Testa in Safari, Firefox, e Edge. Gli script modulo si comportano leggermente diversamente in Safari.
- Dispositivo reale: Apri il negozio su un telefono effettivo su una connessione mobile reale. L’emulazione è utile, ma la latenza del mondo reale espone i bug di timing che i test desktop mancano.
- Controllo funzionalità: Apri ogni dropdown, testa il toggle del menu mobile, fai clic su ogni link, e verifica che nulla si rompa.
Prima di fare il pushEsegui un test PageSpeed Insights sulla stessa pagina prima e dopo aver abilitato defer. Dovresti vedere un calo misurabile nell'LCP e una riduzione nelle risorse che bloccano il rendering. Se l'LCP non migliora, lo script potrebbe non essere stato il collo di bottiglia, oppure potrebbero esserci altre risorse di blocco da affrontare.
Quando Async Ha Senso (E Quando Non Lo Ha)
async è appropriato per script che sono completamente autonomi e non dipendono dall’ordine del DOM o da altri script. Gli esempi includono tracker di analytics, widget di chat, e alcuni script pubblicitari.
Per i menu di navigazione, async è rischioso. Il menu ha bisogno che il DOM sia pronto, spesso dipende da utilità del tema, e di solito ha bisogno di essere eseguito prima che il cliente interagisca con la pagina. Se lo script viene eseguito mentre l’HTML è ancora in fase di analisi, potrebbe tentare di interrogare elementi che non esistono ancora.
Se stai considerando async per uno script di menu, lo script deve:
- Non dipendere da nessun altro script
- Non interrogare elementi del DOM (o avvolgere tutte le query in un controllo
DOMContentLoaded) - Essere sufficientemente piccolo che il suo tempo di esecuzione non blocchi l’analisi
La maggior parte degli script menu non soddisfa questi criteri. Resta con defer o type="module" a meno che tu non abbia un motivo specifico per usare async.
Come Appare il Caricamento di Script Menu Buono
Uno script menu correttamente ottimizzato in un negozio Shopify ha questo aspetto:
<script src="{{ 'menu.js' | asset_url }}" defer></script>
Oppure, se usi moduli:
<script type="module" src="{{ 'menu.js' | asset_url }}"></script>
Lo script scarica in parallelo con il resto della pagina, viene eseguito dopo che il DOM è pronto, e non blocca l’immagine hero o nessun altro elemento LCP dal dipingere.
Nella timeline Performance di Chrome DevTools, dovresti vedere l’immagine hero dipingere prima che lo script del menu venga eseguito. Se lo script del menu viene eseguito per primo, sta ancora bloccando.
La maggior parte dei negozi può togliere 300 a 600 millisecondi dal mobile LCP semplicemente cambiando dal caricamento di script sincrono a differito. Il menu continua a funzionare. Il cliente non nota alcuna differenza. Ma Google sì, e più importante ancora, il browser sì. La pagina diventa visibile più velocemente, il che è tutto il punto.
Questo articolo è parte della guida più ampia su Menu e LCP: come la navigazione blocca il largest contentful paint.