Ver contenido
Guía Completa para Eliminar Recursos que Bloquean el Renderizado
Introducción
Los recursos que bloquean el renderizado son uno de los problemas de rendimiento más impactantes que puedes corregir. Al eliminar o minimizar estos bloqueadores, puedes mejorar dramáticamente First Contentful Paint (FCP), Largest Contentful Paint (LCP) y Speed Index.
Esta guía proporciona estrategias avanzadas para identificar y eliminar CSS y JavaScript que bloquean el renderizado, basadas en la documentación oficial de Chrome Developers y las mejores prácticas de web.dev.
¿Nuevo en recursos bloqueantes? Comienza con nuestra guía para principiantes: 📖 Lee: Recursos que Bloquean el Renderizado Explicado
Entendiendo el Critical Rendering Path
Antes de optimizar, entiende cómo los navegadores renderizan páginas:
1. Descarga y Parseo de HTML
├── Construye DOM (Document Object Model)
└── Descubre enlaces CSS/JS
2. Descarga y Parseo de CSS
└── Construye CSSOM (CSS Object Model)
⚠️ BLOQUEANTE: Nada se renderiza hasta completar
3. Descarga y Ejecución de JavaScript
⚠️ BLOQUEANTE: Detiene parseo de HTML (a menos que use async/defer)
4. Construcción del Árbol de Renderizado
└── DOM + CSSOM = Árbol de Renderizado
5. Layout y Pintado
└── ¡Finalmente visible para el usuario!
Por Qué CSS Bloquea el Renderizado
El navegador no puede renderizar nada hasta construir el CSSOM porque:
- No sabe qué colores, fuentes o tamaños usar
- El layout depende de propiedades CSS
- Renderizar sin CSS causaría Flash of Unstyled Content (FOUC)
Por Qué JavaScript Bloquea el Parseo
JavaScript puede modificar tanto DOM como CSSOM, entonces el navegador:
- Pausa el parseo de HTML cuando encuentra un
<script> - Espera a que cargue el CSS (JS puede consultar estilos)
- Ejecuta JS antes de continuar
Estrategia 1: Extraer e Incluir CSS Crítico Inline
El CSS crítico es el CSS mínimo necesario para renderizar contenido above-the-fold.
Extracción Manual de CSS Crítico
<head>
<!-- CSS crítico inline -->
<style>
/* Reset y estilos base */
* { margin: 0; padding: 0; box-sizing: border-box; }
/* Estilos del header */
.header {
background: #fff;
height: 64px;
border-bottom: 1px solid #eee;
}
/* Sección hero */
.hero {
padding: 4rem 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
/* Tipografía para above-the-fold */
h1 { font-size: 2.5rem; font-weight: 700; }
p { font-size: 1.125rem; line-height: 1.6; }
</style>
<!-- Cargar CSS completo asincrónicamente -->
<link rel="preload" href="/css/main.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
</head>
CSS Crítico Automatizado con Herramientas
Usando Critical (paquete npm)
// build-critical.js
const critical = require('critical');
critical.generate({
inline: true,
base: 'dist/',
src: 'index.html',
target: {
html: 'index-critical.html',
css: 'critical.css'
},
width: 1300,
height: 900,
// Incluir viewports móvil y escritorio
dimensions: [
{ width: 375, height: 667 }, // Móvil
{ width: 1300, height: 900 } // Escritorio
]
});
Usando Critters (Webpack/Vite)
// vite.config.js
import critters from 'critters';
export default {
plugins: [
critters({
// CSS crítico inline
preload: 'swap',
// Eliminar CSS no usado
pruneSource: true
})
]
}
Estrategia 2: Diferir CSS No Crítico
Patrón Preload + onload
<!-- Preload inicia la descarga inmediatamente -->
<link rel="preload" href="/css/estilos.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<!-- Fallback para no-JS -->
<noscript>
<link rel="stylesheet" href="/css/estilos.css">
</noscript>
Usando Media Queries
<!-- Solo carga/bloquea para media coincidente -->
<link rel="stylesheet" href="/css/print.css" media="print">
<link rel="stylesheet" href="/css/mobile.css" media="(max-width: 768px)">
<link rel="stylesheet" href="/css/desktop.css" media="(min-width: 769px)">
Patrón LoadCSS
<script>
// Función loadCSS para carga asíncrona de CSS
function loadCSS(href) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
document.head.appendChild(link);
}
// Cargar CSS no crítico después del renderizado inicial
if (window.requestIdleCallback) {
requestIdleCallback(() => loadCSS('/css/below-fold.css'));
} else {
setTimeout(() => loadCSS('/css/below-fold.css'), 100);
}
</script>
Estrategia 3: Optimizar Carga de JavaScript
defer vs async
<!-- DEFER: Descarga en paralelo, ejecuta después del parseo HTML, mantiene orden -->
<script src="/js/vendor.js" defer></script>
<script src="/js/app.js" defer></script>
<!-- ASYNC: Descarga en paralelo, ejecuta inmediatamente cuando está listo, sin orden -->
<script src="/js/analytics.js" async></script>
Cuándo Usar Cada Uno
| Patrón | Caso de Uso | Orden Garantizado |
|---|---|---|
defer |
Bundle principal de la app, dependencias | ✅ Sí |
async |
Analytics, anuncios, widgets independientes | ❌ No |
Final de <body> |
Fallback legacy | ✅ Sí |
Scripts de Módulo (type=“module”)
Los scripts de módulo son diferidos por defecto:
<!-- Estos son diferidos automáticamente -->
<script type="module" src="/js/app.js"></script>
<script type="module">
import { init } from './app.js';
init();
</script>
Estrategia 4: Code Splitting y Lazy Loading
Code Splitting Basado en Rutas
// React
const ProductPage = React.lazy(() => import('./pages/Product'));
// Vue
const ProductPage = () => import('./pages/Product.vue');
// Vanilla JS con imports dinámicos
button.addEventListener('click', async () => {
const { openModal } = await import('./modal.js');
openModal();
});
Splitting a Nivel de Componente
// Ejemplo React
import { Suspense, lazy } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Cargando gráfico...</div>}>
<HeavyChart />
</Suspense>
</div>
);
}
Estrategia 5: Eliminar CSS y JavaScript No Usado
Encontrando Código No Usado con Coverage
- Abre Chrome DevTools (F12)
- Presiona Ctrl+Shift+P, escribe “Coverage”
- Haz clic en “Start instrumenting coverage and reload”
- Navega por tu sitio
- Exporta y analiza los resultados
PurgeCSS para CSS No Usado
// postcss.config.js
const purgecss = require('@fullhuman/postcss-purgecss');
module.exports = {
plugins: [
purgecss({
content: ['./src/**/*.html', './src/**/*.js', './src/**/*.jsx'],
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || [],
safelist: ['active', 'open', /^modal-/]
})
]
};
Tree Shaking en Herramientas de Build
// webpack.config.js
module.exports = {
mode: 'production', // Habilita tree shaking
optimization: {
usedExports: true,
sideEffects: true
}
};
Estrategia 6: Optimizar Scripts de Terceros
Los scripts de terceros son frecuentemente los peores infractores para el bloqueo de renderizado.
Retrasar Hasta Interacción del Usuario
// Cargar widget de chat solo en primera interacción
let chatLoaded = false;
function loadChat() {
if (chatLoaded) return;
chatLoaded = true;
const script = document.createElement('script');
script.src = 'https://chat-widget.example.com/widget.js';
document.body.appendChild(script);
}
// Cargar en cualquier interacción del usuario
['click', 'scroll', 'keydown', 'touchstart'].forEach(event => {
document.addEventListener(event, loadChat, { once: true, passive: true });
});
// O después de 5 segundos como fallback
setTimeout(loadChat, 5000);
Usar Patrón Facade
<!-- Mostrar placeholder estático hasta interacción -->
<div class="youtube-placeholder" data-video-id="abc123">
<img src="/images/video-thumbnail.jpg" alt="Miniatura de video">
<button class="play-button" aria-label="Reproducir video">▶</button>
</div>
<script>
document.querySelector('.youtube-placeholder').addEventListener('click', function() {
const videoId = this.dataset.videoId;
this.outerHTML = `
<iframe
src="https://www.youtube.com/embed/${videoId}?autoplay=1"
allow="autoplay; encrypted-media"
allowfullscreen>
</iframe>
`;
});
</script>
Self-Host de Recursos Críticos de Terceros
<!-- En lugar de CDN externo -->
<link href="https://fonts.googleapis.com/css2?family=Roboto" rel="stylesheet">
<!-- Self-host para mejor control -->
<style>
@font-face {
font-family: 'Roboto';
src: url('/fonts/roboto.woff2') format('woff2');
font-display: swap;
}
</style>
Midiendo Mejoras
Comparación Antes/Después
ANTES:
├── Recursos bloqueantes: 5
├── FCP: 3.8s
├── LCP: 5.2s
├── Speed Index: 4.5s
└── Puntuación Lighthouse: 42
DESPUÉS:
├── Recursos bloqueantes: 0
├── FCP: 1.2s (↓68%)
├── LCP: 2.1s (↓60%)
├── Speed Index: 1.8s (↓60%)
└── Puntuación Lighthouse: 94
Lighthouse CI para Monitoreo Continuo
# .github/workflows/lighthouse.yml
- name: Run Lighthouse
uses: treosh/lighthouse-ci-action@v9
with:
urls: https://example.com/
budgetPath: ./budget.json
// budget.json
{
"resourceSizes": [
{ "resourceType": "script", "budget": 150 },
{ "resourceType": "stylesheet", "budget": 50 }
],
"resourceCounts": [
{ "resourceType": "third-party", "budget": 10 }
]
}
Errores Comunes y Soluciones
Error 1: Sobrecarga de Framework CSS
<!-- ❌ Cargando Bootstrap completo -->
<link href="bootstrap.min.css" rel="stylesheet">
<!-- ✅ Importar solo lo que necesitas -->
<style>
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/grid";
/* Solo utilidades de grid, nada más */
</style>
Error 2: Flash de Carga de Fuentes
/* ❌ Sin font-display causa texto invisible */
@font-face {
font-family: 'FuentePersonalizada';
src: url('fuente.woff2');
}
/* ✅ Mostrar fallback inmediatamente */
@font-face {
font-family: 'FuentePersonalizada';
src: url('fuente.woff2');
font-display: swap;
}
Error 3: Scripts Inline Bloqueando
<!-- ❌ Script inline bloquea el parseo -->
<script>
// Computación pesada aquí
</script>
<!-- ✅ Mover a archivo externo con defer -->
<script src="computacion.js" defer></script>
Checklist de Optimización
Antes de desplegar, verifica:
- [ ] CSS crítico está inline en
<head> - [ ] CSS no crítico carga asincrónicamente
- [ ] Todos los
<script>usandeferoasync - [ ] Scripts de terceros retrasados o usan facades
- [ ] CSS no usado eliminado (PurgeCSS o similar)
- [ ] JavaScript dividido por ruta
- [ ] Pestaña Coverage muestra código no usado mínimo
- [ ] Lighthouse muestra 0 recursos bloqueantes
- [ ] FCP < 1.8s y LCP < 2.5s
Artículos Relacionados
Continúa optimizando el rendimiento de renderizado de tu sitio:
- Recursos Bloqueantes Explicado - Entiende los fundamentos
- Guía de Optimización de Speed Index - Mejora el progreso visual
- Guía de Optimización de FCP - Acelera el primer pintado
- Guía de Optimización de LCP - Optimiza el contenido más grande
📚 Volver al Hub de Performance SEO - Explora todos los temas de rendimiento
Referencias
- Chrome Developers - Eliminate Render-Blocking Resources
- web.dev - Critical Rendering Path
- web.dev - Render-Blocking CSS
- web.dev - Defer Non-Critical CSS
Pruébalo Tú Mismo
¿Listo para eliminar recursos bloqueantes?
🔧 Descarga UXR SEO Analyzer (Gratis, análisis 100% local)
Disclaimer: Los analizadores en esta extensión son guías de referencia basadas en documentación oficial de Chrome Developers y web.dev. No representan verdades absolutas sobre cómo los motores de búsqueda evalúan tu contenido—solo los motores de búsqueda conocen sus algoritmos internos. Usa estas recomendaciones como punto de partida para mejorar tu sitio.
Última actualización: 14 de diciembre de 2025