minify html

Reduce el HTML de tu WordPress para bajar el peso de la página

La parte pública de una web o expresado de otro modo, lo que el navegador pinta, no es más que un documento HTML, donde encontraremos etiquetas de todo tipo: imgs, links, sections, articles, headers, footers, etc… A grosso modo algo tipo:

<!DOCTYPE html>
<html lang="es-ES" class="no-js no-svg">
<head>
    /* Un montón de Metas, title, links, scripts, styles... */
</head>

<body class="home blog hfeed has-header-image has-sidebar colors-light">
<div id="page" class="site">
    <header id="masthead" class="site-header" role="banner">
        /* Elementos de cabecera, logos, menus, iconos de redes sociales, buscador... */
    </header><!-- #masthead -->

    <div class="site-content-contain">
        /* Contenidos, sidebars, articles, asides, etc... */
    </div><!-- .site-content-contain -->
</div><!-- #page -->
<script type='text/javascript' src='http://wptest.dev/wp-content/themes/twentyseventeen/assets/js/skip-link-focus-fix.js?ver=1.0'></script>
<script type='text/javascript' src='http://wptest.dev/wp-content/themes/twentyseventeen/assets/js/navigation.js?ver=1.0'></script>
<script type='text/javascript' src='http://wptest.dev/wp-content/themes/twentyseventeen/assets/js/global.js?ver=1.0'></script>
<script type='text/javascript' src='http://wptest.dev/wp-content/themes/twentyseventeen/assets/js/jquery.scrollTo.js?ver=2.1.2'></script>
<script type='text/javascript' src='http://wptest.dev/wp-includes/js/wp-embed.min.js?ver=4.7.4'></script>
</body>
</html>

Este es el HTML (he reducido algunas cosas) que pinta el tema twentyseventeen de WordPress basándonos en la instalación inicial, sin plugins y únicamente con la página de ejemplo y la entrada Hola Mundo. Físicamente ocupa 12.124 bytes. Vamos a ver qué podemos hacer para reducir el tamaño del HTML que se genera en la parte pública de un WordPress.

Limpiar el HTML de la cabecera de WordPress

En la cabecera <head> se cargan una serie de etiquetas como wlwmanifest, generator, emojis (script y css), etc… que probablemente no utilices y no hacen más que ocupar espacio y generar más peticiones http innecesarias. Podemos añadir una función en nuestro functions.php para limpiar la cabecera:

<?php

// Removes some links from the header
function remove_headlinks_and_emojis() {
    remove_action( 'wp_head', 'wp_generator' );
    remove_action( 'wp_head', 'rsd_link' );
    remove_action( 'wp_head', 'wlwmanifest_link' );
    remove_action( 'wp_head', 'start_post_rel_link' );
    remove_action( 'wp_head', 'index_rel_link' );
    remove_action( 'wp_head', 'wp_shortlink_wp_head' );
    remove_action( 'wp_head', 'adjacent_posts_rel_link' );
    remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0 );
    remove_action( 'wp_head', 'parent_post_rel_link' );
    remove_action( 'wp_head', 'feed_links', 2 );
    remove_action( 'wp_head', 'feed_links_extra', 3 );
    remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
    remove_action( 'wp_print_styles', 'print_emoji_styles' );
    remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
    remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
    remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
}
add_action( 'init', 'remove_headlinks_and_emojis' );

Nuestra cabecera pasaría de tener todo este código HTML:

<!DOCTYPE html>
<html lang="es-ES" class="no-js no-svg">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="profile" href="http://gmpg.org/xfn/11">

<script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script>
<title>wptest &#8211; Otro sitio realizado con WordPress</title>
<link rel='dns-prefetch' href='//fonts.googleapis.com' />
<link rel='dns-prefetch' href='//s.w.org' />
<link href='https://fonts.gstatic.com' crossorigin rel='preconnect' />
<link rel="alternate" type="application/rss+xml" title="wptest &raquo; Feed" href="http://wptest.dev/feed/" />
<link rel="alternate" type="application/rss+xml" title="wptest &raquo; RSS de los comentarios" href="http://wptest.dev/comments/feed/" />
<script type="text/javascript">
    window._wpemojiSettings = {"baseUrl":"https:\/\/s.w.org\/images\/core\/emoji\/2.2.1\/72x72\/","ext":".png","svgUrl":"https:\/\/s.w.org\/images\/core\/emoji\/2.2.1\/svg\/","svgExt":".svg","source":{"concatemoji":"http:\/\/wptest.dev\/wp-includes\/js\/wp-emoji-release.min.js?ver=4.7.4"}};
    !function(a,b,c){function d(a){var b,c,d,e,f=String.fromCharCode;if(!k||!k.fillText)return!1;switch(k.clearRect(0,0,j.width,j.height),k.textBaseline="top",k.font="600 32px Arial",a){case"flag":return k.fillText(f(55356,56826,55356,56819),0,0),!(j.toDataURL().length<3e3)&&(k.clearRect(0,0,j.width,j.height),k.fillText(f(55356,57331,65039,8205,55356,57096),0,0),b=j.toDataURL(),k.clearRect(0,0,j.width,j.height),k.fillText(f(55356,57331,55356,57096),0,0),c=j.toDataURL(),b!==c);case"emoji4":return k.fillText(f(55357,56425,55356,57341,8205,55357,56507),0,0),d=j.toDataURL(),k.clearRect(0,0,j.width,j.height),k.fillText(f(55357,56425,55356,57341,55357,56507),0,0),e=j.toDataURL(),d!==e}return!1}function e(a){var c=b.createElement("script");c.src=a,c.defer=c.type="text/javascript",b.getElementsByTagName("head")[0].appendChild(c)}var f,g,h,i,j=b.createElement("canvas"),k=j.getContext&&j.getContext("2d");for(i=Array("flag","emoji4"),c.supports={everything:!0,everythingExceptFlag:!0},h=0;h<i.length;h++)c.supports[i[h]]=d(i[h]),c.supports.everything=c.supports.everything&&c.supports[i[h]],"flag"!==i[h]&&(c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&c.supports[i[h]]);c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&!c.supports.flag,c.DOMReady=!1,c.readyCallback=function(){c.DOMReady=!0},c.supports.everything||(g=function(){c.readyCallback()},b.addEventListener?(b.addEventListener("DOMContentLoaded",g,!1),a.addEventListener("load",g,!1)):(a.attachEvent("onload",g),b.attachEvent("onreadystatechange",function(){"complete"===b.readyState&&c.readyCallback()})),f=c.source||{},f.concatemoji?e(f.concatemoji):f.wpemoji&&f.twemoji&&(e(f.twemoji),e(f.wpemoji)))}(window,document,window._wpemojiSettings);
</script>
<style type="text/css">
img.wp-smiley,
img.emoji {
    display: inline !important;
    border: none !important;
    box-shadow: none !important;
    height: 1em !important;
    width: 1em !important;
    margin: 0 .07em !important;
    vertical-align: -0.1em !important;
    background: none !important;
    padding: 0 !important;
}
</style>
<link rel='stylesheet' id='twentyseventeen-fonts-css'  href='https://fonts.googleapis.com/css?family=Libre+Franklin%3A300%2C300i%2C400%2C400i%2C600%2C600i%2C800%2C800i&#038;subset=latin%2Clatin-ext' type='text/css' media='all' />
<link rel='stylesheet' id='twentyseventeen-style-css'  href='http://wptest.dev/wp-content/themes/twentyseventeen/style.css?ver=4.7.4' type='text/css' media='all' />
<!--[if lt IE 9]>
<link rel='stylesheet' id='twentyseventeen-ie8-css'  href='http://wptest.dev/wp-content/themes/twentyseventeen/assets/css/ie8.css?ver=1.0' type='text/css' media='all' />
<![endif]-->
<!--[if lt IE 9]>
<script type='text/javascript' src='http://wptest.dev/wp-content/themes/twentyseventeen/assets/js/html5.js?ver=3.7.3'></script>
<![endif]-->
<script type='text/javascript' src='http://wptest.dev/wp-includes/js/jquery/jquery.js?ver=1.12.4'></script>
<script type='text/javascript' src='http://wptest.dev/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.4.1'></script>
<link rel='https://api.w.org/' href='http://wptest.dev/wp-json/' />
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://wptest.dev/xmlrpc.php?rsd" />
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://wptest.dev/wp-includes/wlwmanifest.xml" /> 
<meta name="generator" content="WordPress 4.7.4" />
<style type="text/css">.recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}</style>
</head>

A reducirse a esto:

<!DOCTYPE html>
<html lang="es-ES" class="no-js no-svg">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="profile" href="http://gmpg.org/xfn/11">

<script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script>
<title>wptest &#8211; Otro sitio realizado con WordPress</title>
<link rel='dns-prefetch' href='//fonts.googleapis.com' />
<link rel='dns-prefetch' href='//s.w.org' />
<link href='https://fonts.gstatic.com' crossorigin rel='preconnect' />
<link rel='stylesheet' id='twentyseventeen-fonts-css'  href='https://fonts.googleapis.com/css?family=Libre+Franklin%3A300%2C300i%2C400%2C400i%2C600%2C600i%2C800%2C800i&#038;subset=latin%2Clatin-ext' type='text/css' media='all' />
<link rel='stylesheet' id='twentyseventeen-style-css'  href='http://wptest.dev/wp-content/themes/twentyseventeen/style.css?ver=4.7.4' type='text/css' media='all' />
<!--[if lt IE 9]>
<link rel='stylesheet' id='twentyseventeen-ie8-css'  href='http://wptest.dev/wp-content/themes/twentyseventeen/assets/css/ie8.css?ver=1.0' type='text/css' media='all' />
<![endif]-->
<!--[if lt IE 9]>
<script type='text/javascript' src='http://wptest.dev/wp-content/themes/twentyseventeen/assets/js/html5.js?ver=3.7.3'></script>
<![endif]-->
<script type='text/javascript' src='http://wptest.dev/wp-includes/js/jquery/jquery.js?ver=1.12.4'></script>
<script type='text/javascript' src='http://wptest.dev/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.4.1'></script>
<link rel='https://api.w.org/' href='http://wptest.dev/wp-json/' />
<style type="text/css">.recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}</style>
</head>

El peso total del HTML se reduciría hasta los 9.171 bytes. No sólo logramos reducir el peso, si no que evitamos peticiones HTTP que aumentan el tiempo de carga de nuestra web.

Limpiar las clases que no utilizamos en body, articles y menús

Podemos observar en varias etiquetas que WordPress pinta una serie de clases con el fin de darnos la opción de personalizar un template concreto, o un post, o incluso los post pertenecientes a una categoría o etiqueta, etc… Ejemplo:

Etiqueta <body>:

<body class="home blog hfeed has-header-image has-sidebar colors-light">

Etiquetas de menú (<ul><li>):

<ul id="top-menu" class="menu">
    <li id="menu-item-5" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-5"><a href="http://wptest.dev/pagina-ejemplo/">Página de ejemplo</a></li>
    <li id="menu-item-4" class="menu-item menu-item-type-post_type menu-item-object-post menu-item-4"><a href="http://wptest.dev/2017/05/11/hola-mundo/">¡Hola mundo!</a></li>
</ul>

Etiqueta <article>:

<article id="post-1" class="post-1 post type-post status-publish format-standard hentry category-sin-categoria">

Si no vas a utilizar estas clases para maquetar o dar estilo a partes de tu web, mejor elimínalas. Ojo, quizá te interese mantener algunas, en los ejemplos de abajo crearemos unas listas blancas.

Para eliminar las clases de la etiqueta body haremos uso del filtro body_class. Este filtro pinta una serie de clases en función de la plantilla en la que estemos (por ejemplo: home, error404, category…):

<?php

// Eliminar las clases del body
function remove_body_classes($classes) {
    // Listado de clases generadas por WordPress permitidas
    $whitelist = [ 'home', 'error404' ];

    // Nos quedamos sólo con las clases permitidas
    $wp_classes = array_intersect( $classes, $whitelist );

    return $wp_classes;
}
add_filter( 'body_class', 'remove_body_classes' );

Lo mismo para las clases añadidas a los articles, pero en esta ocasión utilizando el filtro post_class:

<?php

// Eliminar las clases del article
function remove_post_classes($classes) {
    // Listado de clases generadas por WordPress permitidas
    $whitelist = [ 'post' ];

    // Nos quedamos sólo con las clases permitidas
    $wp_classes = array_intersect( $classes, $whitelist );

    return $wp_classes;
}
add_filter( 'post_class', 'remove_post_classes' );

También para los menús, utilizando el filtro nav_menu_css_class:

<?php

// Eliminar las clases del menú
function remove_menu_classes($classes) {
    // Listado de clases generadas por WordPress permitidas
    $whitelist = [ 'menu-item' ];

    // Nos quedamos sólo con las clases permitidas
    $wp_classes = array_intersect( $classes, $whitelist );

    return $wp_classes;
}
add_filter( 'nav_menu_css_class', 'remove_menu_classes' );

No es mucho pero hemos pasado de 9.171 bytes a 8.868 bytes. Recuerdo que es una instalación inicial básica, la ganancia sería mayor en un entorno real con más posts, más elementos de menú, etc…

Eliminar el parámetro de versión de los query strings

WordPress suele añadir a la URL de los archivos CSS y JS un parámetro al final con el número de la versión, por ejemplo: ?ver=4.7.4. Para eliminarlo bastará con añadir esta función:

<?php

// Eliminar las versiones de los parámetros en las URLs
function remove_version_params( $src ){
    $parts = explode( '?ver', $src );

    return $parts[0];
}
add_filter( 'script_loader_src', 'remove_version_params', 15, 1 );
add_filter( 'style_loader_src', 'remove_version_params', 15, 1 );

En este ejemplo aparece en 10 ocasiones, eliminándolo conseguimos reducir el HTML a 8.775 bytes. Además, algunos analizadores de rendimiento como Pingdom Tools ó GT Metrix te recomiendan eliminarlos porque con cada cambio de versión la caché quedaría «anulada», y el cliente tendría que descargarse de nuevo el recurso que probablemente sea el mismo.

Minificar el HTML para terminar

La mayoría de plugins de caché ofrecen la posibilidad de minificar el HTML. Minificar el HTML significa eliminar todos los espacios, tabulaciones, saltos de línea, comentarios, etc… quedando todo el HTML en una única línea, quedándote algo tan feo como la imagen de portada de esta entrada.

En nuestro ejemplo bajamos el peso hasta los 7.758 bytes. Desde los 12.124 bytes iniciales, todas estas acciones han supuesto una mejora de 4.366 bytes. No es mucho, pero hay que tener en cuenta que este ejemplo es única y exclusivamente con los contenidos generados en una instalación limpia de WordPress.

En la vida real, con plugins instalados y bastante más contenido la mejora puede ser de varios KB. Sigue siendo poca, pero aún con este ejemplo siempre cargarán más rápido 7,7 KB que 12,1 KB, aunque apenas sean unas milésimas imperceptibles para el ojo humano.

Estas técnicas hay que enfocarlas en el sentido de que son un granito de arena más que nos va a ayudar a tener un mejor rendimiento. Si en lugar de un plugin de caché, quieres aventurarte a hacerlo a mano y tener el control de cómo minificar el HTML, te invito a visitar este repo en GitHub donde tengo una clase PHP para realizar esta tarea.

Una reflexión para terminar: En listados de posts donde podemos mostrar los últimos 10 posts, si cada post tiene una imagen, un título, un enlace, un botón, un extracto, etc…y reducimos el listado a los 8 últimos, el HTML pintado no sólo será menor, la query es más rápida y también hay menos peticiones http por ejemplo a las imágenes, además de evitar la carga de estas dos imágenes en sí.

¿Te ha resultado útil esta información? 🍺

Si este post te ha resuelto un problema, invítame a un café o a una cerveza. Con este pequeño gesto me animas a seguir escribiendo.

Comentarios

5 comentarios en Reduce el HTML de tu WordPress para bajar el peso de la página

  1. Hola Pablo!!

    A mi me gusta el uso de los query strings con la versión. Precisamente porque «con cada cambio de versión la caché quedaría anulada», es una forma muy fácil de actualizar el script y que el cliente se descargue la nueva versión. Si no se pone el query string con la versión habría que poner la versión en cualquier otra parte de las URL del script, de lo contrario no habría forma de que el cliente tenga la versión más nueva hasta que la caché expire; las dos son técnicas de invalidación de caché válidas:

    https://example.com/scripts/1.0/validate-form.js
    https://example.com/scripts/validate-form.js?ver=1.0

    Pero utilizar el query string en WordPress me parece mucho más cómodo, pues viene integrado en el sistema de manejo de scripts.

    1. Hola Juan!

      Efectivamente, con una versión nueva anularías la caché, que si hay cambios en esos archivos te viene bien.

      Del lado contrario, test como pingdom tools te marcan como punto a mejorar: Remove query strings from static resources. Precisamente porque cachear estos recursos es una buena práctica para mejorar la velocidad del sitio.

      Si no hay cambios en estos archivos estaríamos haciendo descargar el mismo recurso «con otro nombre». Que tampoco es nada dramático… 😉

      1. Claro, pero yo entiendo que no se va a cambiar de versión si no hay una nueva versión, ¿no?. Así que ese problema de descargar el mismo recurso de forma innecesaria no existe si se hace bien. Por eso yo nunca le hice caso a ese aviso de algunas herramientas, herramientas que encima no te dan ningún aviso si utilizas la versión como ruta del URL y es exactamente lo mismo.

        En WordPress, lo que hay que hacer, en mi opinión, es no dejar la versión del script sin especificar, hay que poner la versión, o como mucho null. Si se pone null WordPress no añadirá el query string con la versión, pero si deja en el valor por defecto (cadena vacía), entonces WordPress añade como versión la versión de WordPress, y eso si que no estaría bien porque se cambiaría de versión del script sin que el script haya cambiado realmente.

        En cualquier, creo que sería mucho peor actualizar un script o un css y no tener forma de que los usuarios se descarguen la nueva versión y que sigan utilizando la versión en caché. En el caso de scripts y css las cachés suelen ser muy muy largas, del meses hasta un año, imagina todo ese tiempo con usuarios que actualicen los scripts y estilos de tu web. Yo esa situación la veo mucho peor.

        1. Si, por eso digo siempre lo de no obsesionarse con estas herramientas. No siempre tener mejor puntuación significa que la web vaya más rápido. Espero que se me entienda con un ejemplo:

          Google Fonts. El propio google te dice que las cargues en el , sin embargo si lo haces ahí PageSpeed te dice que tienes un recurso que está bloqueando la visualización del contenido de la mitad superior de la página

          Si lo pones en el footer desaparece ese «aviso», pero aparece el FOUT. Es decir, pasas el filtro, tienes mejor puntuación, pero sin embargo aparece un flashazo de las fuentes en tu web que queda muy feo.

          Con esto pasa lo mismo, si un visitante tiene cacheado un recurso estático por un año, bien porque pasas el filtro, obtienes mejor nota y el usuario no tiene que descargarse ese recurso la próxima vez que te visite. Ahora, si hay modificaciones en ese recurso no lo verá a menos que borre la caché de su navegador regularmente, etc…

          1. Jejeje, justo edité el comentario con el ejemplo de Google Fonts y el tema de las versiones sin ver que me habías respondido.

            Es evidente que esas herramientas son automáticas y no todos los detalles son aplicables a todas las situaciones.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *:

  • El fin del tratamiento es únicamente la moderación de comentarios para evitar spam
  • La legitimación es tu consentimiento al comentar
  • No se comunicará ningún dato a terceros salvo por obligación legal
  • Tienes derecho al acceso, rectificación y eliminación de los comentarios