Guía detallada

Render Blocking Resources Optimization Guide

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

  1. Abre Chrome DevTools (F12)
  2. Presiona Ctrl+Shift+P, escribe “Coverage”
  3. Haz clic en “Start instrumenting coverage and reload”
  4. Navega por tu sitio
  5. 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> usan defer o async
  • [ ] 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:

📚 Volver al Hub de Performance SEO - Explora todos los temas de rendimiento


Referencias

  1. Chrome Developers - Eliminate Render-Blocking Resources
  2. web.dev - Critical Rendering Path
  3. web.dev - Render-Blocking CSS
  4. 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

Artículos relacionados

Versión relacionada

Introducción

Render Blocking Resources Explained

Los recursos que bloquean el renderizado son archivos que impiden que el navegador muestre contenido a los usuarios hasta que se descarguen y procesen...

Hub de categoría

Hub

Performance Seo Hub

El rendimiento es un factor de ranking crítico e impacta directamente la experiencia del usuario

En la misma categoría

Guía detallada

Speed Index Optimization Guide

Speed Index mide qué tan rápido la parte visible de tu página se llena durante la carga

Guía detallada

Fcp Optimization Guide

First Contentful Paint (FCP) mide cuándo los usuarios ven por primera vez contenido en tu página