perf: Defer Bootstrap with inline critical CSS for LCP optimization
- Add grid system (row, col-*) to critical-bootstrap.css to prevent CLS - Add text utilities, sizing, spacing, and alert component to critical CSS - Enable CriticalBootstrapService to inline critical Bootstrap in <head> - Defer bootstrap-subset.min.css (21KB) via media=print + onload - Fix preload pointing to wrong Bootstrap file (was 227KB, now 147KB) Expected improvement: ~970ms reduction in render-blocking CSS 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,78 @@ button:focus:not(:focus-visible) {
|
|||||||
.container { max-width: 1320px; }
|
.container { max-width: 1320px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
GRID SYSTEM (Layout crítico - Previene CLS)
|
||||||
|
========================================================================== */
|
||||||
|
.row {
|
||||||
|
--bs-gutter-x: 1.5rem;
|
||||||
|
--bs-gutter-y: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: calc(-1 * var(--bs-gutter-y));
|
||||||
|
margin-right: calc(-0.5 * var(--bs-gutter-x));
|
||||||
|
margin-left: calc(-0.5 * var(--bs-gutter-x));
|
||||||
|
}
|
||||||
|
.row > * {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding-right: calc(var(--bs-gutter-x) * 0.5);
|
||||||
|
padding-left: calc(var(--bs-gutter-x) * 0.5);
|
||||||
|
margin-top: var(--bs-gutter-y);
|
||||||
|
}
|
||||||
|
|
||||||
|
.col { flex: 1 0 0%; }
|
||||||
|
.col-auto { flex: 0 0 auto; width: auto; }
|
||||||
|
.col-1 { flex: 0 0 auto; width: 8.33333333%; }
|
||||||
|
.col-2 { flex: 0 0 auto; width: 16.66666667%; }
|
||||||
|
.col-3 { flex: 0 0 auto; width: 25%; }
|
||||||
|
.col-4 { flex: 0 0 auto; width: 33.33333333%; }
|
||||||
|
.col-5 { flex: 0 0 auto; width: 41.66666667%; }
|
||||||
|
.col-6 { flex: 0 0 auto; width: 50%; }
|
||||||
|
.col-7 { flex: 0 0 auto; width: 58.33333333%; }
|
||||||
|
.col-8 { flex: 0 0 auto; width: 66.66666667%; }
|
||||||
|
.col-9 { flex: 0 0 auto; width: 75%; }
|
||||||
|
.col-10 { flex: 0 0 auto; width: 83.33333333%; }
|
||||||
|
.col-11 { flex: 0 0 auto; width: 91.66666667%; }
|
||||||
|
.col-12 { flex: 0 0 auto; width: 100%; }
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.col-md-1 { flex: 0 0 auto; width: 8.33333333%; }
|
||||||
|
.col-md-2 { flex: 0 0 auto; width: 16.66666667%; }
|
||||||
|
.col-md-3 { flex: 0 0 auto; width: 25%; }
|
||||||
|
.col-md-4 { flex: 0 0 auto; width: 33.33333333%; }
|
||||||
|
.col-md-5 { flex: 0 0 auto; width: 41.66666667%; }
|
||||||
|
.col-md-6 { flex: 0 0 auto; width: 50%; }
|
||||||
|
.col-md-7 { flex: 0 0 auto; width: 58.33333333%; }
|
||||||
|
.col-md-8 { flex: 0 0 auto; width: 66.66666667%; }
|
||||||
|
.col-md-9 { flex: 0 0 auto; width: 75%; }
|
||||||
|
.col-md-10 { flex: 0 0 auto; width: 83.33333333%; }
|
||||||
|
.col-md-11 { flex: 0 0 auto; width: 91.66666667%; }
|
||||||
|
.col-md-12 { flex: 0 0 auto; width: 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.col-lg-1 { flex: 0 0 auto; width: 8.33333333%; }
|
||||||
|
.col-lg-2 { flex: 0 0 auto; width: 16.66666667%; }
|
||||||
|
.col-lg-3 { flex: 0 0 auto; width: 25%; }
|
||||||
|
.col-lg-4 { flex: 0 0 auto; width: 33.33333333%; }
|
||||||
|
.col-lg-5 { flex: 0 0 auto; width: 41.66666667%; }
|
||||||
|
.col-lg-6 { flex: 0 0 auto; width: 50%; }
|
||||||
|
.col-lg-7 { flex: 0 0 auto; width: 58.33333333%; }
|
||||||
|
.col-lg-8 { flex: 0 0 auto; width: 66.66666667%; }
|
||||||
|
.col-lg-9 { flex: 0 0 auto; width: 75%; }
|
||||||
|
.col-lg-10 { flex: 0 0 auto; width: 83.33333333%; }
|
||||||
|
.col-lg-11 { flex: 0 0 auto; width: 91.66666667%; }
|
||||||
|
.col-lg-12 { flex: 0 0 auto; width: 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gutter utilities */
|
||||||
|
.g-0, .gx-0 { --bs-gutter-x: 0; }
|
||||||
|
.g-0, .gy-0 { --bs-gutter-y: 0; }
|
||||||
|
.g-3, .gx-3 { --bs-gutter-x: 1rem; }
|
||||||
|
.g-3, .gy-3 { --bs-gutter-y: 1rem; }
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
FLEXBOX UTILITIES (Layout crítico)
|
FLEXBOX UTILITIES (Layout crítico)
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
@@ -96,10 +168,16 @@ button:focus:not(:focus-visible) {
|
|||||||
.d-block {
|
.d-block {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
.d-inline-block {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-wrap {
|
.flex-wrap {
|
||||||
flex-wrap: wrap !important;
|
flex-wrap: wrap !important;
|
||||||
}
|
}
|
||||||
|
.flex-column {
|
||||||
|
flex-direction: column !important;
|
||||||
|
}
|
||||||
|
|
||||||
.justify-content-center {
|
.justify-content-center {
|
||||||
justify-content: center !important;
|
justify-content: center !important;
|
||||||
@@ -107,31 +185,98 @@ button:focus:not(:focus-visible) {
|
|||||||
.justify-content-between {
|
.justify-content-between {
|
||||||
justify-content: space-between !important;
|
justify-content: space-between !important;
|
||||||
}
|
}
|
||||||
|
.justify-content-start {
|
||||||
|
justify-content: flex-start !important;
|
||||||
|
}
|
||||||
|
.justify-content-end {
|
||||||
|
justify-content: flex-end !important;
|
||||||
|
}
|
||||||
|
|
||||||
.align-items-center {
|
.align-items-center {
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
}
|
}
|
||||||
|
.align-items-start {
|
||||||
|
align-items: flex-start !important;
|
||||||
|
}
|
||||||
|
.align-items-end {
|
||||||
|
align-items: flex-end !important;
|
||||||
|
}
|
||||||
|
|
||||||
.gap-2 {
|
.gap-2 {
|
||||||
gap: 0.5rem !important;
|
gap: 0.5rem !important;
|
||||||
}
|
}
|
||||||
|
.gap-3 {
|
||||||
|
gap: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
SPACING UTILITIES (Margin/Padding críticos)
|
SPACING UTILITIES (Margin/Padding críticos)
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
.m-0 { margin: 0 !important; }
|
||||||
|
.m-auto { margin: auto !important; }
|
||||||
|
|
||||||
.mb-0 { margin-bottom: 0 !important; }
|
.mb-0 { margin-bottom: 0 !important; }
|
||||||
|
.mb-1 { margin-bottom: 0.25rem !important; }
|
||||||
.mb-2 { margin-bottom: 0.5rem !important; }
|
.mb-2 { margin-bottom: 0.5rem !important; }
|
||||||
.mb-3 { margin-bottom: 1rem !important; }
|
.mb-3 { margin-bottom: 1rem !important; }
|
||||||
|
.mb-4 { margin-bottom: 1.5rem !important; }
|
||||||
|
|
||||||
|
.mt-0 { margin-top: 0 !important; }
|
||||||
|
.mt-2 { margin-top: 0.5rem !important; }
|
||||||
|
.mt-3 { margin-top: 1rem !important; }
|
||||||
|
|
||||||
.me-1 { margin-right: 0.25rem !important; }
|
.me-1 { margin-right: 0.25rem !important; }
|
||||||
.me-2 { margin-right: 0.5rem !important; }
|
.me-2 { margin-right: 0.5rem !important; }
|
||||||
|
.me-3 { margin-right: 1rem !important; }
|
||||||
|
|
||||||
.ms-2 { margin-left: 0.5rem !important; }
|
.ms-2 { margin-left: 0.5rem !important; }
|
||||||
.ms-3 { margin-left: 1rem !important; }
|
.ms-3 { margin-left: 1rem !important; }
|
||||||
|
|
||||||
.py-3 {
|
.mx-auto { margin-left: auto !important; margin-right: auto !important; }
|
||||||
padding-top: 1rem !important;
|
|
||||||
padding-bottom: 1rem !important;
|
.p-0 { padding: 0 !important; }
|
||||||
|
.p-2 { padding: 0.5rem !important; }
|
||||||
|
.p-3 { padding: 1rem !important; }
|
||||||
|
|
||||||
|
.py-2 { padding-top: 0.5rem !important; padding-bottom: 0.5rem !important; }
|
||||||
|
.py-3 { padding-top: 1rem !important; padding-bottom: 1rem !important; }
|
||||||
|
.py-4 { padding-top: 1.5rem !important; padding-bottom: 1.5rem !important; }
|
||||||
|
|
||||||
|
.px-2 { padding-left: 0.5rem !important; padding-right: 0.5rem !important; }
|
||||||
|
.px-3 { padding-left: 1rem !important; padding-right: 1rem !important; }
|
||||||
|
.px-4 { padding-left: 1.5rem !important; padding-right: 1.5rem !important; }
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
SIZING UTILITIES (Width/Height críticos)
|
||||||
|
========================================================================== */
|
||||||
|
.w-100 { width: 100% !important; }
|
||||||
|
.w-auto { width: auto !important; }
|
||||||
|
.h-100 { height: 100% !important; }
|
||||||
|
.h-auto { height: auto !important; }
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
TEXT UTILITIES (Críticos para layout)
|
||||||
|
========================================================================== */
|
||||||
|
.text-center { text-align: center !important; }
|
||||||
|
.text-start { text-align: left !important; }
|
||||||
|
.text-end { text-align: right !important; }
|
||||||
|
.text-white { color: #fff !important; }
|
||||||
|
.text-muted { color: var(--bs-secondary-color, #6c757d) !important; }
|
||||||
|
|
||||||
|
.fw-normal { font-weight: 400 !important; }
|
||||||
|
.fw-medium { font-weight: 500 !important; }
|
||||||
|
.fw-semibold { font-weight: 600 !important; }
|
||||||
|
.fw-bold { font-weight: 700 !important; }
|
||||||
|
|
||||||
|
.fs-5 { font-size: 1.25rem !important; }
|
||||||
|
.fs-6 { font-size: 1rem !important; }
|
||||||
|
|
||||||
|
.small { font-size: 0.875em !important; }
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.text-md-start { text-align: left !important; }
|
||||||
|
.text-md-center { text-align: center !important; }
|
||||||
|
.text-md-end { text-align: right !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
@@ -384,6 +529,48 @@ button:focus:not(:focus-visible) {
|
|||||||
.text-decoration-underline {
|
.text-decoration-underline {
|
||||||
text-decoration: underline !important;
|
text-decoration: underline !important;
|
||||||
}
|
}
|
||||||
|
.text-decoration-none {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
IMAGE UTILITIES
|
||||||
|
========================================================================== */
|
||||||
|
.img-fluid {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
ALERT COMPONENT (Above-the-fold notifications)
|
||||||
|
========================================================================== */
|
||||||
|
.alert {
|
||||||
|
--bs-alert-padding-x: 1rem;
|
||||||
|
--bs-alert-padding-y: 1rem;
|
||||||
|
--bs-alert-margin-bottom: 1rem;
|
||||||
|
--bs-alert-border-radius: 0.375rem;
|
||||||
|
position: relative;
|
||||||
|
padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x);
|
||||||
|
margin-bottom: var(--bs-alert-margin-bottom);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: var(--bs-alert-border-radius);
|
||||||
|
}
|
||||||
|
.alert-warning {
|
||||||
|
--bs-alert-color: #664d03;
|
||||||
|
--bs-alert-bg: #fff3cd;
|
||||||
|
--bs-alert-border-color: #ffecb5;
|
||||||
|
color: var(--bs-alert-color);
|
||||||
|
background-color: var(--bs-alert-bg);
|
||||||
|
border-color: var(--bs-alert-border-color);
|
||||||
|
}
|
||||||
|
.alert-info {
|
||||||
|
--bs-alert-color: #055160;
|
||||||
|
--bs-alert-bg: #cff4fc;
|
||||||
|
--bs-alert-border-color: #b6effb;
|
||||||
|
color: var(--bs-alert-color);
|
||||||
|
background-color: var(--bs-alert-bg);
|
||||||
|
border-color: var(--bs-alert-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
BUTTON CLOSE (Dismiss notification)
|
BUTTON CLOSE (Dismiss notification)
|
||||||
|
|||||||
@@ -17,12 +17,16 @@ if (!defined('ABSPATH')) {
|
|||||||
* Estos CSS se cargan con media="print" y onload="this.media='all'"
|
* Estos CSS se cargan con media="print" y onload="this.media='all'"
|
||||||
* para evitar bloquear el renderizado inicial.
|
* para evitar bloquear el renderizado inicial.
|
||||||
*
|
*
|
||||||
* NOTA: Bootstrap NO está diferido porque causa CLS alto (0.954).
|
* NOTA: Bootstrap AHORA está diferido porque CriticalBootstrapService
|
||||||
* Bootstrap debe cargar bloqueante para evitar layout shifts.
|
* inyecta las clases críticas (grid, navbar, utilities) inline en <head>.
|
||||||
|
* Esto permite diferir el bootstrap-subset.min.css sin causar CLS.
|
||||||
*
|
*
|
||||||
* @since 1.0.21
|
* @since 1.0.21
|
||||||
|
* @updated 1.0.23 - Bootstrap diferido gracias a critical-bootstrap.css
|
||||||
*/
|
*/
|
||||||
define('ROI_DEFERRED_CSS', [
|
define('ROI_DEFERRED_CSS', [
|
||||||
|
// Bootstrap (diferido - critical inline via CriticalBootstrapService)
|
||||||
|
'roi-bootstrap',
|
||||||
// Componentes específicos (below the fold)
|
// Componentes específicos (below the fold)
|
||||||
'roi-badges',
|
'roi-badges',
|
||||||
'roi-pagination',
|
'roi-pagination',
|
||||||
@@ -100,18 +104,24 @@ add_action('wp_enqueue_scripts', 'roi_enqueue_fonts', 1);
|
|||||||
* - Reducción: 36% menos CSS para parsear
|
* - Reducción: 36% menos CSS para parsear
|
||||||
* - Generado con PurgeCSS: node build-bootstrap-subset.js
|
* - Generado con PurgeCSS: node build-bootstrap-subset.js
|
||||||
*
|
*
|
||||||
* NOTA: Bootstrap debe cargar BLOQUEANTE (media='all').
|
* NOTA: Bootstrap AHORA es DIFERIDO (media='print' + onload).
|
||||||
* Diferirlo causa CLS alto (0.954) por layout shifts.
|
* CriticalBootstrapService inyecta inline las clases críticas:
|
||||||
|
* - Grid system (row, col-*)
|
||||||
|
* - Navbar component
|
||||||
|
* - Flexbox utilities
|
||||||
|
* - Spacing utilities
|
||||||
|
* Esto evita CLS mientras mejora LCP significativamente.
|
||||||
*/
|
*/
|
||||||
function roi_enqueue_bootstrap() {
|
function roi_enqueue_bootstrap() {
|
||||||
// Bootstrap CSS SUBSET - BLOQUEANTE para evitar CLS
|
// Bootstrap CSS SUBSET - DIFERIDO (critical CSS es inline)
|
||||||
// Original: 227KB -> Subset: 145KB (36% reducción)
|
// Original: 227KB -> Subset: 145KB (36% reducción)
|
||||||
|
// Critical: ~20KB inline en <head> via CriticalBootstrapService
|
||||||
wp_enqueue_style(
|
wp_enqueue_style(
|
||||||
'roi-bootstrap',
|
'roi-bootstrap',
|
||||||
get_template_directory_uri() . '/Assets/Vendor/Bootstrap/Css/bootstrap-subset.min.css',
|
get_template_directory_uri() . '/Assets/Vendor/Bootstrap/Css/bootstrap-subset.min.css',
|
||||||
array('roi-fonts'),
|
array('roi-fonts'),
|
||||||
'5.3.2-subset',
|
'5.3.2-subset',
|
||||||
'all' // Bloqueante - diferirlo causa CLS alto
|
'print' // DIFERIDO - critical CSS inline evita CLS
|
||||||
);
|
);
|
||||||
|
|
||||||
// Bootstrap Icons CSS - SUBSET OPTIMIZADO (Fase 4.1 PageSpeed)
|
// Bootstrap Icons CSS - SUBSET OPTIMIZADO (Fase 4.1 PageSpeed)
|
||||||
|
|||||||
@@ -336,10 +336,11 @@ add_filter( 'wp_resource_hints', 'roi_add_resource_hints', 10, 2 );
|
|||||||
function roi_preload_critical_resources() {
|
function roi_preload_critical_resources() {
|
||||||
$theme_uri = get_template_directory_uri();
|
$theme_uri = get_template_directory_uri();
|
||||||
|
|
||||||
// Preload del CSS de Bootstrap (crítico para el layout)
|
// Preload del CSS de Bootstrap SUBSET (crítico para el layout)
|
||||||
|
// IMPORTANTE: Debe coincidir con el archivo encolado en enqueue-scripts.php
|
||||||
printf(
|
printf(
|
||||||
'<link rel="preload" href="%s" as="style">' . "\n",
|
'<link rel="preload" href="%s" as="style">' . "\n",
|
||||||
esc_url( $theme_uri . '/Assets/Vendor/Bootstrap/Css/bootstrap.min.css' )
|
esc_url( $theme_uri . '/Assets/Vendor/Bootstrap/Css/bootstrap-subset.min.css' )
|
||||||
);
|
);
|
||||||
|
|
||||||
// Preload del CSS de fuentes (crítico para evitar FOIT/FOUT)
|
// Preload del CSS de fuentes (crítico para evitar FOIT/FOUT)
|
||||||
|
|||||||
@@ -283,17 +283,24 @@ function roi_render_component(string $componentName): string {
|
|||||||
* Registra hooks para inyectar CSS crítico en <head>
|
* Registra hooks para inyectar CSS crítico en <head>
|
||||||
*
|
*
|
||||||
* FLUJO:
|
* FLUJO:
|
||||||
* 1. wp_head (priority 1) → CriticalCSSService::render()
|
* 1. wp_head (priority 0) → CriticalBootstrapService::render()
|
||||||
|
* - Inyecta critical-bootstrap.css inline (grid, navbar, utilities)
|
||||||
|
* - Permite diferir bootstrap-subset.min.css
|
||||||
|
*
|
||||||
|
* 2. wp_head (priority 1) → CriticalCSSService::render()
|
||||||
* - Consulta BD por componentes con is_critical=true
|
* - Consulta BD por componentes con is_critical=true
|
||||||
* - Genera CSS usando los métodos públicos generateCSS() de los Renderers
|
* - Genera CSS usando los métodos públicos generateCSS() de los Renderers
|
||||||
* - Output: <style id="roi-critical-css">...</style>
|
* - Output: <style id="roi-critical-css">...</style>
|
||||||
*
|
*
|
||||||
* NOTA: CriticalBootstrapService está DESHABILITADO porque diferir
|
* IMPORTANTE: CriticalBootstrapService HABILITADO para mejorar LCP.
|
||||||
* Bootstrap causa CLS alto (0.954). Bootstrap carga bloqueante.
|
* critical-bootstrap.css incluye grid system para evitar CLS.
|
||||||
*
|
|
||||||
* Cuando los Renderers ejecutan, detectan is_critical y omiten CSS inline.
|
|
||||||
*/
|
*/
|
||||||
add_action('after_setup_theme', function() {
|
add_action('after_setup_theme', function() {
|
||||||
|
// 1. Critical Bootstrap CSS (priority 0) - inline bootstrap crítico
|
||||||
|
$criticalBootstrapService = \ROITheme\Shared\Infrastructure\Services\CriticalBootstrapService::getInstance();
|
||||||
|
add_action('wp_head', [$criticalBootstrapService, 'render'], 0);
|
||||||
|
|
||||||
|
// 2. Critical Component CSS (priority 1) - CSS de componentes críticos
|
||||||
$criticalCSSService = roi_get_critical_css_service();
|
$criticalCSSService = roi_get_critical_css_service();
|
||||||
$hooksRegistrar = new \ROITheme\Shared\Infrastructure\Wordpress\CriticalCSSHooksRegistrar($criticalCSSService);
|
$hooksRegistrar = new \ROITheme\Shared\Infrastructure\Wordpress\CriticalCSSHooksRegistrar($criticalCSSService);
|
||||||
$hooksRegistrar->register();
|
$hooksRegistrar->register();
|
||||||
|
|||||||
Reference in New Issue
Block a user