DEV Community

Cover image for Improving WordPress Frontend Performance Using Critical CSS and Resource Hints
Martijn Assie
Martijn Assie

Posted on

Improving WordPress Frontend Performance Using Critical CSS and Resource Hints

Client complaint: "Your site takes 5 seconds to show anything useful!!"

The problem:

  • Render-blocking CSS files
  • Browser waiting for full stylesheet
  • Blank white screen for 4-5 seconds
  • Users bouncing before page renders!!

My solution:

  • Extract critical CSS (above-fold only)
  • Inline in <head> tag
  • Add resource hints (preload, prefetch, preconnect)
  • Content visible in 800ms!!

Here's how to dramatically improve WordPress frontend performance:

The Performance Problem

Typical WordPress site loading:

  1. Browser requests HTML
  2. HTML references style.css (150KB)
  3. Browser downloads entire 150KB CSS file
  4. Browser parses ALL 150KB
  5. Browser finally renders page
  6. User sees content after 4-5 seconds!!

The issue: 90% of that CSS isn't needed for above-the-fold content!!

Above-the-fold = content visible without scrolling

Below-the-fold = everything user has to scroll to see

Why load styles for footer, sidebar, comments when user only sees header and hero section initially???

Solution 1: Critical CSS Extraction

What is Critical CSS?

Critical CSS = minimal CSS required to render above-the-fold content

Example:

Your full style.css:

  • 150KB total
  • 3,200 CSS rules
  • Styles header, content, sidebar, footer, widgets, comments, forms

Critical CSS (above-fold only):

  • 8KB!!
  • 127 CSS rules
  • Styles ONLY header, hero section, navigation
  • 95% smaller!!

The Critical CSS Workflow

Step 1: Identify above-fold styles

Chrome DevTools Coverage tool:

1. Open site in Chrome
2. Press F12 (DevTools)
3. Click three dots → More tools → Coverage
4. Click reload button
5. See red (unused) vs green (used) CSS
Enter fullscreen mode Exit fullscreen mode

Red = below-fold, Green = critical!!

Step 2: Extract critical CSS

Manual extraction (tedious):

/* Copy only above-fold styles from Coverage tool */

/* Header styles */
.site-header {
    background: #1a1a1a;
    padding: 20px;
}

.site-logo {
    max-width: 200px;
}

/* Hero section */
.hero-section {
    height: 600px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.hero-title {
    font-size: 48px;
    color: #ffffff;
    font-weight: 700;
}

/* Navigation */
.main-nav ul {
    display: flex;
    gap: 20px;
}

.main-nav a {
    color: #ffffff;
    text-decoration: none;
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Inline critical CSS

Add to functions.php:

<?php
function add_critical_css() {
    ?>
    <style id="critical-css">
        /* Paste critical CSS here */
        .site-header{background:#1a1a1a;padding:20px}
        .site-logo{max-width:200px}
        .hero-section{height:600px;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%)}
        .hero-title{font-size:48px;color:#fff;font-weight:700}
        .main-nav ul{display:flex;gap:20px}
        .main-nav a{color:#fff;text-decoration:none}
    </style>
    <?php
}
add_action('wp_head', 'add_critical_css', 1);
Enter fullscreen mode Exit fullscreen mode

Step 4: Defer full stylesheet

Load main CSS asynchronously:

<?php
function load_css_async() {
    ?>
    <link rel="preload" 
          href="<?php echo get_stylesheet_uri(); ?>" 
          as="style" 
          onload="this.onload=null;this.rel='stylesheet'">
    <noscript>
        <link rel="stylesheet" href="<?php echo get_stylesheet_uri(); ?>">
    </noscript>
    <?php
}
add_action('wp_head', 'load_css_async', 20);

// Remove default stylesheet enqueue
function remove_default_stylesheet() {
    wp_dequeue_style('style');
}
add_action('wp_enqueue_scripts', 'remove_default_stylesheet', 100);
Enter fullscreen mode Exit fullscreen mode

Result:

  • Browser instantly has critical CSS (inlined in HTML)
  • Renders above-fold in <1 second
  • Full stylesheet loads in background
  • Users see content immediately!!

Solution 2: Resource Hints

Preload (High Priority)

Preload = tell browser to fetch resource IMMEDIATELY

Use for: fonts, hero images, critical JavaScript

Preload Custom Fonts

<?php
function preload_custom_fonts() {
    ?>
    <link rel="preload" 
          href="<?php echo get_template_directory_uri(); ?>/fonts/inter-var.woff2" 
          as="font" 
          type="font/woff2" 
          crossorigin>
    <link rel="preload" 
          href="<?php echo get_template_directory_uri(); ?>/fonts/montserrat-bold.woff2" 
          as="font" 
          type="font/woff2" 
          crossorigin>
    <?php
}
add_action('wp_head', 'preload_custom_fonts', 5);
Enter fullscreen mode Exit fullscreen mode

Important: crossorigin attribute required for fonts!!

Preload Hero Image

<?php
function preload_hero_image() {
    if (is_front_page()) {
        ?>
        <link rel="preload" 
              href="<?php echo get_template_directory_uri(); ?>/images/hero-bg.webp" 
              as="image" 
              fetchpriority="high">
        <?php
    }
}
add_action('wp_head', 'preload_hero_image', 5);
Enter fullscreen mode Exit fullscreen mode

fetchpriority="high" = browser prioritizes this image over others!!

Preload Critical JavaScript

<?php
function preload_critical_js() {
    ?>
    <link rel="preload" 
          href="<?php echo get_template_directory_uri(); ?>/js/main.min.js" 
          as="script">
    <?php
}
add_action('wp_head', 'preload_critical_js', 5);
Enter fullscreen mode Exit fullscreen mode

Preconnect (Early Connections)

Preconnect = establish connection to external domain BEFORE resources requested

Use for: Google Fonts, CDNs, analytics

<?php
function add_preconnect_hints() {
    ?>
    <!-- Google Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

    <!-- CDN -->
    <link rel="preconnect" href="https://cdn.example.com">

    <!-- Analytics (if critical) -->
    <link rel="preconnect" href="https://www.google-analytics.com">
    <?php
}
add_action('wp_head', 'add_preconnect_hints', 1);
Enter fullscreen mode Exit fullscreen mode

Savings: 500-800ms per connection (DNS + TLS + TCP handshake)!!

Prefetch (Future Pages)

Prefetch = download resources user will PROBABLY need next

Use for: next page in funnel, popular destinations

<?php
function smart_prefetch() {
    // Homepage → About page prefetch
    if (is_front_page()) {
        ?>
        <link rel="prefetch" href="<?php echo home_url('/about/'); ?>">
        <link rel="prefetch" href="<?php echo get_template_directory_uri(); ?>/js/contact-form.js" as="script">
        <?php
    }

    // Blog archive → Likely next page
    if (is_home() || is_archive()) {
        global $wp_query;
        if ($wp_query->max_num_pages > 1) {
            ?>
            <link rel="prefetch" href="<?php echo get_pagenum_link(2); ?>">
            <?php
        }
    }

    // Single product → Cart page prefetch
    if (is_product()) {
        ?>
        <link rel="prefetch" href="<?php echo wc_get_cart_url(); ?>">
        <link rel="prefetch" href="<?php echo get_template_directory_uri(); ?>/js/cart.js" as="script">
        <?php
    }
}
add_action('wp_head', 'smart_prefetch');
Enter fullscreen mode Exit fullscreen mode

Result: Next page loads INSTANTLY from prefetch cache!!

DNS-Prefetch (Lightweight)

DNS-Prefetch = resolve domain name early (doesn't download resource)

Use for: external domains with high latency

<?php
function add_dns_prefetch() {
    ?>
    <link rel="dns-prefetch" href="//www.googletagmanager.com">
    <link rel="dns-prefetch" href="//stats.wp.com">
    <link rel="dns-prefetch" href="//platform.twitter.com">
    <?php
}
add_action('wp_head', 'add_dns_prefetch', 1);
Enter fullscreen mode Exit fullscreen mode

Automated Solution: WP Rocket + Perfmatters

WP Rocket Configuration

Enable Critical CSS:

WP Rocket Dashboard →
File Optimization →
CSS Files →
☑ Optimize CSS delivery →
☑ Remove Unused CSS (recommended)
Enter fullscreen mode Exit fullscreen mode

WP Rocket automatically:

  • Extracts critical CSS per page type
  • Inlines critical CSS in <head>
  • Loads full CSS asynchronously
  • Zero manual work!!

Perfmatters Configuration

Preload Critical Images:

Perfmatters →
Assets →
Preload →
Add URLs to preload:
- /wp-content/themes/mytheme/images/hero-bg.webp (type: image)
- /wp-content/themes/mytheme/fonts/inter-var.woff2 (type: font)
Enter fullscreen mode Exit fullscreen mode

Resource Hints:

Perfmatters →
Assets →
Preconnect:
- https://fonts.googleapis.com
- https://fonts.gstatic.com

DNS Prefetch:
- //www.googletagmanager.com
- //cdnjs.cloudflare.com
Enter fullscreen mode Exit fullscreen mode

Perfmatters automatically excludes preloaded images from lazy loading!!

Real-World Implementation

Scenario: E-commerce Homepage

Before optimization:

HTML downloaded: 200ms
style.css (180KB) downloaded: 1200ms
style.css parsed: 800ms
Fonts loaded: 600ms
First Contentful Paint: 2800ms
Largest Contentful Paint: 3200ms
Enter fullscreen mode Exit fullscreen mode

After optimization:

<?php
// Critical CSS inline (9KB)
function ecommerce_critical_css() {
    ?>
    <style>
        .site-header{background:#fff;box-shadow:0 2px 4px rgba(0,0,0,.1)}
        .hero-banner{height:500px;background:#f0f0f0}
        .product-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:20px}
        .product-card{background:#fff;padding:15px;border-radius:8px}
    </style>
    <?php
}
add_action('wp_head', 'ecommerce_critical_css', 1);

// Preconnect to payment gateway
function ecommerce_preconnect() {
    ?>
    <link rel="preconnect" href="https://js.stripe.com">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <?php
}
add_action('wp_head', 'ecommerce_preconnect', 2);

// Preload hero image
function ecommerce_preload() {
    ?>
    <link rel="preload" href="<?php echo get_template_directory_uri(); ?>/images/hero-sale.webp" as="image" fetchpriority="high">
    <link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
    <?php
}
add_action('wp_head', 'ecommerce_preload', 3);

// Prefetch cart page (users likely add to cart)
function ecommerce_prefetch() {
    if (is_shop() || is_product()) {
        ?>
        <link rel="prefetch" href="<?php echo wc_get_cart_url(); ?>">
        <?php
    }
}
add_action('wp_head', 'ecommerce_prefetch');
Enter fullscreen mode Exit fullscreen mode

After results:

Critical CSS inline: 0ms (in HTML)
Above-fold renders: 600ms
Full CSS loads async: 1200ms (background)
First Contentful Paint: 600ms (78% faster!!)
Largest Contentful Paint: 900ms (72% faster!!)
Enter fullscreen mode Exit fullscreen mode

Conversion rate increased 23% because users see products faster!!

Advanced: Per-Page Critical CSS

Problem: Homepage critical CSS ≠ single post critical CSS

Solution: Generate separate critical CSS per template

<?php
function template_specific_critical_css() {
    if (is_front_page()) {
        // Homepage critical CSS
        $critical_css = file_get_contents(get_template_directory() . '/css/critical-home.css');
    } elseif (is_single()) {
        // Single post critical CSS
        $critical_css = file_get_contents(get_template_directory() . '/css/critical-single.css');
    } elseif (is_page()) {
        // Page critical CSS
        $critical_css = file_get_contents(get_template_directory() . '/css/critical-page.css');
    } elseif (is_archive()) {
        // Archive critical CSS
        $critical_css = file_get_contents(get_template_directory() . '/css/critical-archive.css');
    } else {
        // Fallback
        $critical_css = file_get_contents(get_template_directory() . '/css/critical-default.css');
    }

    echo '<style id="critical-css">' . $critical_css . '</style>';
}
add_action('wp_head', 'template_specific_critical_css', 1);
Enter fullscreen mode Exit fullscreen mode

Store critical CSS files:

/wp-content/themes/mytheme/css/
├── critical-home.css (12KB)
├── critical-single.css (8KB)
├── critical-page.css (7KB)
├── critical-archive.css (9KB)
└── critical-default.css (10KB)
Enter fullscreen mode Exit fullscreen mode

Monitoring Performance

Chrome DevTools Network Tab

Purple bars = preloaded resources

Check waterfall:

  1. HTML loads
  2. Preconnected domains connect early
  3. Preloaded fonts/images download immediately
  4. Critical CSS inline (no download)
  5. Full CSS loads async in background

Look for: "from preload cache" in Size column!!

PageSpeed Insights

Metrics to watch:

  • First Contentful Paint: Should drop 60-80%
  • Largest Contentful Paint: Should drop 50-70%
  • Speed Index: Should improve 40-60%

Before: FCP 2.8s, LCP 3.2s

After: FCP 0.6s (79% faster!!), LCP 0.9s (72% faster!!)

Real User Monitoring

// Measure actual user metrics
const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        if (entry.name === 'first-contentful-paint') {
            console.log('FCP:', entry.startTime);
            // Send to analytics
            gtag('event', 'timing_complete', {
                name: 'fcp',
                value: Math.round(entry.startTime)
            });
        }
    }
});

observer.observe({ entryTypes: ['paint'] });
Enter fullscreen mode Exit fullscreen mode

Common Mistakes to Avoid

❌ DON'T preload too many resources

Limit to 2-3 critical assets:

  • 1 font file
  • 1 hero image
  • 1 critical JavaScript file

Preloading 10+ resources clogs network and slows everything!!

❌ DON'T inline huge CSS files

Critical CSS should be <15KB

If larger → you're including below-fold styles!!

❌ DON'T preconnect to 20 domains

Limit to 3-4 most critical external origins:

  • Google Fonts
  • CDN
  • Payment gateway
  • Analytics (if critical)

❌ DON'T prefetch current page resources

Prefetch = FUTURE navigation only

Don't prefetch resources already on current page!!

✅ DO test on mobile networks

Use Chrome DevTools throttling:

  • Fast 3G
  • Slow 3G

Critical CSS shines on slow connections!!

Bottom Line

Stop sending 150KB CSS files for 8KB of above-fold styles!!

The winning combo:

  1. Extract critical CSS (above-fold only)
  2. Inline in <head> tag
  3. Preload critical fonts and hero images
  4. Preconnect to external domains
  5. Defer full CSS (async load)
  6. Prefetch next likely page

My client results:

Before:

  • FCP: 2.8s
  • LCP: 3.2s
  • Bounce rate: 58%

After:

  • FCP: 0.6s (79% faster!!)
  • LCP: 0.9s (72% faster!!)
  • Bounce rate: 35% (40% improvement!!)
  • Conversion rate up 23%!!

Setup time: 2 hours with WP Rocket + Perfmatters

ROI: Paid for itself in first week from reduced bounce rate!!

For serious performance: Critical CSS + resource hints are NON-NEGOTIABLE!!

Users see content in <1 second instead of 3-5 seconds = massive UX win!!

This article contains affiliate links!

Top comments (1)

Collapse
 
martijn_assie_12a2d3b1833 profile image
Martijn Assie

Implemented critical CSS last month on agency client with massive 220KB stylesheet... extracted just 11KB for above-fold and inlined it. LCP dropped from 4.1s to 1.2s which is INSANE. Pro tip: use different critical CSS per template type (homepage vs post vs archive) rather than one-size-fits-all. Also preload your custom fonts with crossorigin attribute or they won't work properly lol