Skip to content

Wszystko o WordPressie, tworzeniu stron — i nie tylko

⚡ Jak dodać defer i async dla skryptów WordPress w function.php

⚡ Jak dodać defer i async dla skryptów WordPress w function.php

Strony ładują się wolno, Google PageSpeed Insights pokazuje pomarańczowe ostrzeżenia, a klient pyta: „dlaczego strona działa tak wolno?" W dziewięciu na dziesięć przypadków źródłem problemu jest JavaScript blokujący renderowanie. Przeglądarka napotyka <script>, zatrzymuje budowanie DOM, pobiera i wykonuje skrypt, i dopiero wtedy kontynuuje. Na nowoczesnej stronie z kilkunastoma wtyczkami to opóźnienie zamienia się w sekundy.

WordPress przez długi czas nie oferował standardowego sposobu zarządzania ładowaniem skryptów. Deweloperzy uciekali się do sztuczek: filtrowali script_loader_tag, łatowali wyjście przez clean_url lub wręcz pisali własny walker dla WP_Scripts. Jednak wraz z premierą WordPress 6.3 sytuacja zmieniła się radykalnie i obecnie mamy czysty, łatwy w utrzymaniu sposób na dodanie defer lub async do dowolnego skryptu bez żadnego hacka.

Poniżej dwie działające metody: nowoczesna natywna (WP 6.3+) i sprawdzony filtr script_loader_tag (WP 4.1+). Obie przetestowane na rzeczywistych projektach, obie nie psują kolejki zależności.

💡 Szybki przegląd:

  • Proszę zrozumieć, czym różni się defer od async i kiedy stosować każde z nich, od tego zależy, czy funkcjonalność nie ulegnie uszkodzeniu po optymalizacji
  • Proszę używać natywnej metody WordPress 6.3+ przez wp_enqueue_script() z parametrem strategy, to najczystszy sposób, który zachowuje kolejność wykonywania
  • Jeśli strona jest na wersji niższej niż 6.3, proszę zastosować filtr script_loader_tag z tablicą uchwytów, działa to począwszy od WordPress 4.1
  • Dla kilku skryptów proszę zebrać tablicę uchwytów i przetworzyć ją pętlą, jeden filtr dla wszystkich skryptów zamiast kopiowania i wklejania

Czym są defer i async oraz kiedy je stosować

Gdy przeglądarka napotyka zwykły znacznik <script>, wykonuje trzy czynności po kolei: zatrzymuje parsowanie HTML, pobiera skrypt, wykonuje go. I dopiero potem wraca do HTML. Na stronie z pięcioma skryptami w <head> oznacza to, że użytkownik widzi biały ekran, dopóki nie załaduje się ostatnia wtyczka komentarzy, nawet jeśli sam wpis mógłby już dawno zostać wyrenderowany.

Atrybuty defer i async rozwiązują ten problem, ale działają inaczej:

Atrybut

Kiedy pobierany

Kiedy wykonywany

Kolejność wykonywania

(brak)

Blokuje parsowanie natychmiast

Natychmiast po pobraniu

Według kolejności w DOM

defer

Równolegle z parsowaniem

Po pełnym załadowaniu DOM

Według kolejności w DOM

async

Równolegle z parsowaniem

Natychmiast po pobraniu

Który pierwszy się pobrał

Defer to koń roboczy dla większości scenariuszy. Skrypt pobiera się równolegle z HTML, a wykonuje się dopiero, gdy DOM jest w pełni zbudowany. Kolejność jest zachowana: skrypt A wykona się przed skryptem B, nawet jeśli B pobrał się szybciej. Jest to krytyczne dla jQuery i wszystkiego, co od niego zależy.

Async to narzędzie dla niezależnych skryptów. Analityka, reklamy, widżety mediów społecznościowych: nie potrzebują DOM, nie zależy im na kolejności, muszą po prostu zadziałać jak najwcześniej. Ale jeśli ustawi Pan async dla skryptu zależnego od jQuery, z dużym prawdopodobieństwem zobaczy Pan błąd $ is not defined.

Prosta zasada: skrypt zależy od innych skryptów lub od DOM → defer. Skrypt jest w pełni autonomiczny → async. W razie wątpliwości zawsze proszę zaczynać od defer.

Sposób 1: Natywna metoda WordPress 6.3+

Od lipca 2023 roku w jądrze WordPress działa nowy mechanizm. Funkcje wp_register_script() oraz wp_enqueue_script() otrzymały przeciążony piąty parametr $args, tablicę, w której można wskazać strategię ładowania. Żadnych filtrów, żadnej magii z ciągami znaków, żadnego ryzyka popsucia kolejności zależności.

