Skip to content

Tutto per WordPress, lo sviluppo web — e non solo

⚡ Come aggiungere defer e async per gli script WordPress in function.php

⚡ Come aggiungere defer e async per gli script WordPress in function.php

Le pagine si caricano lentamente, Google PageSpeed Insights mostra avvisi arancioni e il cliente chiede: «perché il sito è lento?». Nove volte su dieci la causa principale è il JavaScript che blocca il rendering. Il browser incontra un <script>, interrompe la costruzione del DOM, carica ed esegue lo script e solo dopo riprende. In un sito moderno con una dozzina di plugin questo ritardo si trasforma in secondi.

WordPress per molto tempo non ha offerto un modo standard per controllare il caricamento degli script. Gli sviluppatori si destreggiavano tra workaround: filtrare script_loader_tag, modificare l'output tramite clean_url o persino scrivere walker personalizzati per WP_Scripts. Ma con il rilascio di WordPress 6.3 la situazione è cambiata radicalmente e ora abbiamo un modo pulito e supportato per aggiungere defer o async a qualsiasi script senza un solo hack.

Di seguito trovi due metodi funzionanti: l'approccio nativo moderno (WP 6.3+) e il collaudato filtro script_loader_tag (WP 4.1+). Entrambi sono stati testati su progetti reali, entrambi preservano l'integrità della coda delle dipendenze.

💡 Panoramica rapida:

  • Comprendi la differenza tra defer e async e quando usare ciascuno, questo determina se le funzionalità si rompono dopo l'ottimizzazione
  • Usa il metodo nativo di WordPress 6.3+ tramite wp_enqueue_script() con il parametro strategy, l'approccio più pulito che preserva l'ordine di esecuzione
  • Se il sito gira su una versione inferiore alla 6.3, applica il filtro script_loader_tag con un array di handle, questo funziona a partire da WordPress 4.1
  • Per script multipli raccogli gli handle in un array e itera con un ciclo, un solo filtro per tutti gli script invece di fare copia-incolla

Cosa sono defer e async e quando usarli

Quando un browser incontra un tag <script> normale, fa tre cose in sequenza: interrompe il parsing dell'HTML, carica lo script, lo esegue. Solo dopo torna all'HTML. In una pagina con cinque script nell'<head> questo significa che l'utente vede una schermata bianca mentre l'ultimo plugin dei commenti si carica, anche se il post stesso avrebbe potuto essere renderizzato molto prima.

Gli attributi defer e async risolvono questo problema, ma funzionano in modo diverso:

Attributo

Quando carica

Quando esegue

Ordine di esecuzione

(nessuno)

Blocca subito il parsing

Subito dopo il caricamento

In ordine DOM

defer

In parallelo al parsing

Dopo il caricamento completo del DOM

In ordine DOM

async

In parallelo al parsing

Subito dopo il caricamento

Chi carica per primo

Defer è il cavallo di battaglia per la maggior parte degli scenari. Lo script si carica in parallelo all'HTML e viene eseguito solo quando il DOM è completamente costruito. L'ordine è preservato: lo script A verrà eseguito prima dello script B, anche se B ha caricato più velocemente. Questo è critico per jQuery e tutto ciò che ne dipende.

Async è uno strumento per script indipendenti. Analytics, pubblicità, widget dei social media: non hanno bisogno del DOM, non si preoccupano dell'ordine, devono solo essere eseguiti il prima possibile. Ma se metti async su uno script che dipende da jQuery, probabilmente otterrai $ is not defined.

Regola semplice: lo script dipende da altri script o dal DOM → defer. Lo script è completamente autonomo → async. Nel dubbio, inizia sempre con defer.

Metodo 1: approccio nativo WordPress 6.3+

Da luglio 2023 un nuovo meccanismo è attivo nel core di WordPress. Le funzioni wp_register_script() e wp_enqueue_script() hanno ricevuto un quinto parametro sovraccaricato $args, un array dove puoi specificare la strategia di caricamento. Niente filtri, niente magie con le stringhe, nessun rischio di rompere l'ordine delle dipendenze.

Sintassi di base per defer:

1wp_enqueue_script(
2 'my-js-handle',
3 get_template_directory_uri() . '/js/my-script.js',
4 array('jquery'),
5 '1.0.0',
6 array(
7 'strategy' => 'defer',
8 'in_footer' => true,
9 )
10);

Per async, stessa meccanica:

1wp_enqueue_script(
2 'google-analytics',
3 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX',
4 array(),
5 '1.0.0',
6 array(
7 'strategy' => 'async',
8 'in_footer' => false,
9 )
10);

La chiave in_footer dentro l'array funziona come il vecchio parametro booleano: true mette lo script nel footer, false nell'<head>. Per defer tipicamente imposti true (lo script aspetta comunque il DOM, non ha senso caricarlo prima), per async come preferisci.

