CrackedRuby CrackedRuby

Overview

The critical rendering path represents the sequence of steps browsers execute to convert HTML, CSS, and JavaScript into rendered pixels on screen. Understanding this path enables developers to identify and eliminate bottlenecks that delay initial page rendering. The browser must construct the Document Object Model (DOM) from HTML, build the CSS Object Model (CSSOM) from stylesheets, combine these into a render tree, calculate layout geometry, and finally paint pixels to the screen.

Each step in this pipeline depends on previous steps completing. The browser cannot render content until it constructs both the DOM and CSSOM. Render-blocking resources like external stylesheets and synchronous scripts halt this progression, increasing the time until users see content. Optimizing the critical rendering path focuses on minimizing the time from request initiation to first meaningful paint.

Ruby web applications control the HTML structure, asset organization, and resource delivery that directly impact critical rendering path performance. Rails applications, Sinatra services, and other Ruby web frameworks determine how browsers receive and process resources. A Ruby application serving bloated HTML with multiple render-blocking CSS files creates a slower critical rendering path than one delivering optimized, prioritized resources.

# Basic HTML generation affecting critical rendering path
class PageController < ApplicationController
  def index
    # Browser cannot render until all CSS loads
    @stylesheets = ['reset.css', 'layout.css', 'components.css', 'theme.css']
    
    # Synchronous scripts block parsing
    @scripts = ['jquery.js', 'analytics.js', 'app.js']
  end
end

The distinction between loading a resource and executing it matters for performance. The browser can download resources in parallel but must process them sequentially for construction steps. A page with ten small CSS files performs differently than one large concatenated file, even at identical total bytes, due to network overhead and parsing behavior.

Key Principles

The critical rendering path consists of five distinct stages that browsers execute in sequence. Each stage transforms data from the previous stage until pixels appear on screen.

DOM Construction occurs when the browser parses HTML markup into a tree structure representing document elements. The parser processes HTML incrementally, building the DOM as bytes arrive. The parser encounters opening tags, creates corresponding nodes, establishes parent-child relationships, and handles closing tags. External resources referenced in HTML (images, stylesheets, scripts) trigger additional requests but don't block DOM construction unless they are parser-blocking resources.

# HTML structure affecting DOM construction speed
def optimized_structure
  <<~HTML
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <!-- Critical CSS inlined -->
      <style>#{critical_css}</style>
    </head>
    <body>
      <!-- Content appears here without waiting for external CSS -->
      <h1>Immediate Content</h1>
      
      <!-- Non-critical CSS loads asynchronously -->
      <link rel="stylesheet" href="full.css" media="print" onload="this.media='all'">
    </body>
    </html>
  HTML
end

CSSOM Construction builds a parallel tree structure from CSS rules. Unlike HTML, CSS requires complete parsing before the browser can proceed. The CSSOM must be complete because CSS rules are hierarchical and cascade - the browser cannot determine final styles without processing all rules. A stylesheet loaded at the end of the document still blocks rendering just as much as one in the head.

The browser computes styles by traversing the CSSOM and DOM together, applying rules based on specificity and cascade order. Selectors like div.container > p.text require the browser to match patterns against DOM nodes. Complex selectors increase computation time, though modern browsers optimize this aggressively.

Render Tree Construction combines the DOM and CSSOM to create a tree containing only visible nodes with computed styles. The browser omits nodes with display: none, script tags, meta tags, and other non-visual elements. Each render tree node contains geometric and styling information needed for layout. The render tree represents what will appear on screen, not every DOM node.

