Skip to content

Todo para WordPress, el desarrollo web — y mucho más

⚡ Cómo añadir defer y async para scripts de WordPress en function.php

⚡ Cómo añadir defer y async para scripts de WordPress en function.php

Las páginas cargan lento, Google PageSpeed Insights muestra advertencias naranjas y el cliente pregunta: «¿por qué el sitio va lento?». Nueve de cada diez veces la causa raíz es que el JavaScript bloquea la renderización. El navegador encuentra un <script>, detiene la construcción del DOM, carga y ejecuta el script, y solo entonces continúa. En un sitio moderno con una docena de plugins esta demora se convierte en segundos.

WordPress durante mucho tiempo no ofreció una forma estándar de controlar la carga de scripts. Los desarrolladores bailaban con soluciones alternativas: filtrar script_loader_tag, parchear la salida mediante clean_url o incluso escribir walkers personalizados para WP_Scripts. Pero con el lanzamiento de WordPress 6.3 la situación cambió radicalmente, y ahora tenemos una forma limpia y soportada de añadir defer o async a cualquier script sin un solo truco.

A continuación, dos métodos funcionales: el enfoque nativo moderno (WP 6.3+) y el probado filtro script_loader_tag (WP 4.1+). Ambos han sido probados en proyectos reales, ambos preservan la integridad de la cola de dependencias.

💡 Resumen rápido:

  • Comprenda la diferencia entre defer y async y cuándo usar cada uno, esto determina si la funcionalidad se rompe después de la optimización
  • Use el método nativo de WordPress 6.3+ mediante wp_enqueue_script() con el parámetro strategy, el enfoque más limpio que preserva el orden de ejecución
  • Si el sitio corre una versión inferior a 6.3, aplique el filtro script_loader_tag con un array de handles, esto funciona desde WordPress 4.1
  • Para múltiples scripts recolecte los handles en un array e itere sobre él, un filtro para todos los scripts en lugar de copiar y pegar

Qué son defer y async y cuándo usarlos

Cuando un navegador encuentra una etiqueta <script> normal, hace tres cosas en secuencia: detiene el análisis del HTML, carga el script, lo ejecuta. Solo entonces vuelve al HTML. En una página con cinco scripts en el <head> esto significa que el usuario ve una pantalla en blanco mientras carga el último plugin de comentarios, aunque la entrada misma podría haberse renderizado hace rato.

Los atributos defer y async resuelven este problema, pero funcionan de manera diferente:

Atributo

Cuándo carga

Cuándo ejecuta

Orden de ejecución

(ninguno)

Bloquea el análisis inmediatamente

Inmediatamente después de cargar

En orden del DOM

defer

En paralelo con el análisis

Después de que el DOM cargue completamente

En orden del DOM

async

En paralelo con el análisis

Inmediatamente después de cargar

El que cargue primero

Defer es el caballo de batalla para la mayoría de los escenarios. El script carga en paralelo con el HTML y se ejecuta solo cuando el DOM está completamente construido. Se preserva el orden: el script A se ejecutará antes que el script B, incluso si B cargó más rápido. Esto es crítico para jQuery y todo lo que depende de él.

Async es una herramienta para scripts independientes. Analíticas, anuncios, widgets de redes sociales: no necesitan el DOM, no les importa el orden, solo necesitan ejecutarse lo antes posible. Pero si pone async en un script que depende de jQuery, probablemente obtendrá $ is not defined.

Regla simple: el script depende de otros scripts o del DOM → defer. El script es completamente autónomo → async. En caso de duda, empiece siempre con defer.

Método 1: Enfoque nativo de WordPress 6.3+

Desde julio de 2023 funciona un nuevo mecanismo en el núcleo de WordPress. Las funciones wp_register_script() y wp_enqueue_script() recibieron un quinto parámetro sobrecargado $args, un array donde puede especificar la estrategia de carga. Sin filtros, sin magia de cadenas, sin riesgo de romper el orden de dependencias.

Sintaxis básica para 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);

Para async, la misma mecánica:

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 clave in_footer dentro del array funciona igual que el antiguo parámetro booleano: true coloca el script en el footer, false en el <head>. Para defer típicamente se establece true (el script espera el DOM de todos modos, no tiene sentido cargarlo temprano), para async lo que funcione.

La principal ventaja del método nativo es que el propio núcleo verifica el árbol de dependencias. Si el script A con defer depende del script B, y B está registrado sin estrategia (bloqueante), WordPress no romperá su sitio: degradará automáticamente la estrategia del script A a bloqueante. Al usar script_loader_tag carece de esta protección, el filtro simplemente inserta el atributo sin mirar las dependencias.

Importante: el array $args apareció en WordPress 6.3. Si un tema o plugin debe funcionar en versiones inferiores, use el método 2 o añada una comprobación:

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

Método 2: Filtro script_loader_tag (WordPress 4.1+)

Si el sitio corre una versión inferior a 6.3 o necesita mantener compatibilidad hacia atrás, aplique el probado filtro script_loader_tag. Existe desde WordPress 4.1 y todavía funciona impecablemente.

El filtro se dispara justo antes de que la etiqueta <script> se emita al HTML, usted recibe la cadena de etiqueta lista, el handle del script y la ruta del archivo, y puede reemplazar src por defer="defer" src o async="async" src.

Script único 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);

El código va en el functions.php del tema activo o, más correctamente, en un plugin de fragmentos separado como Code Snippets o WPCode. Si lo coloca en el functions.php de un tema hijo, cuando cambie de tema los scripts volverán a ser bloqueantes y no lo notará de inmediato.

