Skip to content

FOUC with auto_load_bundle: Need better pattern for stylesheet_pack_tag in head #1864

@justin808

Description

@justin808

Problem

When using auto_load_bundle = true with server-side rendering, there's no straightforward way to prevent FOUC (Flash of Unstyled Content) because stylesheets must load in the <head>, but react_component auto-appends happen during body rendering.

Current Constraint

Shakapacker requires append_stylesheet_pack_tag to be called before stylesheet_pack_tag. With auto_load_bundle = true, react_component automatically calls append_stylesheet_pack_tag during rendering. This creates a chicken-and-egg problem:

  • To prevent FOUC, stylesheet_pack_tag should be in <head>
  • But react_component calls in <body> trigger appends after the head has rendered
  • This violates the "append before main tag" constraint

Current Workaround

The only solution is to use content_for to render the entire body BEFORE the head:

<% content_for :body_content do %>
  <%= react_component "NavigationBarApp" %>
  
  <div class="container">
    <%= yield %>
  </div>
  
  <%= react_component "Footer" %>
<% end %>
<!DOCTYPE html>
<html>
<head>
  <%= append_stylesheet_pack_tag('stimulus-bundle') %>
  <%= stylesheet_pack_tag(media: 'all') %>
  <%= javascript_pack_tag(defer: true) %>
</head>
<body>
  <%= yield :body_content %>
</body>
</html>

This works but is:

  • Non-intuitive (body content defined before <!DOCTYPE html>)
  • Not documented anywhere
  • Easy to get wrong
  • Confusing for developers unfamiliar with the pattern

See working example: shakacode/react-webpack-rails-tutorial#686

Proposed Solutions

Option 1: Document the Pattern

Add clear documentation showing the content_for :body_content pattern for preventing FOUC with SSR and auto_load_bundle.

Option 2: Built-in Helper

Provide a helper that handles this automatically:

<!DOCTYPE html>
<html>
<head>
  <%= react_on_rails_head_tags do %>
    <%= append_stylesheet_pack_tag('stimulus-bundle') %>
  <% end %>
</head>
<body>
  <%= react_component "NavigationBarApp" %>
  <%= yield %>
</body>
</html>

The react_on_rails_head_tags helper would internally:

  1. Capture and render the body first to collect auto-appends
  2. Execute the provided block (explicit appends)
  3. Render stylesheet_pack_tag and javascript_pack_tag
  4. Return only the head tag content

Option 3: Config for content_for Block Name

Allow react_component with auto_load_bundle to put appends into a named content_for block:

# config/initializers/react_on_rails.rb
config.auto_load_bundle = true
config.auto_load_bundle_target = :head # Appends go into content_for :head

Then in layout:

<head>
  <%= yield :head %>
  <%= stylesheet_pack_tag(media: 'all') %>
</head>
<body>
  <%= react_component "MyComponent" %>
</body>

Related Issues

This issue affects any SSR application using auto_load_bundle that wants to avoid FOUC by loading stylesheets in the head.

Environment

  • react_on_rails: 16.1.1
  • shakapacker: 9.1.0
  • Rails: 8.0

Related shakapacker documentation issue: shakacode/shakapacker#720

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions