Per què el JavaScript no utilitzat perjudica els teus Core Web Vitals
La pàgina web mitjana envia 500 KB de JavaScript al navegador. Aquesta dada, publicada per HTTP Archive al seu Web Almanac de 2025, és la manifestació d’un problema sistèmic: la indústria web ha normalitzat l’enviament de grans quantitats de codi que la majoria dels usuaris mai executarà. El JavaScript s’acumula sprint rere sprint, dependència rere dependència, plugin rere plugin, fins que el pes total es converteix en un llast invisible per al rendiment.
La diferència entre el CSS i el JavaScript pel que fa a l’impacte en rendiment és que el CSS bloqueja el renderitzat però es processa ràpidament, mentre que el JavaScript es descarrega, es processa, es compila i s’executa, amb un cost computacional significativament major. Segons l’equip de V8 de Google, cada 100 KB de JavaScript addicional afegeix entre 150 i 350 ms de processament en un dispositiu mòbil de gamma mitjana (un Samsung A52 o un Xiaomi Redmi Note, els dispositius més venuts). En dispositius de gamma alta com un iPhone 15 Pro, el mateix JavaScript tarda 50-100 ms. La diferència de 4x a 7x entre gamma alta i gamma mitjana explica per què un lloc web pot semblar ràpid al MacBook del desenvolupador i ser dolorosament lent per al 60% dels seus usuaris reals.
L’impacte als Core Web Vitals és directe i mesurable. El LCP es retarda quan JavaScript bloqueja el fil principal durant la càrrega inicial, impedint que el navegador pinti el contingut més gran. L’INP (Interaction to Next Paint) es degrada quan scripts de llarga execució mantenen el fil principal ocupat, impedint que el navegador respongui a les interaccions de l’usuari. El CLS augmenta quan JavaScript modifica el DOM després del renderitzat inicial, desplaçant elements visibles.
Chrome DevTools Coverage revela la magnitud del malbaratament. A les auditories que realitzem, entre el 40% i el 70% del JavaScript descarregat no s’executa a la pàgina actual. Aquest codi mort consumeix amplada de banda, temps de processament i CPU sense aportar cap funcionalitat. És l’equivalent digital de pagar per un magatzem ple de mercaderia que mai es ven: un cost fix que llasta els marges sense generar valor.
La relació amb la velocitat web i SEO és clara. Google mesura els Core Web Vitals amb dades de camp —usuaris reals de Chrome— i usa aquestes mètriques com a senyal de rànquing. Un lloc amb 500 KB de JavaScript innecessari pot tenir un INP de 400 ms en dispositius de gamma mitjana, molt per sobre del llindar de 200 ms que Google considera “bo”. Eliminar aquest JavaScript no utilitzat pot ser la diferència entre superar o no els llindars de Core Web Vitals que impacten directament en el posicionament orgànic.
Com identificar JavaScript no utilitzat amb Chrome DevTools
La identificació precisa de JavaScript no utilitzat és el pas previ a qualsevol optimització. Sense un diagnòstic clar de quin codi no s’executa, les intervencions són especulatives i el risc de trencar funcionalitat és alt.
Chrome DevTools Coverage és l’eina principal. S’accedeix des de DevTools (F12) > Ctrl+Shift+P > “Show Coverage” > botó de recàrrega. Coverage analitza l’execució de cada fitxer JavaScript i CSS durant una sessió d’usuari, marcant en vermell les línies que no s’han executat i en blau les que sí. El resultat és un percentatge d’utilització per fitxer: un fitxer amb un 30% d’utilització significa que el 70% del seu codi és potencialment eliminable.
La metodologia correcta per usar Coverage és: primer, fer una recàrrega neta de la pàgina per capturar el CSS i JS de la càrrega inicial; segon, interactuar amb tots els elements de la pàgina —menús, modals, pestanyes, formularis— per activar codi que s’executa sota demanda; tercer, navegar a 3-5 pàgines representatives del lloc. El resultat final mostra la utilització real del JavaScript a nivell de lloc, no només d’una pàgina individual. Un fitxer amb un 15% d’utilització després d’aquesta metodologia completa és un candidat clar per a optimització.
Lighthouse complementa Coverage amb l’auditoria “Remove unused JavaScript”. Mentre Coverage mostra dades granulars per fitxer i línia, Lighthouse quantifica l’estalvi potencial en KB i segons. L’auditoria llista els fitxers amb major quantitat de codi no utilitzat i estima l’impacte en el temps de càrrega. Per a la priorització, els fitxers que Lighthouse marca amb més de 50 KB d’estalvi potencial són els que mereixen atenció immediata.
Webpack Bundle Analyzer (o el seu equivalent a Vite: rollup-plugin-visualizer) proporciona una vista d’arbre del bundle JavaScript complet, mostrant quines dependències npm ocupen més espai. És comú descobrir que una sola dependència —moment.js (300 KB), lodash complet (70 KB), o una biblioteca d’icones que s’importa sencera— representa el 30-50% del bundle total. Aquestes dependències són els candidats prioritaris per a tree shaking, substitució o eliminació.
Un patró que detectem freqüentment en e-commerces és l’acumulació de scripts de seguiment. Google Tag Manager, Facebook Pixel, Hotjar, Clarity, TikTok Pixel, Pinterest Tag, Google Ads remarketing, i scripts de plataformes d’afiliació poden sumar fàcilment 300-500 KB de JavaScript addicional. Cadascun d’aquests scripts s’executa al fil principal, competint amb el JavaScript de l’aplicació per temps de CPU. L’auditoria d’scripts de tercers és tan important com la del codi propi.
JavaScript de tercers: el pitjor culpable (i com gestionar-lo)
El JavaScript de tercers és la font més freqüent de JavaScript no utilitzat i, paradoxalment, la més difícil de controlar. A diferència del codi propi, on l’equip de desenvolupament pot modificar, optimitzar o eliminar, el codi de tercers és una caixa negra que es carrega des de servidors externs i executa lògica sobre la qual no hi ha control directe.
Segons el Web Almanac d’HTTP Archive, el lloc web medià carrega scripts de 8 orígens de tercers diferents. Cada origen introdueix latència de DNS, establiment de connexió TCP/TLS i transferència de dades. Però el cost real no és la descàrrega: és l’execució. Un script de xat en directe que pesa 150 KB després de descarregar-se pot expandir-se a 400 KB de JavaScript executable, monopolitzant el fil principal durant 500-800 ms en dispositius de gamma mitjana.
Les estratègies per gestionar JavaScript de tercers, ordenades per impacte, són les següents.
Auditar i eliminar l’innecessari. El primer pas és inventariar tots els scripts de tercers carregats al lloc i qüestionar si cadascun d’ells aporta valor proporcional al seu cost de rendiment. Un widget d’enquestes que ha generat 12 respostes en els últims 6 mesos no justifica 80 KB de JavaScript a cada càrrega de pàgina. Un script d’A/B testing que no s’usa activament no justifica 120 KB de SDK. L’eliminació pura és l’optimització més eficaç: zero bytes és sempre més ràpid que qualsevol quantitat optimitzada.
Facade Pattern per a càrrega sota demanda. La Facade Pattern consisteix a mostrar un placeholder visual lleuger (una imatge estàtica o un component CSS pur) en lloc del widget real, i carregar el JavaScript complet només quan l’usuari hi interactua. L’exemple més documentat és l’embed de YouTube: substituir l’iframe de YouTube per una imatge del thumbnail amb un botó de play, i carregar el player complet en fer clic, estalvia 500-700 KB de JavaScript a la càrrega inicial. El mateix patró aplica a chatbots, mapes de Google i widgets de xarxes socials.
Defer i async per a scripts no crítics. L’atribut defer en un <script> indica al navegador que descarregui l’script en paral·lel amb el processament de l’HTML però que l’executi només després que el DOM estigui complet. L’atribut async descarrega en paral·lel però executa immediatament quan la descàrrega acaba, potencialment interrompent el processament. Per a scripts d’analytics i seguiment, async és l’elecció correcta, ja que són completament independents i no necessiten accés al DOM. Per a scripts que depenen del DOM, defer és apropiat.
Google Tag Manager amb regles d’activació. GTM permet controlar quins scripts es carreguen a quines pàgines mitjançant regles d’activació (triggers). Un pixel de Facebook configurat per activar-se només a pàgines de producte i checkout, en lloc de a totes les pàgines, redueix la càrrega de JavaScript a les pàgines informatives on aquell pixel no genera conversions. Aquesta granularitat requereix configuració inicial, però el benefici de rendiment és proporcional al nombre d’scripts gestionats.
Tree shaking i code splitting: eliminar codi mort al build
Tree shaking i code splitting són tècniques complementàries que operen al pipeline de build per reduir el JavaScript que arriba al navegador. Tree shaking elimina codi que existeix però mai s’executa. Code splitting divideix el codi en fragments que es carreguen sota demanda.
Tree shaking és un procés automàtic dels bundlers moderns (Webpack 5, Vite/Rollup, esbuild) que analitza els imports de mòduls ES (import/export) i identifica les exportacions que cap altre mòdul consumeix. Aquelles exportacions mortes s’eliminen del bundle final. El nom “tree shaking” prové de la metàfora de sacsejar un arbre perquè caiguin les fulles mortes.
La condició tècnica necessària perquè tree shaking funcioni és usar mòduls ES (ESM) amb import i export, no CommonJS amb require i module.exports. CommonJS és dinàmic —els imports poden dependre de condicions en temps d’execució— i els bundlers no poden determinar estàticament què s’usa. ESM és estàtic: els imports es declaren a l’arrel del fitxer i el bundler pot analitzar-los sense executar el codi.
Un exemple clar és la diferència entre import _ from 'lodash' (importa la biblioteca completa, 70 KB) i import { debounce } from 'lodash-es' (importa només la funció necessària, 3 KB). L’ús de la variant ESM (lodash-es) permet al bundler aplicar tree shaking i eliminar les 300+ funcions de lodash que no s’usen. Per a dependències que no ofereixen variant ESM, alternatives com babel-plugin-lodash o babel-plugin-import transformen els imports complets en imports selectius durant el build.
Code splitting divideix el bundle JavaScript en múltiples chunks que es carreguen sota demanda. La tècnica principal és l’import() dinàmic: en lloc de import { Gallery } from './Gallery' (càrrega estàtica, inclòs al bundle principal), s’usa const { Gallery } = await import('./Gallery') (càrrega dinàmica, genera un chunk separat que es descarrega només quan es necessita).
Els frameworks moderns implementen code splitting per defecte a les rutes. Next.js genera un chunk separat per a cada pàgina. Astro va més enllà amb l’arquitectura d’Islands, on cada component interactiu es carrega com un chunk independent només quan és visible al viewport (client:visible). Aquesta granularitat converteix el code splitting d’una optimització manual en un comportament per defecte del framework.
La combinació de tree shaking + code splitting + lazy loading de components pot reduir el payload inicial de JavaScript un 50-70% en aplicacions de mida mitjana. Per a una SPA amb 1,2 MB de JavaScript total, això significa passar d’enviar 1,2 MB a la càrrega inicial a enviar 350-500 KB, amb la resta carregat progressivament segons la navegació de l’usuari.
Defer vs async: quan usar cada atribut en scripts
Els atributs defer i async controlen quan i com el navegador descarrega i executa scripts externs. Usar-los correctament pot millorar el FCP i l’INP sense modificar el codi JavaScript en si. Usar-los incorrectament pot trencar la funcionalitat de la pàgina.
Sense defer ni async (script normal): el navegador atura el processament de l’HTML quan troba el <script>, descarrega el fitxer, l’executa, i només llavors reprèn el processament. Això és render-blocking i és la causa principal que scripts al <head> retardin el FCP. Només s’ha d’usar per a scripts que han d’executar-se abans que l’usuari vegi qualsevol contingut —casos extremadament rars com feature detection o polyfills crítics.
Amb async: el navegador descarrega l’script en paral·lel amb el processament de l’HTML, però l’executa immediatament quan la descàrrega acaba, aturant el processament si cal. L’ordre d’execució entre múltiples scripts async no està garantit: el primer que acabi de descarregar-se s’executa primer. Això significa que async és inadequat per a scripts que depenen d’altres scripts. És apropiat per a scripts completament independents com analytics (Google Analytics, Plausible) o widgets de tercers sense dependències.
Amb defer: el navegador descarrega l’script en paral·lel amb el processament de l’HTML i l’executa només després que el DOM estigui completament construït, just abans de l’event DOMContentLoaded. L’ordre d’execució entre múltiples scripts defer es manté: s’executen en l’ordre en què apareixen a l’HTML. Defer és l’opció correcta per a la majoria dels scripts d’aplicació que necessiten accés al DOM.
La recomanació pràctica és: defer per a tots els scripts d’aplicació que accedeixen al DOM, async per a scripts independents de tercers (analytics, pixels), i ni defer ni async només per a polyfills crítics que han d’estar disponibles abans que qualsevol altre script s’executi.
Un error freqüent és col·locar scripts amb defer al final del <body> pensant que així es carreguen després. Amb defer al <head>, l’script es descarrega en paral·lel amb el processament de l’HTML des del principi, però s’executa al final. Al <body> sense defer, l’script es descarrega i executa seqüencialment quan el parser arriba a aquella posició. El primer cas és més ràpid perquè la descàrrega comença abans.
La diferència de rendiment és mesurable. En un test amb WebPageTest simulant 4G, moure tres scripts de tercers (GTM, Facebook Pixel, Hotjar) de càrrega normal a defer va millorar l’INP de la pàgina de 380 ms a 180 ms, creuant el llindar de “bo” de 200 ms de Google. El FCP també va millorar 0,4 segons perquè els scripts van deixar de bloquejar el fil principal durant la càrrega inicial.
Mètriques: quant pots millorar eliminant JS no usat
La quantificació de l’impacte potencial abans d’implementar canvis permet prioritzar intervencions i justificar la inversió de temps de desenvolupament. Les mètriques clau per avaluar JavaScript no utilitzat són: pes total de JS, percentatge de codi no executat, temps de processament al fil principal, i les mètriques de Core Web Vitals afectades.
Pes total de JavaScript es mesura filtrant per tipus “Script” a la pestanya Network de DevTools. El pes transferit (comprimit) és el que impacta el temps de descàrrega. El pes sense comprimir (parsed) és el que impacta el temps de processament i compilació. Per a la web mediana, el pes transferit és de 500 KB i el pes processat és d’1,5-2 MB (la ràtio de compressió gzip/brotli per a JavaScript és típicament de 3:1 a 4:1).
Percentatge de codi no executat el proporciona Chrome DevTools Coverage. El benchmark és: per sota del 30% de codi no utilitzat és acceptable, entre 30% i 50% requereix atenció, per sobre del 50% és un problema crític. La mediana de llocs web té un 45% de JavaScript no utilitzat, segons les dades de Coverage recopilades per Web Almanac.
Temps de processament al fil principal es visualitza a la pestanya Performance de DevTools. La categoria “Scripting” mostra quants mil·lisegons del temps de càrrega es dediquen a compilar i executar JavaScript. Per a pàgines amb LCP per sobre de 2,5 segons, si Scripting supera els 500 ms, la reducció de JavaScript és la intervenció prioritària.
Els resultats documentats d’eliminar JavaScript no utilitzat són consistents. Un estudi de cas de web.dev sobre un lloc de notícies va mostrar que reduir el JavaScript de 800 KB a 350 KB va millorar el LCP de 3,8 a 2,1 segons en dispositius mòbils de gamma mitjana. L’INP va millorar de 350 ms a 120 ms. El tràfic orgànic mòbil va créixer un 15% a les vuit setmanes posteriors a la implementació.
Per a e-commerces amb problemes de JavaScript SEO, la reducció de JavaScript té un benefici doble: millora els Core Web Vitals per als usuaris reals i redueix el cost computacional per a Googlebot, que necessita executar JavaScript per indexar pàgines amb Client-Side Rendering. Menys JavaScript significa indexació més ràpida i més completa.
La regla de priorització que recomanem és: primer eliminar scripts de tercers no essencials (major impacte amb menor risc), segon implementar defer/async als scripts restants (impacte mitjà, risc baix), tercer aplicar code splitting per ruta (impacte alt, risc mitjà), i quart optimitzar tree shaking de dependències npm (impacte variable, risc baix). Aquesta seqüència maximitza el retorn per unitat d’esforç i minimitza la probabilitat de regressions funcionals.