Preventing FOUC (Flash of Unstyled Content)
Overview
FOUC (Flash of Unstyled Content) occurs when content is rendered before stylesheets load, causing a brief flash of unstyled content. This guide explains how to prevent FOUC when using Shakapacker's view helpers.
Basic Solution
Place stylesheet_pack_tag in the <head> section of your layout, not at the bottom of the <body>. This ensures styles load before content is rendered.
Recommended layout structure:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My App</title>
<%= stylesheet_pack_tag 'application', media: 'all' %>
<%= javascript_pack_tag 'application', defer: true %>
</head>
<body>
<%= yield %>
</body>
</html>
Advanced: Using content_for with Dynamic Pack Loading
If you're using libraries that dynamically append packs during rendering (like React on Rails with auto_load_bundle), or if you need to append packs from views/partials, you must ensure that all append_* helpers execute before the pack tags render.
Rails' content_for pattern solves this execution order problem.
The content_for :body_content Pattern
This pattern renders the body content first, allowing all append calls to register before the pack tags in the head are rendered:
<% content_for :body_content do %>
<%= render 'shared/header' %>
<%= yield %>
<%= render 'shared/footer' %>
<% end %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My App</title>
<%= stylesheet_pack_tag 'application', media: 'all' %>
<%= javascript_pack_tag 'application', defer: true %>
</head>
<body>
<%= yield :body_content %>
</body>
</html>
How this works:
- The
content_for :body_contentblock executes first during template rendering - Any
append_stylesheet_pack_tagorappend_javascript_pack_tagcalls in your views/partials register their packs - Libraries like React on Rails can auto-append component-specific packs during rendering
- The pack tags in
<head>then render with all registered appends - Finally,
yield :body_contentoutputs the pre-rendered content
Result:
- ✅ All appends (explicit + auto) happen before pack tags
- ✅ Stylesheets load in head, eliminating FOUC
- ✅ Works with
auto_load_bundleand similar features
Alternative: Using yield :head for Explicit Appends
For simpler cases where you know which packs you need upfront and can explicitly specify them, you can use content_for :head in your views and yield it in your layout.
Layout (app/views/layouts/application.html.erb):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My App</title>
<%= yield :head %>
<%= stylesheet_pack_tag 'application', media: 'all' %>
<%= javascript_pack_tag 'application', defer: true %>
</head>
<body>
<%= yield %>
</body>
</html>
View (app/views/pages/show.html.erb):
<% content_for :head do %>
<%= append_stylesheet_pack_tag 'my-component' %>
<%= append_javascript_pack_tag 'my-component' %>
<% end %>
<h1>My Page</h1>
<p>Content goes here...</p>
This approach works when:
- You're not using auto-appending libraries
- You can explicitly list all required packs in each view
- You don't need dynamic pack determination
Key Takeaways
- Always place
stylesheet_pack_tagin<head>to prevent FOUC - Use
content_forto control execution order when usingappend_*helpers - Ensure
append_*helpers execute before the main pack tags - JavaScript can use
defer: true(default) or be placed at end of<body> - The
content_for :body_contentpattern is essential when using auto-appending libraries
Related
- View Helpers Documentation
- Troubleshooting Guide
- Original issue: #720
- Working implementation: react-webpack-rails-tutorial PR #686