Il vantaggio principale del metodo nativo è che il core stesso verifica l'albero delle dipendenze. Se lo script A con defer dipende dallo script B, e B è registrato senza strategia (bloccante), WordPress non romperà il sito: declasserà automaticamente la strategia dello script A a bloccante. Usando script_loader_tag non hai questa protezione, il filtro inserisce semplicemente l'attributo senza guardare le dipendenze.

Importante: l'array $args è apparso in WordPress 6.3. Se un tema o un plugin deve funzionare su versioni inferiori, usa il metodo 2 o aggiungi un controllo:

1if ( version_compare( $GLOBALS['wp_version'], '6.3', '>=' ) ) {
2 // native method
3} else {
4 // script_loader_tag filter
5}

Metodo 2: filtro script_loader_tag (WordPress 4.1+)

Se il sito gira su una versione inferiore alla 6.3 o devi mantenere la retrocompatibilità, applica il collaudato filtro script_loader_tag. Esiste da WordPress 4.1 e funziona ancora perfettamente.

Il filtro si attiva subito prima che il tag <script> venga inviato all'HTML, ricevi la stringa del tag pronta, l'handle dello script e il percorso del file, e puoi sostituire src con defer="defer" src o async="async" src.

Script singolo con defer:

1function add_defer_to_my_script($tag, $handle) {
2 if ( 'my-js-handle' !== $handle ) {
3 return $tag;
4 }
5 return str_replace( ' src', ' defer="defer" src', $tag );
6}
7add_filter('script_loader_tag', 'add_defer_to_my_script', 10, 2);

Il codice va nel functions.php del tema attivo o, più correttamente, in un plugin snippet separato come Code Snippets o WPCode. Se lo metti nel functions.php di un tema child, quando cambi tema gli script torneranno a essere bloccanti e non te ne accorgerai subito.

L'handle dello script è il primo parametro che hai passato a wp_register_script() o wp_enqueue_script(). È ciò che appare nella condizione if. Non tirare a indovinare l'handle, apri il codice sorgente del plugin o del tema e trova la chiamata wp_enqueue_script.

Defer e async per script multipli

Aggiungere un filtro per script è la strada per un functions.php gonfio ed errori di copia-incolla. La soluzione giusta: un array di handle e un solo filtro con un ciclo.

1function add_defer_to_scripts($tag, $handle) {
2 $scripts_to_defer = array(
3 'my-js-handle',
4 'another-handle',
5 'third-party-lib',
6 );
7
8 foreach ( $scripts_to_defer as $defer_script ) {
9 if ( $defer_script === $handle ) {
10 return str_replace( ' src', ' defer="defer" src', $tag );
11 }
12 }
13 return $tag;
14}
15add_filter('script_loader_tag', 'add_defer_to_scripts', 10, 2);

Per async, cambiano solo l'attributo e il nome dell'array:

1function add_async_to_scripts($tag, $handle) {
2 $scripts_to_async = array(
3 'google-tag-manager',
4 'facebook-pixel',
5 'hotjar',
6 );
7
8 foreach ( $scripts_to_async as $async_script ) {
9 if ( $async_script === $handle ) {
10 return str_replace( ' src', ' async="async" src', $tag );
11 }
12 }
13 return $tag;
14}
15add_filter('script_loader_tag', 'add_async_to_scripts', 10, 2);

Entrambi i filtri possono essere agganciati simultaneamente, defer sui tuoi script, async sui tracker di terze parti. Funzionano indipendentemente e non entrano in conflitto.

Esempio pratico: Google Maps API

Google Maps è un candidato classico per defer. La mappa è tipicamente nel footer della pagina contatti, lo script scarica oltre 100 KB e l'utente non ha bisogno della mappa immediatamente. Inoltre, l'API stessa non dipende da altri script della pagina, un caso ideale.

Connessione e defer:

1// theme's functions.php
2function enqueue_google_maps() {
3 if ( ! is_page('contacts') ) {
4 return;
5 }
6
7 wp_enqueue_script(
8 'google-maps-api',
9 'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY',
10 array(),
11 null,
12 array(
13 'strategy' => 'defer',
14 'in_footer' => true,
15 )
16 );
17}
18add_action('wp_enqueue_scripts', 'enqueue_google_maps');

Stesso risultato tramite script_loader_tag:

1function add_defer_to_google_maps($tag, $handle) {
2 if ( 'google-maps-api' !== $handle ) {
3 return $tag;
4 }
5 return str_replace( ' src', ' defer="defer" src', $tag );
6}
7add_filter('script_loader_tag', 'add_defer_to_google_maps', 10, 2);