El handle del script es el primer parámetro que pasó a wp_register_script() o wp_enqueue_script(). Esto es lo que aparece en la condición if. No adivine el handle, abra el código fuente del plugin o tema y busque la llamada a wp_enqueue_script.

Defer y async para múltiples scripts

Añadir un filtro por script es el camino hacia un functions.php inflado y errores de copiar y pegar. La solución correcta: un array de handles y un filtro con un bucle.

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

Para async, solo cambian el atributo y el nombre del 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);

Ambos filtros pueden engancharse simultáneamente, defer en sus scripts, async en rastreadores de terceros. Funcionan de forma independiente y no entran en conflicto.

Ejemplo práctico: API de Google Maps

Google Maps es un candidato clásico para defer. El mapa típicamente está en el footer de la página de contactos, el script descarga más de 100 KB y el usuario no necesita el mapa de inmediato. Además, la API en sí no depende de otros scripts de la página, un caso ideal.

Conexión y 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');

El mismo resultado mediante 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);

Después de instalar cualquiera de las variantes, verifique sin falta el mapa en la página de contactos. Abra la consola del navegador (F12), asegúrese de que no haya errores de JavaScript y de que el mapa se renderizó correctamente. Si obtiene un error como initMap is not a function, significa que su script de inicialización también necesita marcarse como defer y colocarse estrictamente después de la conexión de la API.

Cómo verificar que defer y async están funcionando

Después de la implementación viene la verificación. Sin ella no sabe si la optimización funcionó o simplemente quedó como código muerto.

Abra el código fuente de la página (Ctrl+U) y busque sus scripts. La etiqueta <script> debería tener los atributos:

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

Si no hay atributos, verifique si el handle en el filtro coincide con el handle real del script. Error común: en wp_enqueue_script el handle es my-plugin-frontend, pero en el filtro es my_plugin_frontend. Guion versus guion bajo, y el filtro omite silenciosamente el script.

Toque final, Google PageSpeed Insights o Lighthouse en la pestaña Auditorías de las herramientas de desarrollo. La sección «Eliminar recursos que bloquean la renderización» debería mostrar mejora. La ganancia específica depende del número y tamaño de los scripts, pero para un sitio WordPress típico con 5-7 plugins una reducción del 40-60% en JavaScript bloqueante es un resultado alcanzable.

⁉️🤔 Preguntas frecuentes

¿Puedo usar tanto defer como async en un script?

No. Si especifica ambos atributos simultáneamente, el navegador ignorará defer y ejecutará el script como async. Este comportamiento está integrado en la especificación HTML, async siempre tiene prioridad. Elija uno según si el orden de ejecución importa.

¿Qué hacer si después de añadir defer el script dejó de funcionar?

Lo más probable es que el script espere que el DOM aún no esté construido e intente manipular elementos que no existen en el momento de la ejecución. Reemplace defer por carga bloqueante estándar para ese script específico. O envuelva el código del script en DOMContentLoaded, entonces podrá funcionar con defer sin errores. La segunda opción es preferible: conserva la optimización y corrige la compatibilidad.

¿Cuál es la diferencia entre defer y mover el script al footer mediante wp_enqueue_script con $in_footer = true?

$in_footer = true simplemente mueve la etiqueta <script> del <head> al final del <body>. El script aún bloquea la renderización, solo que más tarde. defer carga en paralelo con el análisis del HTML y se ejecuta estrictamente después de que el DOM esté construido. El uso combinado (in_footer => true + strategy => 'defer') da el máximo efecto: el script en el footer no retrasa la primera renderización, y defer garantiza que tampoco bloqueará la renderización final.

¿Debería actualizar WordPress a 6.3 solo por el método nativo?

Si el sitio está en la versión 6.2 o anterior, actualizar vale la pena no solo por strategy. WordPress 6.3 cerró docenas de vulnerabilidades y trajo mejoras de rendimiento del núcleo. Pero si una actualización es imposible por alguna razón, el filtro script_loader_tag funciona de manera absolutamente confiable desde la versión 4.1, lanzada en 2014. No pierde nada usándolo.

¿Qué pasa con jQuery, defer o dejarlo como está?

jQuery debería cargar con defer si todos los scripts dependientes también están marcados como defer. El problema es que los plugins de WordPress raramente gestionan los atributos de sus scripts. Si pone defer en jQuery mientras un plugin de formulario de contacto conecta su script sin atributos, el navegador ejecutará el plugin antes que jQuery y el formulario se romperá. Consejo práctico: empiece con defer para los scripts de su propio tema. No toque jQuery hasta que haya probado cada plugin del sitio.

Qué poner en un sitio de producción en 2026

Si el servidor corre WordPress 6.3 o más reciente, solo el método nativo. Código limpio, protección contra conflictos de dependencias, soporte del núcleo. Empiece con defer para todos los scripts del tema y plugins críticamente importantes; reserve async para analíticas y widgets de terceros.

Si la versión es inferior a 6.3, el filtro script_loader_tag con un array de handles. Ha funcionado durante una década, nada que romper. Lo único que no puede hacer es verificar automáticamente el árbol de dependencias, así que añada scripts al array de uno en uno y revise el sitio después de cada uno.

Y lo más importante: ningún método reemplaza auditar los scripts en sí mismos. Si un plugin de galería conecta 15 archivos solo para mostrar tres imágenes, ni defer ni async ayudarán radicalmente. La optimización de carga empieza con la pregunta «¿este script es siquiera necesario?», y solo después, «¿cómo cargarlo?».

🔗 Documentación oficial de WordPress 6.3, Estrategias de carga de scripts