Podstawowa składnia dla 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);

Dla async ta sama mechanika:

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);

Klucz in_footer wewnątrz tablicy działa tak samo, jak stary parametr logiczny: true to skrypt w stopce, false to w <head>. Dla defer zazwyczaj ustawia się true (skrypt i tak czeka na DOM, nie ma sensu ładować go wcześnie), dla async jak jest wygodniej.

Główna zaleta metody natywnej: jądro samo sprawdza drzewo zależności. Jeśli skrypt A z defer zależy od skryptu B, a B jest zarejestrowany bez strategii (blokująco), WordPress nie popsuje Panu strony: automatycznie obniży strategię skryptu A do blokującej. Przy użyciu script_loader_tag jest Pan pozbawiony takiej ochrony, filtr tępo podstawia atrybut, nie patrząc na zależności.

Ważne: tablica $args pojawiła się w WordPress 6.3. Jeśli motyw lub wtyczka mają działać na niższych wersjach, proszę użyć sposobu 2 lub dodać sprawdzenie:

1if ( version_compare( $GLOBALS['wp_version'], '6.3', '>=' ) ) {
2 // нативный метод
3} else {
4 // фильтр script_loader_tag
5}

Sposób 2: Filtr script_loader_tag (WordPress 4.1+)

Jeśli strona działa na wersji niższej niż 6.3 lub potrzebna jest kompatybilność wsteczna, proszę zastosować sprawdzony filtr script_loader_tag. Istnieje on od WordPress 4.1 i nadal działa niezawodnie.

Filtr uruchamia się tuż przed wypisaniem znacznika <script> w HTML, otrzymuje Pan gotowy ciąg znacznika, uchwyt skryptu i ścieżkę do pliku, i może Pan zamienić src na defer="defer" src lub async="async" src.

Pojedynczy skrypt z 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);

Kod umieszcza się w functions.php aktywnego motywu lub, co jest bardziej poprawne, w osobnej wtyczce do fragmentów kodu, takiej jak Code Snippets lub WPCode. Jeśli umieści się go w functions.php motywu potomnego, przy zmianie motywu skrypty znów staną się blokujące i nie dowie się Pan o tym od razu.

Uchwyt skryptu to pierwszy parametr, który przekazał Pan do wp_register_script() lub wp_enqueue_script(). To on figuruje w warunku if. Proszę nie zgadywać uchwytu, proszę otworzyć kod źródłowy wtyczki lub motywu i znaleźć wywołanie wp_enqueue_script.

Defer i async dla wielu skryptów

Dodawanie po jednym filtrze na każdy skrypt to droga do rozdętego functions.php i błędów przy kopiowaniu. Prawidłowe rozwiązanie: tablica uchwytów i jeden filtr z pętlą.

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);

Dla async zmienia się tylko atrybut i nazwa tablicy:

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);

Oba filtry można podpinać jednocześnie, defer na swoje skrypty, async na zewnętrzne trackery. Działają niezależnie i nie kolidują ze sobą.

Praktyczny przykład: Google Maps API

Google Maps to klasyczny kandydat na defer. Mapa zazwyczaj znajduje się w stopce strony kontaktowej, skrypt pobiera 100+ KB, a użytkownikowi mapa nie jest potrzebna od razu. Przy tym samo API nie zależy od innych skryptów strony, to idealny przypadek.

Podłączamy i odraczamy:

1// 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');

Ten sam rezultat przez 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);

Po wdrożeniu któregokolwiek z wariantów koniecznie proszę sprawdzić mapę na stronie kontaktowej. Proszę otworzyć konsolę przeglądarki (F12), upewnić się, że nie ma błędów JavaScript i że mapa wyrenderowała się poprawnie. Jeśli pojawił się błąd w rodzaju initMap is not a function, oznacza to, że Pana skrypt inicjalizujący również trzeba oznaczyć jako defer i umieścić go ściśle po podłączeniu API.

Jak sprawdzić, czy defer i async działają

Po wdrożeniu następuje weryfikacja. Bez niej nie wie Pan, czy optymalizacja zadziałała, czy kod po prostu leży martwy.

Proszę otworzyć kod źródłowy strony (Ctrl+U) i znaleźć swoje skrypty. W znaczniku <script> powinny pojawić się atrybuty:

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

Jeśli atrybutów nie ma, proszę sprawdzić, czy uchwyt w filtrze pokrywa się z rzeczywistym uchwytem skryptu. Częsty błąd: w wp_enqueue_script uchwyt to my-plugin-frontend, a w filtrze my_plugin_frontend. Myślnik kontra podkreślenie i filtr po cichu pomija skrypt.