Dopo aver installato una delle due varianti, verifica assolutamente la mappa nella pagina contatti. Apri la console del browser (F12), assicurati che non ci siano errori JavaScript e che la mappa sia renderizzata correttamente. Se ottieni un errore come initMap is not a function, significa che anche il tuo script di inizializzazione deve essere marcato come defer e posizionato rigorosamente dopo la connessione all'API.

Come verificare che defer e async funzionino

Dopo l'implementazione viene la verifica. Senza non sai se l'ottimizzazione ha funzionato o è solo codice morto.

Apri il sorgente della pagina (Ctrl+U) e trova i tuoi script. Il tag <script> dovrebbe avere gli attributi:

1<script defer="defer" src="/wp-content/themes/my-theme/js/my-script.js"></script>

Se non ci sono attributi, controlla se l'handle nel filtro corrisponde all'handle reale dello script. Errore comune: in wp_enqueue_script l'handle è my-plugin-frontend, ma nel filtro è my_plugin_frontend. Trattino contro underscore, e il filtro salta silenziosamente lo script.

Tocco finale, Google PageSpeed Insights o Lighthouse nella scheda Audits degli strumenti per sviluppatori. La sezione «Elimina le risorse che bloccano il rendering» dovrebbe mostrare un miglioramento. Il guadagno specifico dipende dal numero e dalla dimensione degli script, ma per un tipico sito WordPress con 5-7 plugin una riduzione del 40-60% del JavaScript bloccante è un risultato raggiungibile.

⁉️🤔 Domande frequenti

Posso usare sia defer che async su uno script?

No. Se specifichi entrambi gli attributi contemporaneamente, il browser ignorerà defer ed eseguirà lo script come async. Questo comportamento è previsto dalle specifiche HTML, async ha sempre la priorità. Scegline uno in base all'importanza dell'ordine di esecuzione.

Cosa fare se dopo aver aggiunto defer lo script smette di funzionare?

Molto probabilmente lo script si aspetta che il DOM non sia ancora stato costruito e cerca di manipolare elementi che non esistono al momento dell'esecuzione. Sostituisci defer con il caricamento bloccante standard per quello script specifico. Oppure avvolgi il codice dello script in DOMContentLoaded, così potrà funzionare con defer senza errori. La seconda opzione è preferibile: mantieni l'ottimizzazione e risolvi la compatibilità.

Qual è la differenza tra defer e spostare lo script nel footer tramite wp_enqueue_script con $in_footer = true?

$in_footer = true sposta semplicemente il tag <script> dall'<head> alla fine del <body>. Lo script blocca ancora il rendering, solo più tardi. defer carica in parallelo al parsing HTML ed esegue rigorosamente dopo che il DOM è costruito. L'uso combinato (in_footer => true + strategy => 'defer') dà il massimo effetto: lo script nel footer non ritarda il primo rendering e defer garantisce che non blocchi nemmeno il rendering finale.

Dovrei aggiornare WordPress alla 6.3 solo per il metodo nativo?

Se il sito è sulla versione 6.2 o precedente, vale la pena aggiornare non solo per strategy. WordPress 6.3 ha chiuso dozzine di vulnerabilità e ha portato miglioramenti delle prestazioni del core. Ma se un aggiornamento è impossibile per qualche motivo, il filtro script_loader_tag funziona in modo assolutamente affidabile dalla versione 4.1, rilasciata nel 2014. Non perdi nulla a usarlo.

E jQuery, defer o lo lascio così com'è?

jQuery dovrebbe caricarsi con defer se tutti gli script dipendenti sono anch'essi marcati defer. Il problema è che i plugin WordPress gestiscono molto raramente gli attributi per i loro script. Se metti defer su jQuery mentre un plugin di contact form collega il suo script senza attributi, il browser eseguirà il plugin prima di jQuery e il form si romperà. Consiglio pratico: inizia con defer per gli script del tuo tema. Non toccare jQuery finché non hai testato ogni plugin del sito.

Cosa mettere su un sito in produzione nel 2026

Se il server esegue WordPress 6.3 o superiore, solo il metodo nativo. Codice pulito, protezione dai conflitti di dipendenza, supporto del core. Inizia con defer per tutti gli script del tema e i plugin criticamente importanti; riserva async per analytics e widget di terze parti.

Se la versione è inferiore alla 6.3, il filtro script_loader_tag con un array di handle. Ha funzionato per un decennio, niente da rompere. L'unica cosa che non può fare è controllare automaticamente l'albero delle dipendenze, quindi aggiungi gli script all'array uno alla volta e controlla il sito dopo ciascuno.

E soprattutto: nessun metodo sostituisce l'audit degli script stessi. Se un plugin galleria collega 15 file solo per mostrare tre immagini, né deferasync aiuteranno radicalmente. L'ottimizzazione del caricamento inizia con la domanda «questo script è davvero necessario» e solo dopo «come caricarlo».

🔗 Documentazione ufficiale WordPress 6.3, Strategie di caricamento degli script