# Generating HTML that creates efficient render trees
class LayoutOptimizer
  def conditional_content(user)
    # Avoid DOM nodes that never render
    return '' unless user.premium?
    
    # Generate only visible content server-side
    %(<div class="premium-feature">#{premium_content}</div>)
  end
  
  def hidden_vs_absent
    # Creates DOM node with display:none (still in render tree)
    bad = '<div style="display:none">Hidden</div>'
    
    # Better: don't generate the HTML at all
    good = user.show_feature? ? '<div>Visible</div>' : ''
    
    good
  end
end

Layout (or reflow) calculates the exact position and size of each render tree node. The browser starts at the root and traverses the tree, computing geometric properties based on viewport dimensions and CSS box model rules. Layout is expensive because it affects the entire page - changing one element's size can cascade to many others. The browser outputs a box model where each node has exact coordinates.

Paint converts the layout tree into actual pixels on screen. The browser creates layers for certain elements (position: fixed, transforms, opacity), paints each layer, and composites them in the correct order. Paint operations include drawing text, colors, images, borders, and shadows. The browser paints in a specific order: background, borders, content, outlines.

The critical rendering path blocks on CSS completely but handles JavaScript differently. Scripts with the defer or async attribute don't block parsing. Scripts without these attributes halt DOM construction, execute, then resume parsing. This blocking occurs because scripts might modify the DOM or query computed styles, requiring complete CSSOM availability.

# Script loading strategies in Ruby templates
module ScriptLoader
  def self.critical_scripts
    # Inline small, essential scripts
    <<~JS
      <script>
        // Critical feature detection
        if (!('querySelector' in document)) {
          window.location = '/legacy-browser.html';
        }
      </script>
    JS
  end
  
  def self.deferred_scripts
    # Non-critical scripts don't block rendering
    %(<script defer src="/js/analytics.js"></script>)
  end
  
  def self.async_scripts
    # Independent scripts load without blocking
    %(<script async src="/js/social-widgets.js"></script>)
  end
end

Performance Considerations

Critical rendering path optimization targets three metrics: minimizing critical resources, reducing critical bytes, and shortening critical path length. Critical resources are files required before first render - typically HTML, critical CSS, and synchronous JavaScript. Each additional critical resource adds network round trips. Critical bytes represent the total file size of critical resources. Critical path length measures the longest dependency chain.

Inlining Critical CSS eliminates a render-blocking request by embedding essential styles directly in HTML. The browser can render above-the-fold content immediately without waiting for external stylesheets. Critical CSS typically includes layout styles, fonts, colors, and visibility rules for initial viewport content. The remaining CSS loads asynchronously.

# Extracting and inlining critical CSS in Rails
class CriticalCssGenerator
  def initialize(view_path)
    @view_path = view_path
  end
  
  def extract_critical
    # Use tools like critical or penthouse to analyze page
    # Returns CSS needed for above-the-fold rendering
    critical_rules = analyze_viewport_css(@view_path)
    minify(critical_rules)
  end
  
  private
  
  def analyze_viewport_css(path)
    # Identify CSS rules affecting initial viewport
    # This typically happens at build time, not runtime
    CriticalCssExtractor.extract(
      path: path,
      viewport: { width: 1300, height: 900 }
    )
  end
  
  def minify(css)
    css.gsub(/\s+/, ' ').gsub(/\s*([{}:;,])\s*/, '\1')
  end
end

# Usage in views
def page_with_critical_css
  critical = CriticalCssGenerator.new('pages/home').extract_critical
  
  <<~HTML
    <head>
      <style>#{critical}</style>
      <link rel="preload" href="/css/full.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
      <noscript><link rel="stylesheet" href="/css/full.css"></noscript>
    </head>
  HTML
end

Deferring Non-Critical JavaScript prevents scripts from blocking HTML parsing. The defer attribute loads scripts in parallel with parsing and executes them after DOM construction completes but before DOMContentLoaded fires. Multiple deferred scripts execute in order. The async attribute loads scripts in parallel and executes them as soon as downloaded, potentially out of order.

# Managing script loading priorities
class AssetTagHelper
  def script_tag(source, critical: false, defer: true)
    attributes = []
    
    if critical
      # Critical scripts load synchronously (use sparingly)
      %(<script src="#{source}"></script>)
    elsif defer
      # Most application scripts should defer
      %(<script src="#{source}" defer></script>)
    else
      # Independent third-party scripts can be async
      %(<script src="#{source}" async></script>)
    end
  end
end

# In layout template
class ApplicationLayout
  def script_tags
    [
      script_tag('/js/app.js', defer: true),
      script_tag('/js/analytics.js', critical: false, defer: false),
      script_tag('https://cdn.example.com/widget.js', critical: false, defer: false)
    ].join("\n")
  end
end

Resource Prioritization guides browsers to fetch critical resources first. The <link rel="preload"> hint tells browsers to fetch resources with high priority without blocking rendering. This proves useful for fonts, critical images, and other resources discovered late in HTML parsing.

# Resource hints for optimal loading
class ResourcePreloader
  def self.preload_hints
    <<~HTML
      <!-- Preload critical font files -->
      <link rel="preload" href="/fonts/primary.woff2" as="font" type="font/woff2" crossorigin>
      
      <!-- Preload hero image -->
      <link rel="preload" href="/images/hero.jpg" as="image">
      
      <!-- Preconnect to third-party domains -->
      <link rel="preconnect" href="https://api.example.com">
      <link rel="dns-prefetch" href="https://cdn.example.com">
    HTML
  end
end

Compression and Minification reduce critical bytes. Gzip or Brotli compression typically reduces text resources by 70-80%. Minification removes whitespace, comments, and renames variables in JavaScript. CSS minification strips whitespace and eliminates redundant rules.

# Asset compression in Rack middleware
class AssetCompressor
  def initialize(app)
    @app = app
  end
  
  def call(env)
    status, headers, response = @app.call(env)
    
    if compressible?(headers)
      compressed = compress_body(response)
      headers['Content-Encoding'] = 'gzip'
      headers['Content-Length'] = compressed.bytesize.to_s
      return [status, headers, [compressed]]
    end
    
    [status, headers, response]
  end
  
  private
  
  def compressible?(headers)
    content_type = headers['Content-Type']
    content_type&.match?(/text|javascript|json|css/)
  end
  
  def compress_body(response)
    body = response.respond_to?(:body) ? response.body : response.join
    Zlib.gzip(body)
  end
end

Font Loading Strategies prevent invisible text during font downloads. The font-display CSS property controls rendering behavior. The value swap shows fallback fonts immediately, then swaps to custom fonts when available. The value optional uses custom fonts only if they load quickly.

Server-Side Rendering delivers complete HTML instead of JavaScript frameworks rendering content client-side. Users see content faster because rendering happens on the server rather than after downloading and executing JavaScript. This particularly benefits mobile users with slower devices and connections.

# Server-side rendering reduces critical path length
class ServerRenderedPage < ApplicationController
  def show
    @data = Product.featured.limit(10)
    
    # Render complete HTML on server
    # Browser displays content immediately without waiting for JS
    render :show
  end
end

# Contrast with client-side rendering approach
class ClientRenderedPage < ApplicationController
  def show
    # Returns minimal HTML with empty containers
    # Browser must download JS, execute it, fetch data, then render
    render :client_shell
  end
  
  def data
    # JavaScript fetches this after page load
    render json: Product.featured.limit(10)
  end
end

Code Splitting breaks JavaScript into smaller chunks loaded on demand. The initial bundle contains only code needed for first render. Additional features load when users navigate to them. This reduces critical bytes and speeds initial rendering.

Ruby Implementation

Ruby web applications control critical rendering path performance through HTML generation, asset organization, and server configuration. Rails provides several mechanisms for optimizing resource delivery.

Asset Pipeline Configuration manages JavaScript and CSS compilation, minification, and fingerprinting. The asset pipeline concatenates files, reducing HTTP requests. Fingerprinting enables long-term caching while ensuring browsers fetch updated files.

# config/initializers/assets.rb
Rails.application.config.assets.configure do |env|
  # Compress assets in production
  env.js_compressor = :terser
  env.css_compressor = :sass
  
  # Precompile assets at deploy time
  env.precompile += %w[
    critical.css
    application.js
    admin.js
  ]
end

# Separate critical CSS from main bundle
# app/assets/stylesheets/critical.css.scss
// Only above-the-fold styles
.header { /* ... */ }
.hero { /* ... */ }
.nav { /* ... */ }

# app/assets/stylesheets/application.css.scss
// Everything else loads asynchronously
@import 'components/*';
@import 'pages/*';

View Helpers for Optimized Loading generate HTML with appropriate resource hints and loading attributes.

# app/helpers/performance_helper.rb
module PerformanceHelper
  def critical_css_tag
    if Rails.env.production?
      # Inline critical CSS in production
      content_tag :style do
        Rails.application.assets['critical.css'].to_s.html_safe
      end
    else
      # Link to file in development for easier debugging
      stylesheet_link_tag 'critical'
    end
  end
  
  def deferred_stylesheet_tag(source)
    # Load non-critical CSS without blocking render
    tag.link(
      rel: 'preload',
      href: asset_path(source),
      as: 'style',
      onload: "this.onload=null;this.rel='stylesheet'"
    ) +
    tag.noscript do
      stylesheet_link_tag(source)
    end
  end
  
  def optimized_script_tag(source, **options)
    defaults = { defer: true }
    javascript_include_tag(source, **defaults.merge(options))
  end
end

# Usage in layout
<!DOCTYPE html>
<html>
<head>
  <%= critical_css_tag %>
  <%= deferred_stylesheet_tag 'application' %>
</head>
<body>
  <%= yield %>
  <%= optimized_script_tag 'application' %>
</body>
</html>

Conditional Content Generation prevents unnecessary DOM nodes from reaching the browser.

# app/controllers/pages_controller.rb
class PagesController < ApplicationController
  def show
    # Only query data that will be rendered
    @featured_products = Product.visible.featured if show_featured_section?
    @user_recommendations = current_user&.recommendations&.limit(5)
    
    # Avoid querying data for hidden content
    @admin_panel = nil unless current_user&.admin?
  end
  
  private
  
  def show_featured_section?
    # Server-side logic prevents generating unused HTML
    params[:category] == 'home' && !mobile_request?
  end
end

# app/views/pages/show.html.erb
<% if @featured_products.present? %>
  <section class="featured">
    <%= render @featured_products %>
  </section>
<% end %>

<!-- Better than: -->
<!-- <section class="featured" style="display:none"> -->

HTTP/2 Server Push proactively sends resources before browsers request them. This eliminates round trips for critical resources discovered during HTML parsing.

# Puma configuration with Early Hints
# config/puma.rb
early_hints true

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_early_hints
  
  private
  
  def set_early_hints
    # Server pushes these resources with HTTP 103 Early Hints
    response.headers['Link'] = [
      '</assets/critical.css>; rel=preload; as=style',
      '</assets/application.js>; rel=preload; as=script',
      '</fonts/primary.woff2>; rel=preload; as=font; crossorigin'
    ].join(', ')
  end
end

Fragment Caching reduces server processing time, allowing faster response delivery.

# app/views/products/show.html.erb
<% cache @product do %>
  <article class="product">
    <h1><%= @product.name %></h1>
    
    <% cache [@product, 'description'] do %>
      <%= render 'description', product: @product %>
    <% end %>
    
    <% cache [@product, 'specifications'] do %>
      <%= render 'specifications', product: @product %>
    <% end %>
  </article>
<% end %>

Content Delivery Network Integration serves assets from edge locations closer to users.

# config/environments/production.rb
Rails.application.configure do
  # CDN hosts for asset URLs
  config.asset_host = 'https://cdn.example.com'
  
  # CORS headers for fonts and other cross-origin resources
  config.public_file_server.headers = {
    'Cache-Control' => 'public, max-age=31536000',
    'Access-Control-Allow-Origin' => '*'
  }
end

# Different CDN for images
# app/helpers/image_helper.rb
module ImageHelper
  def cdn_image_tag(source, **options)
    cdn_url = "https://images.cdn.example.com/#{source}"
    image_tag(cdn_url, **options)
  end
end

Practical Examples

Example 1: Converting Render-Blocking CSS

A typical application initially loads all CSS synchronously, blocking rendering until complete.

# Initial implementation - blocks rendering
# app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>My Application</title>
  <%= stylesheet_link_tag 'reset' %>
  <%= stylesheet_link_tag 'layout' %>
  <%= stylesheet_link_tag 'components' %>
  <%= stylesheet_link_tag 'pages' %>
  <%= stylesheet_link_tag 'theme' %>
</head>
<body>
  <%= yield %>
</body>
</html>

This approach delays rendering until all five stylesheets download and parse. Optimizing requires identifying critical styles and loading non-critical styles asynchronously.

# Optimized implementation
# app/helpers/stylesheet_helper.rb
module StylesheetHelper
  def inline_critical_styles
    content_tag :style do
      critical_css_content.html_safe
    end
  end
  
  def async_stylesheet_link(source)
    content_tag :link,
      nil,
      rel: 'preload',
      href: asset_path(source),
      as: 'style',
      onload: "this.onload=null;this.rel='stylesheet'"
  end
  
  private
  
  def critical_css_content
    # In production, read pre-extracted critical CSS
    # In development, include basic layout styles
    if Rails.env.production?
      File.read(Rails.root.join('public', 'assets', 'critical.css'))
    else
      render_critical_dev_styles
    end
  end
end

# app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>My Application</title>
  <%= inline_critical_styles %>
  <%= async_stylesheet_link 'application.css' %>
  <noscript>
    <%= stylesheet_link_tag 'application' %>
  </noscript>
</head>
<body>
  <%= yield %>
</body>
</html>

Example 2: Optimizing Font Loading

Web fonts often cause flash of invisible text (FOIT) during load. Optimizing font loading improves perceived performance.

# app/assets/stylesheets/fonts.css.scss
@font-face {
  font-family: 'Primary';
  src: url('/fonts/primary.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap; // Show fallback immediately, swap when loaded
}

@font-face {
  font-family: 'Primary';
  src: url('/fonts/primary-bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

# app/helpers/font_helper.rb
module FontHelper
  def preload_fonts
    fonts = [
      '/fonts/primary.woff2',
      '/fonts/primary-bold.woff2'
    ]
    
    fonts.map do |font|
      tag.link(
        rel: 'preload',
        href: asset_path(font),
        as: 'font',
        type: 'font/woff2',
        crossorigin: 'anonymous'
      )
    end.join.html_safe
  end
end

# app/views/layouts/application.html.erb
<head>
  <%= preload_fonts %>
  <style>
    body {
      font-family: 'Primary', -apple-system, BlinkMacSystemFont, sans-serif;
    }
  </style>
</head>

Example 3: Progressive HTML Streaming

Ruby applications can stream HTML responses, sending content as it generates rather than waiting for complete rendering.

# app/controllers/products_controller.rb
class ProductsController < ApplicationController
  def index
    # Stream response for faster first byte
    self.response_body = Enumerator.new do |stream|
      # Send HTML header immediately
      stream << render_to_string(
        partial: 'layouts/header',
        layout: false
      )
      
      # Stream products as they load from database
      Product.find_each(batch_size: 10) do |batch|
        stream << render_to_string(
          partial: 'product',
          collection: batch,
          layout: false
        )
      end
      
      # Send footer
      stream << render_to_string(
        partial: 'layouts/footer',
        layout: false
      )
    end
  end
end

Example 4: Responsive Image Loading

Serving appropriately sized images reduces critical bytes and speeds rendering.

# app/helpers/responsive_image_helper.rb
module ResponsiveImageHelper
  def responsive_image_tag(source, **options)
    # Generate multiple sizes
    sizes = [400, 800, 1200, 1600]
    
    srcset = sizes.map do |width|
      "#{image_url(source, width: width)} #{width}w"
    end.join(', ')
    
    image_tag(
      image_url(source, width: 800),
      srcset: srcset,
      sizes: '(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 800px',
      loading: 'lazy', # Defer off-screen images
      **options
    )
  end
  
  private
  
  def image_url(source, width:)
    # Integration with image processing service
    "https://images.cdn.example.com/#{source}?w=#{width}&q=85"
  end
end

# Usage
<%= responsive_image_tag(
  'product.jpg',
  alt: 'Product image',
  class: 'product-image'
) %>

Tools & Ecosystem

Lighthouse audits web pages for performance, accessibility, and best practices. The tool measures critical rendering path metrics including First Contentful Paint, Largest Contentful Paint, and Time to Interactive. Lighthouse identifies render-blocking resources and provides optimization recommendations.

# Gemfile
gem 'lighthouse-rb' # Ruby wrapper for Lighthouse CLI

# lib/tasks/performance.rake
namespace :performance do
  desc 'Run Lighthouse audit on production homepage'
  task audit: :environment do
    require 'lighthouse'
    
    report = Lighthouse.audit(
      url: 'https://example.com',
      emulated_form_factor: 'mobile',
      throttling_method: 'simulate',
      categories: ['performance']
    )
    
    puts "Performance Score: #{report.score}"
    puts "First Contentful Paint: #{report.metrics.first_contentful_paint}"
    puts "Largest Contentful Paint: #{report.metrics.largest_contentful_paint}"
    
    # Identify render-blocking resources
    blocking_resources = report.audits['render-blocking-resources']
    if blocking_resources.details
      puts "\nRender-blocking resources:"
      blocking_resources.details.items.each do |item|
        puts "  #{item.url} - #{item.total_bytes} bytes"
      end
    end
  end
end

WebPageTest provides detailed waterfall charts showing resource loading timelines. The service tests from multiple locations and connection speeds, revealing critical rendering path bottlenecks.

PageSpeed Insights combines Lighthouse metrics with real-user data from Chrome User Experience Report. This reveals how actual users experience a site's performance.

Rails Performance Tools include several gems for optimizing critical rendering path:

# Gemfile
gem 'turbo-rails'       # Reduces full-page loads
gem 'propshaft'         # Modern asset pipeline
gem 'image_processing'  # Optimize uploaded images
gem 'rack-mini-profiler' # Development performance insights

# Critical CSS extraction (build-time tool)
gem 'critical_css_rails', group: :development

Critical CSS Extraction Tools analyze pages to identify above-the-fold styles:

# lib/tasks/critical_css.rake
namespace :assets do
  desc 'Extract critical CSS for key pages'
  task extract_critical: :environment do
    require 'critical_css'
    
    pages = {
      'home' => 'http://localhost:3000/',
      'products' => 'http://localhost:3000/products',
      'checkout' => 'http://localhost:3000/checkout'
    }
    
    pages.each do |name, url|
      critical_css = CriticalCss.extract(
        url: url,
        width: 1300,
        height: 900,
        ignore: [/\.optional/, /@media print/]
      )
      
      output_path = Rails.root.join('app', 'assets', 'stylesheets', "critical_#{name}.css")
      File.write(output_path, critical_css)
      puts "Extracted critical CSS for #{name} (#{critical_css.bytesize} bytes)"
    end
  end
end

Browser DevTools provide real-time critical rendering path analysis. Chrome DevTools Performance panel shows each rendering stage with precise timing. The Coverage panel identifies unused CSS and JavaScript. Network panel displays resource loading waterfall with blocking indicators.

Content Delivery Networks accelerate asset delivery through edge caching:

# config/initializers/cdn.rb
Rails.application.configure do
  if Rails.env.production?
    config.action_controller.asset_host = ENV['CDN_HOST']
    
    # Purge CDN cache on deploy
    config.after_initialize do
      if defined?(Cloudflare)
        Cloudflare.configure do |c|
          c.api_token = ENV['CLOUDFLARE_TOKEN']
          c.zone_id = ENV['CLOUDFLARE_ZONE']
        end
      end
    end
  end
end

# Automated cache purging
class CdnCachePurger
  def self.purge_assets
    client = Cloudflare::Client.new
    
    files = [
      'https://cdn.example.com/assets/application.css',
      'https://cdn.example.com/assets/application.js'
    ]
    
    client.purge_files(files)
  end
end

Common Pitfalls

Blocking Resources in Head represents the most frequent critical rendering path mistake. Developers place all stylesheets and scripts in the document head, forcing the browser to wait for each resource before rendering.

# Problematic pattern
<head>
  <%= stylesheet_link_tag 'reset' %>
  <%= stylesheet_link_tag 'layout' %>
  <%= stylesheet_link_tag 'components' %>
  <%= javascript_include_tag 'jquery' %>
  <%= javascript_include_tag 'analytics' %>
  <%= javascript_include_tag 'application' %>
</head>

# Solution: Differentiate critical and non-critical resources
<head>
  <%= inline_critical_styles %>
  <%= async_stylesheet_link 'application' %>
</head>
<body>
  <%= yield %>
  <%= javascript_include_tag 'application', defer: true %>
  <%= javascript_include_tag 'analytics', async: true %>
</body>

Excessive Inline Styles overcorrects by inlining too much CSS. Inlining hundreds of kilobytes of styles eliminates caching benefits and increases HTML size. Critical CSS should only include styles for above-the-fold content, typically 10-20KB.

# Problematic: Inlining entire stylesheet
<head>
  <style>
    <%= Rails.application.assets['application.css'].to_s %>
  </style>
</head>

# Correct: Only inline critical subset
<head>
  <style>
    <%= Rails.application.assets['critical.css'].to_s %>
  </style>
  <%= async_stylesheet_link 'application' %>
</head>

Font Loading Without Optimization causes flash of invisible text or layout shifts. Browsers hide text during font downloads by default (FOIT behavior). Specifying font-display: swap shows text immediately with fallback fonts.

Unused CSS and JavaScript increases critical bytes unnecessarily. Many applications include entire frameworks when using minimal features. Tree-shaking and code splitting remove unused code.

# Remove unused framework features
# Before: Including entire Bootstrap
gem 'bootstrap', '~> 5.0'

# After: Import only needed components
# app/assets/stylesheets/application.scss
@import 'bootstrap/functions';
@import 'bootstrap/variables';
@import 'bootstrap/grid';
@import 'bootstrap/utilities';
// Exclude unused components like carousel, modals, etc.

Third-Party Script Blocking occurs when external scripts load synchronously. Analytics, advertisements, and social widgets block rendering when loaded without async or defer attributes.

# Problematic: Synchronous third-party scripts
<head>
  <script src="https://analytics.example.com/track.js"></script>
  <script src="https://ads.example.com/serve.js"></script>
</head>

# Solution: Async loading for third-party content
<head>
  <script async src="https://analytics.example.com/track.js"></script>
  <script async src="https://ads.example.com/serve.js"></script>
</head>

Image-Heavy Above-Fold Content delays rendering when large images load synchronously. Hero images and banners increase critical path length without optimization.

# Use responsive images with appropriate sizing
module ImageOptimizer
  def optimized_hero(source)
    image_tag(
      cdn_image_url(source, width: 1200, quality: 85),
      srcset: hero_srcset(source),
      sizes: '100vw',
      fetchpriority: 'high', # Prioritize hero image
      alt: 'Hero image'
    )
  end
  
  private
  
  def hero_srcset(source)
    [400, 800, 1200, 1600].map do |w|
      "#{cdn_image_url(source, width: w, quality: 85)} #{w}w"
    end.join(', ')
  end
end

Deeply Nested DOM Structures increase layout calculation time. Browsers traverse the DOM tree to compute layout, and deeply nested structures with many children slow this process.

Excessive Reflows occur when JavaScript repeatedly modifies layout properties. Reading layout properties (offsetHeight, clientWidth) forces synchronous layout calculation. Batching DOM modifications reduces reflow count.

Reference

Critical Rendering Path Stages

Stage Input Output Blocks Rendering
DOM Construction HTML bytes Document Object Model No (incremental)
CSSOM Construction CSS bytes CSS Object Model Yes (complete parse required)
Render Tree DOM + CSSOM Visible nodes with styles Yes
Layout Render tree Geometry of each node Yes
Paint Layout tree Pixels on screen Yes

Resource Loading Attributes

Attribute Loading Execution Parse Blocking Use Case
None (default) Immediately When loaded Yes Critical synchronous scripts
async Parallel with parsing As soon as loaded No Independent third-party scripts
defer Parallel with parsing After DOM ready No Application scripts needing full DOM
preload High priority parallel Manual No Critical resources discovered late
prefetch Low priority parallel Manual No Resources for next navigation

Font Display Values

Value Behavior Block Period Swap Period Use When
auto Browser default Short (~100ms) Infinite Browser decides best approach
block Hide text Long (~3s) Infinite Font critical to design
swap Show fallback immediately None Infinite Readability over design consistency
fallback Hide briefly Short (~100ms) Short (~3s) Balance readability and design
optional Hide briefly Short (~100ms) None Font only if cached

Performance Metrics

Metric Measures Target Critical Path Impact
First Contentful Paint (FCP) First text/image render < 1.8s Direct - measures critical path completion
Largest Contentful Paint (LCP) Largest content render < 2.5s Indirect - affected by critical resources
Time to Interactive (TTI) Page fully interactive < 3.8s Direct - requires JS parsing completion
Total Blocking Time (TBT) Main thread blocking time < 200ms Direct - long tasks delay interactivity
Cumulative Layout Shift (CLS) Visual stability < 0.1 Indirect - affected by font/image loading

Rails Helper Methods

Method Purpose Example Output
stylesheet_link_tag Generate style link link rel=stylesheet
javascript_include_tag Generate script tag script src=path
image_tag Generate img element img src=path alt=text
asset_path Generate asset URL /assets/file-fingerprint.ext
content_tag Generate HTML tag Custom element with content

Critical CSS Size Guidelines

Content Type Typical Critical CSS Size Notes
Marketing page 8-12 KB Simple layout, hero section
E-commerce listing 12-18 KB Product grids, filters
Application dashboard 15-25 KB Complex layouts, navigation
Blog post 5-10 KB Minimal chrome, content focus
Maximum recommended 14 KB Fits in TCP initial congestion window

HTTP/2 Optimization Patterns

Pattern Benefit Implementation
Server Push Eliminates round trips Early Hints header with resource links
Multiplexing Parallel resource loading Multiple files over single connection
Header Compression Reduces overhead HPACK compression automatic
Prioritization Critical resources first Stream priority in HTTP/2 frames
Binary Protocol Faster parsing HTTP/2 handles automatically