Ostatni szlif to Google PageSpeed Insights lub Lighthouse w zakładce Audits narzędzi deweloperskich. Sekcja „Wyeliminuj zasoby blokujące renderowanie" powinna pokazać poprawę. Konkretny zysk zależy od liczby i rozmiaru skryptów, ale dla typowej strony na WordPress z 5-7 wtyczkami redukcja blokującego JavaScript o 40-60% jest osiągalnym rezultatem.

⁉️🤔 Często zadawane pytania

Czy można używać jednocześnie defer i async na jednym skrypcie?

Nie. Jeśli wskaże Pan oba atrybuty jednocześnie, przeglądarka zignoruje defer i obsłuży skrypt jako async. To zachowanie jest zaszyte w specyfikacji HTML, async ma zawsze wyższy priorytet. Proszę wybrać jedno, kierując się tym, czy kolejność wykonywania jest ważna.

Co zrobić, jeśli po dodaniu defer skrypt przestał działać?

Najprawdopodobniej skrypt oczekuje, że DOM nie jest jeszcze zbudowany i próbuje manipulować elementami, których w momencie wykonania nie ma. Proszę zamienić defer na standardowe ładowanie blokujące dla tego konkretnego skryptu. Albo proszę opakować kod skryptu w DOMContentLoaded, wtedy będzie mógł pracować z defer bez błędów. Druga opcja jest lepsza: zachowuje Pan optymalizację i naprawia kompatybilność.

Jaka jest różnica między defer a przeniesieniem skryptu do stopki przez wp_enqueue_script z $in_footer = true?

$in_footer = true jedynie przenosi znacznik <script> z <head> na koniec <body>. Skrypt nadal blokuje renderowanie, po prostu później. defer pobiera się równolegle z parsowaniem HTML i wykonuje się ściśle po zbudowaniu DOM. Łączne użycie (in_footer => true + strategy => 'defer') daje maksymalny efekt: skrypt w stopce nie opóźnia pierwszego renderowania, a defer gwarantuje, że nie zablokuje on również końcowego wyświetlenia.

Czy trzeba aktualizować WordPress do 6.3 ze względu na natywną metodę?

Jeśli strona jest na wersji 6.2 lub starszej, warto zaktualizować ją nie tylko ze względu na strategy. WordPress 6.3 załatał dziesiątki luk w zabezpieczeniach i przyniósł poprawki wydajności samego jądra. Ale jeśli aktualizacja z jakichś powodów jest niemożliwa, filtr script_loader_tag działa absolutnie niezawodnie od wersji 4.1, wydanej w 2014 roku. Nic Pan nie traci, używając go.

Co zrobić z jQuery, defer czy zostawić jak jest?

jQuery powinno ładować się z defer, jeśli wszystkie zależne od niego skrypty również są oznaczone jako defer. Problem w tym, że wtyczki WordPress niezwykle rzadko zarządzają atrybutami swoich skryptów. Jeśli ustawi Pan defer na jQuery, a wtyczka formularza kontaktowego podłącza swój skrypt bez atrybutów, przeglądarka wykona wtyczkę przed jQuery i formularz się zepsuje. Praktyczna rada: proszę zacząć od defer dla własnych skryptów motywu. Proszę nie ruszać jQuery, dopóki nie przetestuje Pan każdej wtyczki na stronie.

Co stosować na produkcyjnej stronie w 2026 roku

Jeśli serwer działa na WordPress 6.3 lub nowszym, tylko metoda natywna. Czysty kod, ochrona przed konfliktami zależności, wsparcie jądra. Proszę zaczynać od defer dla wszystkich skryptów motywu i krytycznie ważnych wtyczek; async proszę rezerwować dla analityki i zewnętrznych widżetów.

Jeśli wersja jest niższa niż 6.3, filtr script_loader_tag z tablicą uchwytów. Działa od dekady, nie ma się co zepsuć. Jedyne, czego nie potrafi, to automatycznie sprawdzać drzewa zależności, dlatego proszę dodawać skrypty do tablicy pojedynczo i po każdym sprawdzać stronę.

I najważniejsze: żadna metoda nie zastąpi rewizji samych skryptów. Jeśli wtyczka galerii podłącza 15 plików dla pokazania trzech obrazków, ani defer, ani async radykalnie nie pomogą. Optymalizacja ładowania zaczyna się od pytania „czy ten skrypt jest w ogóle potrzebny" i dopiero potem „jak go załadować".

🔗 Oficjalna dokumentacja WordPress 6.3, Script Loading Strategies