LESSON 5 โฑ๏ธ 14 min read

CSS & JavaScript Optimization

The Asset Problem

WordPress sites often load:

  • Multiple CSS files (theme, plugins, blocks)
  • Multiple JavaScript files
  • Files that aren't needed on every page

Analyzing Current Assets

// List all enqueued scripts and styles
add_action('wp_print_scripts', function() {
    global $wp_scripts;
    echo "<!-- Scripts: " . implode(', ', $wp_scripts->queue) . " -->";
}, 999);

add_action('wp_print_styles', function() {
    global $wp_styles;
    echo "<!-- Styles: " . implode(', ', $wp_styles->queue) . " -->";
}, 999);

Removing Unused Assets

// Remove block library CSS (if not using Gutenberg frontend)
add_action('wp_enqueue_scripts', function() {
    wp_dequeue_style('wp-block-library');
    wp_dequeue_style('wp-block-library-theme');
    wp_dequeue_style('global-styles');
}, 100);

// Remove jQuery if not needed
add_action('wp_enqueue_scripts', function() {
    if (!is_admin()) {
        wp_deregister_script('jquery');
    }
});

// Remove emoji scripts
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');

// Remove plugin assets on specific pages
add_action('wp_enqueue_scripts', function() {
    if (!is_page('contact')) {
        wp_dequeue_style('contact-form-7');
        wp_dequeue_script('contact-form-7');
    }
});

Defer and Async JavaScript

// Add defer to scripts
add_filter('script_loader_tag', function($tag, $handle, $src) {
    $defer_scripts = ['theme-scripts', 'analytics', 'lazy-load'];
    
    if (in_array($handle, $defer_scripts)) {
        return str_replace(' src', ' defer src', $tag);
    }
    
    return $tag;
}, 10, 3);

// Add async to specific scripts
add_filter('script_loader_tag', function($tag, $handle, $src) {
    if ($handle === 'google-analytics') {
        return str_replace(' src', ' async src', $tag);
    }
    return $tag;
}, 10, 3);

// WordPress 6.3+ strategy attribute
wp_register_script('my-script', '/path/to/script.js', [], '1.0', [
    'strategy' => 'defer',
    'in_footer' => true,
]);

Critical CSS

Inline critical styles to eliminate render-blocking:

// Inline critical CSS
add_action('wp_head', function() {
    $critical_css = file_get_contents(get_template_directory() . '/css/critical.css');
    echo "<style id="critical-css">{$critical_css}</style>";
}, 1);

// Load full CSS asynchronously
add_action('wp_head', function() {
    ?>
    <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
}, 2);

Generating Critical CSS

# Using Critical (Node.js)
npm install -g critical

critical https://example.com --base ./ --inline --minify > critical.css

# Using Penthouse
npm install -g penthouse

penthouse https://example.com/style.css --url https://example.com > critical.css

Minification

Using Build Tools

// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
    optimization: {
        minimize: true,
        minimizer: [
            new TerserPlugin({
                terserOptions: {
                    compress: { drop_console: true },
                },
            }),
            new CssMinimizerPlugin(),
        ],
    },
};

WordPress Plugins

PluginFeatures
AutoptimizeCombine, minify, defer
WP RocketAll-in-one
Asset CleanUpRemove per-page
PerfmattersScript manager

Code Splitting

Load JavaScript only when needed:

// Dynamic import
button.addEventListener('click', async () => {
    const module = await import('./heavy-module.js');
    module.init();
});

// Intersection Observer for lazy loading
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            import('./carousel.js').then(module => module.init(entry.target));
            observer.unobserve(entry.target);
        }
    });
});

document.querySelectorAll('.carousel').forEach(el => observer.observe(el));

HTTP/2 and Bundling

With HTTP/2, many small files can be faster than one large bundle:

// Individual critical modules
wp_enqueue_script('navigation', '/js/nav.js', [], '1.0', true);
wp_enqueue_script('accessibility', '/js/a11y.js', [], '1.0', true);

// Lazy-load non-critical
wp_register_script('carousel', '/js/carousel.js', [], '1.0', true);
// Only enqueue where needed

Font Loading Optimization

// Preload critical fonts
add_action('wp_head', function() {
    ?>
    <link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
    <?php
}, 1);

// Google Fonts optimization
add_action('wp_enqueue_scripts', function() {
    wp_enqueue_style('google-fonts', 
        'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap',
        [],
        null
    );
});

// Add preconnect
add_action('wp_head', function() {
    echo '<link rel="preconnect" href="https://fonts.googleapis.com">';
    echo '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>';
}, 1);

Resource Hints

<!-- Preconnect to third-party origins -->
<link rel="preconnect" href="https://cdn.example.com">

<!-- DNS prefetch for less critical -->
<link rel="dns-prefetch" href="https://analytics.example.com">

<!-- Preload critical resources -->
<link rel="preload" href="/hero.webp" as="image">
<link rel="preload" href="/critical.js" as="script">

<!-- Prefetch next page resources -->
<link rel="prefetch" href="/next-page/">

Next Steps

In the next lesson, we'll learn to profile and debug performance issues.

๐ŸŽฏ Lesson Complete! You can now optimize CSS and JavaScript delivery effectively.