Skip to content

Fix FOUC in Pro spec/dummy app by inlining critical CSS#2336

Merged
justin808 merged 9 commits intomasterfrom
jg/fix-issue-2100
Feb 8, 2026
Merged

Fix FOUC in Pro spec/dummy app by inlining critical CSS#2336
justin808 merged 9 commits intomasterfrom
jg/fix-issue-2100

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Feb 1, 2026

Summary

  • Inline critical Tailwind CSS classes used in the layout structure to prevent Flash of Unstyled Content (FOUC)
  • Add preload hint for the CSS bundle to speed up loading

Problem

The Pro spec/dummy app showed unstyled HTML on initial page load before the CSS bundle finished loading. This created a poor first impression when running bin/dev.

Solution

Extract critical CSS to a separate _critical_styles.html.erb partial for better maintainability. The partial includes:

  • Layout structure (flex containers, sizing)
  • Overflow handling
  • Spacing
  • Background colors
  • Borders

Verified Fix

Tested locally with Playwright using network throttling (20 Kbps):

Without fix: Single-column layout, no sidebar, no colors - classic FOUC
With fix: Proper two-column layout, gray sidebar, correct structure even during slow CSS load

Fixes #2100

Test plan

  • Verified FOUC exists without the fix (Playwright screenshot during throttled load)
  • Verified fix eliminates FOUC (layout renders correctly before CSS loads)
  • Manual testing with bin/dev and Slow 3G throttling

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Expanded troubleshooting to distinguish FOUC subtypes, added “layout jumps,” and added detailed guidance, examples, and maintenance tips for utility‑first CSS frameworks recommending inlining critical CSS.
  • New Features

    • Added an inline critical‑CSS pattern and integration guidance plus layout adjustments to render critical styles in the head to reduce FOUC and initial layout shifts.
  • Tests

    • Added system tests verifying critical styles are inlined before the main stylesheet to prevent FOUC.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 1, 2026

Walkthrough

Adds an inlined critical-CSS partial to the dummy app and renders it in the head before the main stylesheet to prevent FOUC/layout jumps with Tailwind; expands troubleshooting docs with a "Type 2" section describing inline critical CSS and maintenance; adds a system test asserting ordering of critical styles and bundle CSS.

Changes

Cohort / File(s) Summary
Documentation
docs/core-concepts/auto-bundling-file-system-based-automated-bundle-generation.md, docs/deployment/troubleshooting.md
Reworded FOUC entry to “Unstyled content flash, layout jumps.” Added a "Type 2" section for Tailwind/utility-first frameworks with symptoms, root cause, a problematic example, and a step-by-step inline critical CSS solution including partials, inclusion patterns, examples, and maintenance guidance.
Dummy app layout & partial
react_on_rails_pro/spec/dummy/app/views/layouts/_critical_styles.html.erb, react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
Added _critical_styles.html.erb containing inlined critical CSS for the initial two-column layout and render it in the head before the main stylesheet. Updated layout markup to use app- prefixed wrappers and placed hydration data inside the updated main content area.
System test
react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb
Added system test "Critical styles for FOUC prevention" that asserts an inline data-critical-styles block exists in the head and verifies it appears before the client-bundle.css link; ordering check is skipped if the stylesheet link is absent.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

"I thumped my paw at the jittery sight, 🐇
Pasted a snippet to steady the light.
No more jumps when the stylesheet streams,
Small inlined rules calm wild dreams.
A rabbit nods — the page now gleams."

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: fixing FOUC by inlining critical CSS in the Pro spec/dummy app.
Linked Issues check ✅ Passed The PR implements the recommended solution from #2100: inlines critical layout CSS and adds preload hints, addressing all core requirements.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing FOUC: documentation updates, critical CSS partial, layout refactoring, and integration tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jg/fix-issue-2100

No actionable comments were generated in the recent review. 🎉


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 1, 2026

Code Review: Fix FOUC in Pro spec/dummy app

✅ Summary

This PR effectively addresses the Flash of Unstyled Content (FOUC) issue by inlining critical Tailwind CSS and adding a CSS preload hint. The approach is sound and follows web performance best practices.


🎯 Strengths

  1. Targeted Solution: Inlines only the ~30 critical CSS classes actually used in the layout structure, avoiding unnecessary bloat
  2. Well-Documented: Clear comments explain the purpose of the inlined CSS
  3. Performance Optimization: The preload_pack_asset hint should help speed up CSS loading
  4. Minimal Impact: Only affects the Pro dummy app layout, no changes to core library code
  5. Follows Best Practices: This is a standard technique for preventing FOUC with utility-first CSS frameworks

🔍 Code Quality Observations

Positive:

  • Clean, organized CSS with helpful section comments
  • Precise class definitions matching Tailwind's output
  • No redundant or unused styles inlined
  • Maintains separation between critical (inlined) and full CSS bundle

Minor Suggestions:

  1. Escaped Class Name (line 32):

    .min-w-\[400px\] { min-width: 400px; }
    .max-w-\[400px\] { max-width: 400px; }

    These are correct - Tailwind's arbitrary value syntax requires the escaping. No change needed, but worth noting this is expected.

  2. CSS Duplication: The inlined styles will be duplicated in the full CSS bundle. This is intentional and correct for critical CSS patterns - the small duplication (~1-2KB) is worth the FOUC prevention.

  3. Maintenance Consideration: If the layout classes change, the inlined CSS must be updated manually. Consider adding a comment reminder:

    <%# NOTE: If layout structure changes, update inlined critical CSS below to match %>

🧪 Testing Recommendations

Before merging, please verify:

  1. Visual Testing:

    cd react_on_rails_pro/spec/dummy
    bin/dev
    # Open in browser with Network > Slow 3G throttling
    # Verify: No layout jump, sidebar renders correctly from the start
  2. Cache Testing: Clear browser cache and test a fresh page load to ensure preload hint works

  3. Cross-Browser: Test in Chrome/Firefox/Safari to ensure inlined CSS renders consistently


🚨 Potential Issues

None identified. This is a low-risk change with clear benefits.


📊 Performance Impact

Expected improvements:

  • ✅ Eliminates visible FOUC on slow connections
  • ✅ Reduces Layout Shift (CLS) metric
  • ✅ Faster perceived load time
  • ⚠️ Adds ~1-2KB to HTML (negligible, worth the trade-off)

🔒 Security Considerations

No security concerns. The inlined styles are static CSS, no dynamic content or user input.


📝 Suggestions for Future Enhancement

  1. Automated Critical CSS Extraction: Consider using tools like critical or critters to automatically extract critical CSS during build

  2. Apply to Open Source Dummy: The open-source dummy app (react_on_rails/spec/dummy) doesn't use Tailwind heavily, but if it did in the future, this pattern would be useful there too

  3. Document Pattern: Add a note to CLAUDE.md or developer docs about this critical CSS pattern for future reference


✅ Recommendation

APPROVE - This PR is ready to merge after manual testing confirms the FOUC is resolved.

The solution is:

  • ✅ Correct implementation
  • ✅ Follows best practices
  • ✅ Low risk
  • ✅ Clear documentation
  • ✅ Addresses the reported issue effectively

Next Steps:

  1. Complete the test plan checklist in the PR description
  2. Verify no visual regressions
  3. Merge when CI passes (if applicable)

📚 References

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 1, 2026

Greptile Overview

Greptile Summary

This PR addresses FOUC in the Pro dummy app by adding a _critical_styles.html.erb partial that inlines minimal, layout-defining CSS (via semantic selectors like .app-shell, .app-sidebar) into the <head> before the main stylesheet loads. The layout markup is updated to include these semantic classes alongside Tailwind utilities, and the troubleshooting docs are expanded to distinguish SSR auto_load_bundle FOUC vs utility-first “layout jump” FOUC and document the critical-CSS pattern.

A system spec is added to ensure the inline critical styles are present in the rendered HTML head and (when a CSS bundle link is present) appear before the CSS bundle reference.

Confidence Score: 4/5

  • This PR is generally safe to merge once the new rack_test spec is made robust against missing critical-style markers.
  • Core change (inline critical CSS + semantic selectors) is localized to the Pro dummy layout and documentation. The only merge-blocking concern is brittleness in the new system spec where ordering assertions can end up comparing nil positions when expected markers are absent.
  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb

Important Files Changed

Filename Overview
docs/core-concepts/auto-bundling-file-system-based-automated-bundle-generation.md Adds a note pointing to troubleshooting guidance for Tailwind/utility-first FOUC layout jumps.
docs/deployment/troubleshooting.md Expands FOUC troubleshooting into two types and documents an inline critical-CSS approach for utility-first frameworks.
react_on_rails_pro/spec/dummy/app/views/layouts/_critical_styles.html.erb Introduces a new partial that inlines minimal critical layout CSS using semantic selectors and a data attribute for testability.
react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb Renders the critical-styles partial in and adds semantic wrapper classes to layout elements to prevent FOUC.
react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb Adds rack_test coverage ensuring critical inline styles exist in and (conditionally) precede the CSS bundle link; test may be brittle due to nil comparison when bundle link is absent.

Sequence Diagram

sequenceDiagram
  participant B as Browser
  participant R as Rails layout (application.html.erb)
  participant P as Critical styles partial (_critical_styles)
  participant W as Webpack/Shakapacker CSS bundle

  B->>R: GET /
  R->>P: render "layouts/critical_styles" in <head>
  P-->>R: <style data-critical-styles>…</style>
  R-->>B: HTML with inline critical CSS
  B->>B: First paint uses critical CSS (layout + colors)
  B->>W: Load CSS bundle (preload/link)
  W-->>B: Full Tailwind CSS
  B->>B: Apply full CSS (no visible layout jump)
Loading

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 1, 2026

Code Review - PR #2336

✅ Overall Assessment

This is a well-implemented solution to the FOUC issue. The approach is sound, well-documented, and follows best practices for critical CSS inlining.


🎯 Strengths

  1. Minimal and Focused: Only ~30 lines of critical CSS, targeting layout-critical utilities. This keeps the inline CSS overhead low while solving the visual problem.

  2. Excellent Documentation: The comments in _critical_styles.html.erb clearly explain:

    • Why this exists (FOUC prevention)
    • What should be included (only layout-critical styles)
    • Maintenance guidance (update when layout structure changes)
  3. Performance Optimization: Added preload_pack_asset for CSS bundle, which is a best practice for speeding up CSS loading.

  4. Separation of Concerns: Using a partial (_critical_styles.html.erb) keeps the main layout clean and makes the critical CSS easy to locate and maintain.


💡 Suggestions for Improvement

1. Consider Using content_for Pattern

Instead of a partial, you could use Rails' content_for to make it more explicit that this is head content:

<!-- In _critical_styles.html.erb, rename to _critical_styles_content.html.erb -->
<% content_for :critical_styles do %>
  <style>
    /* ... critical styles ... */
  </style>
<% end %>

<!-- In application.html.erb -->
<%= yield :critical_styles %>

Trade-off: This is more idiomatic Rails but doesn't provide much functional benefit. The current approach is fine.

2. Potential Future Enhancement: Automated Extraction

For maintainability, consider documenting (or implementing) an approach to automatically extract critical CSS from the Tailwind config. Tools like critical npm package can analyze HTML and extract required CSS.

Example documentation to add to the partial:

<%#
  Future enhancement: Consider using automated critical CSS extraction:
  - Install: pnpm add -D critical
  - Extract: critical input.html --base dist --inline
  - This ensures critical styles stay in sync with layout changes
%>

3. Missing Browser Support for Escaped Characters

Line 21-22 uses escaped square brackets:

.min-w-\[400px\] { min-width: 400px; }
.max-w-\[400px\] { max-width: 400px; }

✅ This is correct - the backslash escaping is necessary for Tailwind's arbitrary value syntax. Good job getting this right!


🔍 Potential Issues

Maintenance Risk: Styles Can Drift from Tailwind

Issue: If someone updates the Tailwind config or layout structure, they might forget to update the critical styles.

Severity: Medium - could lead to subtle FOUC returning

Mitigation: The comment in the partial helps, but consider:

  1. Add a comment in application.html.erb near the layout classes:
<div class="flex flex-row h-screen w-screen">
  <%# IMPORTANT: Critical styles for these classes are in _critical_styles.html.erb %>
  <div class="flex flex-col overflow-y-auto p-5 bg-slate-100 ...">
  1. Add to your test plan: Document in the PR that future layout changes should check critical styles.

🧪 Testing Considerations

Your test plan is good, but consider adding:

## Test Plan
- [x] Run `bin/dev` in `react_on_rails_pro/spec/dummy`
- [x] Load any page with network throttling (Slow 3G)
- [x] Verify layout renders correctly without flash of unstyled content
- [ ] **Verify CSS load order**: Check DevTools Network tab to confirm critical CSS loads before external bundle
- [ ] **Test with disabled JavaScript**: Ensure critical layout works without JS
- [ ] **Test multiple pages**: Verify critical styles work across different routes

🔒 Security & Performance

No security concerns: Inline styles are static, no XSS risk
Performance impact is minimal: ~1KB of inline CSS is negligible
No bundling changes: Uses existing preload_pack_asset helper correctly


📋 Checklist Before Merge

Per CLAUDE.md requirements:

  • Run bundle exec rubocop (should pass on .erb files)
  • Ensure files end with newlines (already correct ✅)
  • Test locally with bin/dev and network throttling (per test plan)
  • Consider adding a comment in application.html.erb linking layout classes to critical styles

🎬 Recommendation

APPROVE with minor suggestions

This PR effectively solves the FOUC issue with a clean, well-documented approach. The suggestions above are optional improvements that could be addressed in this PR or follow-up work.

The critical CSS approach is industry-standard for FOUC prevention, and the implementation is solid. Once you've verified the test plan, this is ready to merge.

Great work! 🚀

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 4, 2026

Code Review - PR #2336: Fix FOUC in Pro spec/dummy app

✅ Overall Assessment

This is a well-executed PR that addresses a real UX issue (Flash of Unstyled Content) with a targeted solution. The implementation is clean, well-documented, and provides educational value to users through comprehensive documentation.


🎯 Strengths

1. Excellent Documentation

  • The troubleshooting guide in docs/deployment/troubleshooting.md is exceptionally clear and comprehensive
  • Distinguishes between two types of FOUC (SSR with auto_load_bundle vs. Tailwind/utility-first CSS)
  • Provides step-by-step solutions with code examples
  • Includes helpful guidelines for maintaining critical CSS
  • Links to real-world implementation for reference

2. Clean Implementation

  • Critical CSS is properly extracted to a dedicated partial (_critical_styles.html.erb)
  • Good separation of concerns - layout structure vs. component styles
  • Inline comments clearly explain the purpose and maintenance requirements
  • Minimal CSS footprint - only includes layout-critical classes

3. Performance Optimization

  • Added preload_pack_asset hint for CSS bundle - good performance practice
  • Critical CSS inlined in <head> ensures immediate rendering
  • Focuses only on layout-affecting properties (flex, sizing, overflow, backgrounds)

4. Cross-linking

  • Added reference in auto-bundling docs pointing to the new troubleshooting section
  • Creates a discoverable path for users encountering this issue

⚠️ Areas for Improvement

1. Missing CHANGELOG Entry 🔴 CRITICAL

Per CLAUDE.md, user-visible changes should be documented in the CHANGELOG. This PR should have an entry since it:

Recommendation:

#### Fixed
- [PR 2336](https://github.com/shakacode/react_on_rails/pull/2336) by [justin808](https://github.com/justin808): Fixed Flash of Unstyled Content (FOUC) in Pro spec/dummy app by inlining critical Tailwind CSS classes

#### Added
- [PR 2336](https://github.com/shakacode/react_on_rails/pull/2336): Added comprehensive troubleshooting guide for FOUC issues with Tailwind/utility-first CSS frameworks

Since this affects the Pro package, it may belong in CHANGELOG_PRO.md (check if that file exists in Pro directory).

2. Missing Automated Tests 🟡 MODERATE

The PR description mentions:

  • ✅ Manual testing with Playwright using network throttling
  • ✅ Verified FOUC exists without the fix
  • ✅ Verified fix eliminates FOUC
  • ❌ No automated test added to prevent regression

Recommendation:

Add a Playwright E2E test in react_on_rails_pro/spec/dummy/e2e/playwright/e2e/ to verify:

  1. Critical CSS classes are present in the <head> before the main CSS loads
  2. Layout structure renders correctly even when CSS bundle is delayed
  3. No layout shift occurs when main CSS loads

Example test outline:

test('critical CSS prevents FOUC on initial load', async ({ page }) => {
  // Block CSS bundle loading
  await page.route('**/client-bundle.css', route => route.abort());
  
  await page.goto('/');
  
  // Verify layout structure is correct even without main CSS
  const sidebar = page.locator('.bg-slate-100');
  await expect(sidebar).toHaveCSS('display', 'flex');
  await expect(sidebar).toHaveCSS('background-color', 'rgb(241, 245, 249)');
});

Per CLAUDE.md, Playwright integration is available via cypress-on-rails gem. This would prevent regressions if someone removes/modifies the critical CSS.

3. Potential Maintenance Burden 🟡 MODERATE

The critical CSS partial must be manually kept in sync with application.html.erb. If someone updates the layout classes without updating critical CSS, FOUC returns.

Recommendations:

  • Add a comment in application.html.erb at line 40 (where layout classes are defined) referencing _critical_styles.html.erb
  • Consider adding a simple RSpec test that parses both files and warns if critical classes are missing
  • Document this maintenance requirement in the Pro spec/dummy README if one exists

Example comment:

<%# IMPORTANT: Layout classes below must be defined in _critical_styles.html.erb to prevent FOUC %>
<div class="flex flex-row h-screen w-screen">

4. Tailwind-Specific Escaping 🟢 MINOR

The documentation correctly shows escaping for arbitrary values (.min-w-\[400px\]), but could mention that this is only needed for arbitrary values in Tailwind (the square bracket syntax).

Standard Tailwind classes like .flex, .bg-slate-100 don't need escaping.

Suggestion: Add a note in the documentation:

**Escape special characters** - Tailwind's arbitrary value syntax like `min-w-[400px]` uses square brackets that need escaping in CSS: `.min-w-\[400px\]`. Standard utility classes don't require escaping.

5. Missing Border Color in Critical CSS 🟢 MINOR

In application.html.erb:41, the layout uses border-slate-700, but _critical_styles.html.erb defines it without the color value initially shown in the diff.

Current in critical styles:

.border-r-2 { border-right-width: 2px; }

Layout uses:

class="... border-solid border-r-2 border-slate-700 ...">

The actual file shows .border-slate-700 { border-color: #334155; } is included, so this is good. Just verify the colors match Tailwind's defaults.


🔒 Security & Performance Considerations

No security concerns - Inline CSS is safe and appropriate for critical styles
Performance improvement - Reduces layout shift and improves perceived load time
No bundle bloat - Minimal inline CSS (~40 lines) is acceptable trade-off


📊 Test Coverage Assessment

Manual testing completed:

  • ✅ Playwright screenshots with network throttling
  • ✅ Visual verification before/after fix

Missing automated coverage:

  • ❌ E2E test for FOUC prevention
  • ❌ Regression test for critical CSS maintenance

Recommendation: Add at least one Playwright test before merging to prevent future regressions.


🎓 Educational Value

This PR provides excellent learning material for React on Rails users. The troubleshooting guide will help many developers facing similar FOUC issues with modern CSS frameworks.


✅ Final Recommendations

  1. MUST FIX before merge:

    • Add CHANGELOG entry (per CLAUDE.md guidelines)
  2. SHOULD ADD before merge:

    • Playwright E2E test for FOUC prevention
    • Maintenance comment in application.html.erb linking to critical styles
  3. COULD ENHANCE later:

    • Automated test to verify critical CSS stays in sync with layout
    • Document this pattern in Pro-specific documentation

🎯 Verdict

Approve with minor changes - The core solution is solid and well-documented. Adding the CHANGELOG entry is required per project guidelines. The automated test is strongly recommended but could be added in a follow-up PR if time is constrained.

Great work on the comprehensive documentation! 🚀

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@docs/deployment/troubleshooting.md`:
- Around line 280-284: The two absolute GitHub URLs pointing to
`_critical_styles.html.erb` and `application.html.erb` cause CI link checks to
fail because they reference the master branch; replace those absolute GitHub
links in the docs/deployment/troubleshooting.md entry with repository-relative
Markdown links to the same filenames (use relative path links that reference the
files within the repo instead of full GitHub URLs) so the links resolve against
the current PR branch during CI.

Comment thread docs/deployment/troubleshooting.md Outdated
Comment on lines +280 to +284
**Real-world example:** See the React on Rails Pro `spec/dummy` app implementation:

- [`_critical_styles.html.erb`](https://github.com/shakacode/react_on_rails/blob/master/react_on_rails_pro/spec/dummy/app/views/layouts/_critical_styles.html.erb)
- [`application.html.erb`](https://github.com/shakacode/react_on_rails/blob/master/react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix broken documentation links causing CI failure.

The pipeline is failing because these links point to files on the master branch, but the files don't exist there yet—they're being added in this PR. Once the PR merges, the links will work, but CI checks links against the current state.

Consider using relative paths instead of absolute GitHub URLs, which work regardless of branch:

Proposed fix
 **Real-world example:** See the React on Rails Pro `spec/dummy` app implementation:

-- [`_critical_styles.html.erb`](https://github.com/shakacode/react_on_rails/blob/master/react_on_rails_pro/spec/dummy/app/views/layouts/_critical_styles.html.erb)
-- [`application.html.erb`](https://github.com/shakacode/react_on_rails/blob/master/react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb)
+- [`_critical_styles.html.erb`](../../react_on_rails_pro/spec/dummy/app/views/layouts/_critical_styles.html.erb)
+- [`application.html.erb`](../../react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb)
🤖 Prompt for AI Agents
In `@docs/deployment/troubleshooting.md` around lines 280 - 284, The two absolute
GitHub URLs pointing to `_critical_styles.html.erb` and `application.html.erb`
cause CI link checks to fail because they reference the master branch; replace
those absolute GitHub links in the docs/deployment/troubleshooting.md entry with
repository-relative Markdown links to the same filenames (use relative path
links that reference the files within the repo instead of full GitHub URLs) so
the links resolve against the current PR branch during CI.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 4, 2026

Code Review - PR #2336: Fix FOUC in Pro spec/dummy app

✅ Overall Assessment

This is a well-executed fix for a real UX problem. The solution is clean, well-documented, and follows best practices for critical CSS inlining.


🎯 Strengths

1. Excellent Documentation

  • The troubleshooting guide is comprehensive and educational
  • Clear distinction between Type 1 (SSR auto_load_bundle) and Type 2 (Tailwind/utility-first) FOUC
  • Step-by-step instructions with examples make it easy for users to implement
  • Good cross-referencing between docs

2. Maintainable Implementation

  • Critical CSS is extracted to a separate partial (_critical_styles.html.erb) - excellent separation of concerns
  • Inline comments explain the purpose and maintenance requirements
  • Minimal CSS footprint - only layout-critical classes included

3. Performance Optimization

  • Added preload_pack_asset for CSS bundle (line 24) - good practice for speeding up CSS loading
  • Critical CSS is truly minimal (~40 lines) - won't significantly impact HTML size

4. Cross-Linking Documentation

  • Added note in auto-bundling docs pointing to troubleshooting guide
  • Users encountering layout jumps will find the solution easily

🔍 Potential Issues & Suggestions

1. Missing Test Coverage ⚠️

According to CLAUDE.md:

CRITICAL - LOCAL TESTING REQUIREMENTS:

  1. NEVER claim a test is "fixed" without running it locally first

Concern: The PR description mentions Playwright testing with network throttling, but:

  • No automated E2E test was added to verify the fix
  • The test plan shows manual testing is incomplete: [ ] Manual testing with bin/dev and Slow 3G throttling

Recommendation:

// Consider adding a Playwright test like:
// react_on_rails_pro/spec/dummy/e2e/playwright/e2e/react_on_rails/fouc_prevention.spec.js

test('critical CSS prevents FOUC on slow network', async ({ page }) => {
  // Throttle network to simulate slow 3G
  await page.route('**/*.css', route => route.continue({ 
    delay: 3000  // 3 second delay
  }));
  
  await page.goto('/');
  
  // Check layout is correct BEFORE CSS loads
  const sidebar = page.locator('.bg-slate-100');
  await expect(sidebar).toHaveCSS('background-color', 'rgb(241, 245, 249)');
  await expect(sidebar).toHaveCSS('display', 'flex');
});

2. Maintenance Risk: CSS Synchronization ⚠️

The critical CSS must stay in sync with the layout. If someone updates application.html.erb with new Tailwind classes, they might forget to update _critical_styles.html.erb.

Suggestions:

  1. Add a comment in application.html.erb at the layout div:
<!-- IMPORTANT: If you change Tailwind classes here, update _critical_styles.html.erb -->
<div class="flex flex-row h-screen w-screen">
  1. Consider a follow-up task to add a test that validates the critical CSS contains all classes used in the layout

3. Minor: Missing CSS Class in Critical Styles

I notice the layout uses .border-slate-700 (line 41 of application.html.erb), and it's included in _critical_styles.html.erb (line 39), but the documentation example (troubleshooting.md:258-259) doesn't include the border color.

Current docs:

.border-solid { border-style: solid; }
.border-r-2 { border-right-width: 2px; }

Should be:

.border-solid { border-style: solid; }
.border-r-2 { border-right-width: 2px; }
.border-slate-700 { border-color: #334155; }

This is correct in the actual implementation but inconsistent with the documentation example.

4. Performance: Preload Placement

The preload_pack_asset is great, but it's placed AFTER the critical CSS (line 24). For optimal performance, preload hints should come first:

<%= preload_pack_asset('client-bundle.css', as: 'style') %>
<%= render "layouts/critical_styles" %>
<style>
  html { font-size: 16px !important; }
</style>
<%= stylesheet_pack_tag('client-bundle', ...) %>

This allows the browser to start fetching the CSS earlier while parsing the critical styles.


🔒 Security Considerations

✅ No security concerns. The critical CSS is static and doesn't include user input.


🎨 Code Quality

✅ Follows project conventions:

  • ERB partial naming convention
  • CSS formatting is clean
  • Comments are helpful and concise

📋 Recommendations Before Merge

Must Have:

  1. ✅ Complete manual testing with bin/dev and Slow 3G (currently unchecked in test plan)
  2. ⚠️ Fix documentation inconsistency - add .border-slate-700 to the example

Nice to Have:

  1. Add automated Playwright test for FOUC prevention
  2. Add maintenance comment in application.html.erb about updating critical CSS
  3. Reorder preload hint before critical CSS for optimal performance

📊 Final Verdict

Approve with minor changes recommended. This is a solid fix for a real UX issue with excellent documentation. The main concern is the missing automated test coverage, which should be addressed to prevent regressions.

Risk Level: Low
Complexity: Low
User Impact: High (positive - eliminates jarring layout jumps)

Great work on the comprehensive documentation! 🎉

@coderabbitai coderabbitai Bot added the bug label Feb 4, 2026
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 5, 2026

Code Review - PR #2336: Fix FOUC in Pro spec/dummy app

✅ Overall Assessment

This is a well-executed fix for a real UX issue with excellent documentation. The approach is sound and follows best practices for critical CSS inlining.


🎯 Strengths

1. Excellent Problem-Solution Documentation

  • Clear identification of the FOUC issue specific to utility-first CSS frameworks
  • Distinguished from the existing SSR-related FOUC (Type 1 vs Type 2) - this categorization is helpful
  • Comprehensive troubleshooting guide with step-by-step instructions
  • Good cross-referencing between docs

2. Maintainable Implementation

  • Critical CSS is in a separate partial (_critical_styles.html.erb) - excellent separation of concerns
  • Well-commented with clear maintenance instructions
  • Minimal and focused - only includes layout-critical styles

3. Performance Optimization

  • Added preload_pack_asset('client-bundle.css', as: 'style') - good practice for faster CSS loading
  • Kept critical CSS minimal (40 lines) to avoid bloating HTML

4. Correct Technical Approach

  • Properly escapes Tailwind's arbitrary value syntax (min-w-\[400px\])
  • Includes only layout-affecting properties (flex, sizing, overflow, colors, borders)
  • Places critical CSS before the main stylesheet in <head>

🔍 Areas for Improvement

1. Missing Test Coverage ⚠️

The PR description mentions manual testing with Playwright and network throttling, but there's no automated test to prevent regression.

Recommendation: Add a Playwright E2E test to verify FOUC prevention:

// react_on_rails_pro/spec/dummy/e2e/playwright/e2e/fouc_prevention.spec.js
import { test, expect } from '@playwright/test';

test.describe('FOUC Prevention', () => {
  test('layout renders correctly before CSS bundle loads', async ({ page, context }) => {
    // Block CSS bundle to simulate slow loading
    await page.route('**/client-bundle.css', route => {
      setTimeout(() => route.continue(), 5000);
    });

    await page.goto('/');

    // Check that critical layout styles are applied immediately
    const layout = page.locator('body > div');
    await expect(layout).toHaveCSS('display', 'flex');
    await expect(layout).toHaveCSS('flex-direction', 'row');
    
    const sidebar = page.locator('body > div > div').first();
    await expect(sidebar).toHaveCSS('background-color', 'rgb(241, 245, 249)'); // bg-slate-100
    await expect(sidebar).toHaveCSS('min-width', '400px');
  });
});

Why this matters: Per CLAUDE.md guidelines:

"NEVER claim a test is 'fixed' without running it locally first"
"Distinguish hypothetical fixes from confirmed fixes"

While you tested manually, automated tests prevent future regressions when layout changes.

2. Documentation Discrepancy 📝

The critical CSS in the documentation example (troubleshooting.md:235-260) does not include spacing utilities (.p-5, .px-2), but the actual implementation does (lines 28-30 in _critical_styles.html.erb).

Fix options:

  1. Update docs to include spacing in the example (recommended - more complete)
  2. Remove spacing from _critical_styles.html.erb if it's not critical for layout

Spacing IS used in the layout (application.html.erb:41, 44, 56), so I recommend updating the documentation to match the implementation.

3. Potential Maintenance Burden 🔧

The critical CSS must stay in sync with layout classes. Consider adding:

<%# CRITICAL: Keep in sync with application.html.erb lines 40-55
    Classes used: flex, flex-row, flex-col, flex-1, h-screen, w-screen,
    min-w-[400px], max-w-[400px], overflow-y-auto, overflow-x-hidden,
    p-5, px-2, bg-white, bg-slate-100, border-solid, border-r-2, border-slate-700
%>

This makes it easier to audit and update.

4. Performance Consideration 🚀

The preload hint is good, but you could also consider:

<%= preload_pack_asset('client-bundle.css', as: 'style', crossorigin: 'anonymous') %>

The crossorigin attribute may be needed depending on CDN configuration (not critical for this PR).


🔐 Security & Best Practices

No security concerns - inlined CSS is safe and doesn't introduce XSS risks
Follows React on Rails conventions - uses partials, proper ERB syntax
No performance regressions - minimal inlined CSS is best practice


📊 Code Quality Checklist

  • ✅ Addresses a real user-facing issue
  • ✅ Well-documented with clear examples
  • ✅ Minimal and focused changes
  • ✅ Proper file organization (separate partial)
  • ⚠️ Missing automated test coverage
  • ⚠️ Minor docs/implementation discrepancy

🎬 Recommended Actions Before Merge

  1. Add Playwright E2E test for FOUC prevention (see example above)
  2. Update troubleshooting.md to include spacing utilities in the example
  3. Consider adding a class list comment in _critical_styles.html.erb for easier maintenance
  4. Verify with bundle exec rubocop (should pass based on file structure)

📝 Minor Nitpicks

In troubleshooting.md:

  • Line 12: Table entry is clear and accurate ✅
  • Line 200-279: Excellent comprehensive guide, but could add a note about build tools like critical CSS extraction (optional, not required)

In _critical_styles.html.erb:

  • Consider alphabetizing CSS properties within each section for easier scanning (very minor)

✨ Summary

This is a quality improvement that solves a real UX problem with the Pro dummy app. The implementation is clean, well-documented, and follows best practices.

Biggest gap: Automated test coverage to prevent regression.

Recommendation:Approve with minor suggestions - the missing test isn't blocking, but would be valuable to add.

Great work on the comprehensive troubleshooting documentation! 🎉

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 8, 2026

Code Review: Fix FOUC in Pro spec/dummy app by inlining critical CSS

Overall, this is a well-executed fix that addresses a real UX problem with a proven solution. The implementation is clean, well-documented, and follows best practices.


✅ Strengths

1. Excellent Documentation

  • The _critical_styles.html.erb partial includes comprehensive inline comments explaining:
    • What the file does
    • Why it's needed
    • When to update it
  • The troubleshooting guide provides clear examples and step-by-step instructions
  • Proper escaping of Tailwind arbitrary value syntax (.min-w-\[400px\]) is documented

2. Minimal and Focused

  • The critical CSS only includes layout-essential styles (flex, sizing, overflow, colors)
  • Avoids the temptation to inline too much CSS (~40 lines is appropriate)
  • Properly separates concerns with a dedicated partial for maintainability

3. Performance Optimizations

  • Added preload_pack_asset hint for faster CSS loading (line 24 in application.html.erb)
  • Combined with inline critical CSS for optimal rendering performance
  • Maintains async: true on JavaScript (correct for React 18 Selective Hydration)

4. Comprehensive Troubleshooting Guide

  • Clearly distinguishes between two types of FOUC (SSR auto-loading vs. utility-first CSS)
  • Provides actionable examples with code snippets
  • Includes maintenance guidelines to prevent drift

🔍 Potential Issues & Suggestions

1. Missing Automated Testing
The PR description mentions manual testing with Playwright screenshots, but no automated test was added. Consider:

  • Adding a Playwright visual regression test to prevent FOUC regression
  • Example test structure:
    test('no FOUC on initial load', async ({ page }) => {
      await page.goto('/');
      // Take screenshot immediately on DOMContentLoaded (before CSS loads)
      await page.waitForLoadState('domcontentloaded');
      await expect(page).toHaveScreenshot('before-css-loads.png');
    });

2. CSS Duplication Concerns
The critical styles will be duplicated in the full CSS bundle. While this is acceptable for ~40 lines, consider:

  • Adding a comment in _critical_styles.html.erb acknowledging the intentional duplication
  • The gzip compression will minimize the impact, but worth noting for future readers

3. Maintenance Risk
If someone updates the layout classes in application.html.erb without updating _critical_styles.html.erb, FOUC will return. Consider:

  • Adding a comment at line 40-41 (the layout divs) pointing to the critical styles partial
    <%# If you modify these Tailwind classes, update _critical_styles.html.erb %>
    <div class="flex flex-row h-screen w-screen">

4. Minor Documentation Suggestion
In troubleshooting.md line 222, the partial path shows:

app/views/layouts/_critical_styles.html.erb

But the actual file is in the Pro spec/dummy app:

react_on_rails_pro/spec/dummy/app/views/layouts/_critical_styles.html.erb

Consider clarifying this is an example path for user applications, not the literal path in this repo.


🔒 Security Review

✅ No security concerns identified

  • No user input in critical styles
  • No XSS vectors
  • Proper HTML escaping in ERB templates

⚡ Performance Review

✅ Excellent performance improvements

  • Eliminates layout shift (improved CLS metric)
  • Preload hint reduces CSS load time
  • Minimal inline CSS (~2KB) is negligible
  • No blocking JavaScript

🧪 Test Coverage

⚠️ Action Required: While the PR description mentions manual Playwright testing, no automated test was committed. This is the primary gap.

Recommendation: Add a Playwright E2E test that:

  1. Loads the page with network throttling
  2. Captures a screenshot before CSS loads
  3. Asserts that critical layout elements are visible and positioned correctly

📝 Code Quality

✅ Excellent

  • Clean ERB syntax
  • Proper separation of concerns
  • Clear, descriptive comments
  • Follows Tailwind utility conventions
  • CSS specificity is appropriate

🎯 Final Verdict

Recommendation: Approve with minor suggestions

This PR successfully solves issue #2100 and provides valuable documentation for the community. The implementation is production-ready.

Before merging:

  1. ✅ Run bundle exec rubocop (per CLAUDE.md requirements)
  2. ⚠️ Consider adding automated Playwright test
  3. ⚠️ Add cross-reference comment in application.html.erb to prevent maintenance drift

Nice work! This is exactly the kind of thoughtful, well-documented fix that improves both the codebase and developer experience. 🎉

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 8, 2026

Code Review - PR #2336: Fix FOUC in Pro spec/dummy app by inlining critical CSS

Strengths

1. Excellent Documentation

  • The _critical_styles.html.erb file has exceptional inline documentation explaining the problem, solution, reproduction steps, and maintenance guidelines
  • Clear mapping between semantic selectors and HTML elements
  • Helpful troubleshooting instructions with both manual and automated testing approaches
  • The documentation updates in troubleshooting.md properly distinguish between two types of FOUC (SSR auto-loading vs. utility-first CSS frameworks)

2. Smart Architecture

  • Using semantic selectors (.app-shell, .app-sidebar, .app-main) instead of mirroring Tailwind class names is a best practice that prevents drift when utility classes change
  • The critical CSS is properly scoped with data-critical-styles attribute for easy testing and debugging
  • Clean separation of concerns: critical styles in a dedicated partial

3. Good Test Coverage

  • Added automated test at integration_spec.rb:30-42 that verifies critical styles load before the CSS bundle
  • Uses string position checking to ensure proper ordering in the HTML
  • Clear test expectations with helpful error messages

Issues Found

1. Missing Preload Hint (Major)
The PR description mentions Add preload hint for the CSS bundle to speed up loading but I dont see this implemented in the diff. The PR body states: Add preload hint for the CSS bundle to speed up loading. However, application.html.erb:18-26 shows no preload link tag before the stylesheet.

2. Hardcoded Dimensions
_critical_styles.html.erb:83-84 has min-width: 400px; max-width: 400px; While these match the Tailwind classes min-w-[400px] max-w-[400px], hardcoding pixel values in two places creates a maintenance burden. Consider adding a comment linking these values to the Tailwind classes.

3. Test Could Be More Robust
integration_spec.rb:33-40 uses string position checking which could break if comments are added between the critical styles and stylesheet. Consider also checking that the critical styles actually contain expected selectors like .app-shell and .app-sidebar.

Performance Considerations

Positive:

  • Minimal inline CSS (91 lines, mostly comments) - wont significantly impact HTML size
  • Targets only critical layout properties, not full styling
  • Will improve perceived performance by eliminating layout shift

Potential Optimization:

  • Minify the critical CSS in production (remove whitespace, comments) to reduce HTML payload
  • Consider using PostCSS or similar to auto-generate critical CSS from Tailwind config

Security Considerations

No security concerns found. The inline CSS is static and doesnt include user-generated content.

Test Coverage Assessment

Good:

  • Automated test verifying critical styles load before CSS bundle
  • Manual testing documented in PR description with Playwright screenshots
  • Reproduction steps clearly documented

Could Improve:

  • No E2E test using actual network throttling to verify FOUC is eliminated
  • Consider adding a visual regression test to catch layout changes

Recommendations

Must Fix:

  1. Implement the preload hint mentioned in the PR description, or update the PR description to remove that claim

Should Fix:
2. Add a comment linking the hardcoded 400px values to the Tailwind classes for maintenance
3. Strengthen the test to verify critical selectors are present

Nice to Have:
4. Add minification for production builds
5. Consider E2E test with network throttling to verify FOUC fix

Overall Assessment

This is a solid PR with excellent documentation and a smart architectural approach. The critical CSS solution properly addresses the Tailwind FOUC issue without creating maintenance overhead. The main issue is the missing preload hint mentioned in the PR description.

Recommendation: Request changes to address the missing preload hint, then approve.

@justin808
Copy link
Copy Markdown
Member Author

@greptile, review this PR.
@claude, review this PR.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 8, 2026

Claude finished @justin808's task in 1m 31s —— View job


Code Review: Fix FOUC in Pro spec/dummy app by inlining critical CSS


Summary

This PR successfully addresses the FOUC (Flash of Unstyled Content) issue in the Pro spec/dummy app with a well-executed solution. The implementation uses semantic selectors for critical CSS inlining, comprehensive documentation, and automated tests. Overall, this is high-quality work that follows best practices.

Recommendation: ✅ Approve (with one minor fix for CI)


Strengths

1. Excellent Implementation Architecture

The use of semantic selectors (.app-shell, .app-sidebar, .app-main) instead of mirroring Tailwind class names is a best practice that prevents drift when utility classes change:

<div class="app-shell flex flex-row h-screen w-screen">
  <div class="app-sidebar flex flex-col ...">
/* Critical CSS targets stable semantic selectors */
.app-shell { display: flex; flex-direction: row; ... }
.app-sidebar { display: flex; flex-direction: column; ... }

Why this matters: When you add/remove Tailwind utilities in the HTML, you don't need to update the critical CSS. You only update it when the fundamental layout structure changes.

2. Outstanding Documentation 📚

The _critical_styles.html.erb file contains exceptional inline documentation:

  • Clear problem statement and solution explanation
  • Step-by-step reproduction instructions (manual + Playwright)
  • Maintenance guidelines with semantic selector mappings
  • 91 lines total, mostly comments—makes future maintenance easy

The troubleshooting.md additions:

  • Properly distinguishes between two types of FOUC (SSR auto-loading vs. utility-first CSS)
  • Provides actionable step-by-step solutions with code examples
  • Cross-references to the real-world implementation

3. Comprehensive Test Coverage

Added automated tests in integration_spec.rb (lines 30-54):

  • Verifies critical styles render in <head> before <body>
  • Checks ordering: critical styles appear before CSS bundle
  • Uses data-critical-styles attribute for reliable detection
  • Graceful skip if CSS is inlined via style-loader

4. Minimal and Focused CSS

The critical CSS includes only layout-critical properties:

  • Layout structure: display: flex, flex-direction, height, width
  • Overflow handling: overflow-y: auto, overflow-x: hidden
  • Visible defaults: background colors, borders, padding

Total size: ~40 lines of actual CSS (excluding comments) — won't significantly impact HTML payload.


Issues Found

🔴 CRITICAL: Broken Documentation Links (CI Failure)

Location: docs/deployment/troubleshooting.md (lines 280-284 in the diff)

Problem: The PR uses absolute GitHub URLs pointing to the master branch:

- [`_critical_styles.html.erb`](https://github.com/shakacode/react_on_rails/blob/master/react_on_rails_pro/spec/dummy/app/views/layouts/_critical_styles.html.erb)
- [`application.html.erb`](https://github.com/shakacode/react_on_rails/blob/master/react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb)

These files don't exist on master yet—they're being added in this PR. CI link checks fail because they validate against the current branch state.

Solution: Use relative paths instead:

- [`_critical_styles.html.erb`](../../react_on_rails_pro/spec/dummy/app/views/layouts/_critical_styles.html.erb)
- [`application.html.erb`](../../react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb)

Note: CodeRabbit already flagged this issue in their review. This is the only blocking issue preventing merge.

Fix this issue →


Minor Observations (Non-Blocking)

1. Missing Preload Hint?

The PR description mentions:

Add preload hint for the CSS bundle to speed up loading

However, I don't see a <link rel="preload"> tag in application.html.erb. The current code shows:

<%= render "layouts/critical_styles" %>
<style>
    html { font-size: 16px !important; }
</style>
<%= stylesheet_pack_tag('client-bundle', ...) %>

Expected (if preload was added):

<%= preload_pack_asset('client-bundle.css', as: 'style') %>
<%= render "layouts/critical_styles" %>

Not blocking: The critical CSS inlining alone is sufficient to prevent FOUC. Preload would be an additional optimization, but it's not required. If this was included in an earlier iteration and removed, consider updating the PR description.

2. Test Could Be More Robust

The test uses string position checking:

critical_pos = html.index("data-critical-styles")
stylesheet_pos = html.index("client-bundle.css")
expect(critical_pos).to be < stylesheet_pos

Enhancement suggestion (future PR): Also verify that critical styles contain expected selectors:

expect(html).to include(".app-shell")
expect(html).to include(".app-sidebar")

Rationale: If someone accidentally empties the critical styles partial, the current test would still pass (empty <style> tag exists).

3. Hardcoded Pixel Values

_critical_styles.html.erb lines 83-84:

min-width: 400px;
max-width: 400px;

These match the Tailwind classes min-w-[400px] and max-w-[400px]. Hardcoding in two places creates maintenance burden.

Suggestion: Add a comment linking them:

/* Matches Tailwind min-w-[400px] max-w-[400px] in application.html.erb */
min-width: 400px;
max-width: 400px;

This helps future maintainers understand the relationship.


Security & Performance

No security concerns — inline CSS is static, no XSS vectors
Performance improvement — eliminates layout shift, improves CLS metric
Minimal overhead — ~2KB of inline CSS is negligible trade-off


Recommendations

Before Merge (Required)

  1. Fix broken documentation links (see issue above) — this is causing CI to fail

Before Merge (Nice to Have)

  1. ⚠️ Clarify preload hint — either implement it or update the PR description to remove the mention
  2. ⚠️ Add maintenance comment — link hardcoded 400px values to their Tailwind classes

Future Enhancements (Optional)

  1. Consider automated critical CSS extraction tools (critical, critters)
  2. Apply this pattern to the open-source dummy app if it adopts Tailwind
  3. Strengthen test to verify critical selectors are present

Final Verdict

✅ APPROVE with one required fix (documentation links).

This is excellent work that:

  • Solves a real UX problem with a proven solution
  • Provides educational value through comprehensive documentation
  • Includes automated tests to prevent regression
  • Uses maintainable architecture (semantic selectors)

Once the CI link check is resolved, this is ready to merge! 🚀


Real-world example links: See the implementation files:


Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +39 to +41
body_pos = html.index("<body")
expect(body_pos).not_to be_nil, "Expected <body> tag in the HTML"
expect(critical_pos).to be < body_pos,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nil comparison in test
If client-bundle.css isn’t present, stylesheet_pos is nil and the test skips, but critical_pos can also be nil (e.g., regression where the partial stops rendering). In that case expect(critical_pos).to be < stylesheet_pos compares nil < Integer / nil < nil and will raise before the skip logic is reached in other branches. Consider asserting critical_pos is present (like the first spec) before any ordering comparisons, and use skip only for the stylesheet position.

Also appears in: react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb:48-56.

justin808 and others added 8 commits February 8, 2026 00:31
Extract critical Tailwind CSS to a separate partial for better
maintainability. The partial includes documentation explaining
which styles are critical and why they're needed.

Changes:
- Add _critical_styles.html.erb partial with layout-critical CSS
- Render partial in layout before main stylesheet
- Add preload hint for client-bundle.css

The partial approach is cleaner than inline CSS in the layout:
- Styles are isolated in one documented file
- Easy to update when layout changes
- Keeps the main layout file clean

Fixes #2100

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Document the critical CSS inlining pattern for preventing Flash of
Unstyled Content when using Tailwind CSS or other utility-first
frameworks.

Changes:
- Add Type 2 FOUC section for Tailwind/utility-first CSS frameworks
- Include step-by-step guide for creating critical styles partial
- Add guidelines for maintaining critical CSS
- Link to real-world example in Pro spec/dummy app
- Add cross-reference from auto-bundling docs

Related to #2100

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The troubleshooting docs referenced _critical_styles.html.erb and
application.html.erb files on master, but these files are being
added by this PR. The link checker fails because the files don't
exist on master until after merge.

Removed the "Real-world example" links since the inline code example
already provides the complete implementation details.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The documentation example for critical CSS was missing the border-color
class that's present in the actual implementation. This ensures the
docs example is complete and consistent with _critical_styles.html.erb.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The documentation example was missing .p-5 and .px-2 spacing utilities
that are present in the actual _critical_styles.html.erb implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expand the _critical_styles partial comment with:
- Explanation of why the bug happens
- How the inline CSS fix works
- Step-by-step reproduction instructions (both manual and Playwright)
- Maintenance guide mapping CSS rules to layout elements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace Tailwind utility class mirroring with stable semantic selectors
  (.app-body, .app-shell, .app-sidebar, .app-main, etc.) so the critical
  CSS partial doesn't drift when utility classes change
- Remove redundant preload_pack_asset since stylesheet_pack_tag already
  creates a blocking <link> in <head>
- Add data-critical-styles attribute to the <style> tag for testability
- Add regression spec asserting critical styles appear before the
  stylesheet bundle in the HTML
- Update FOUC prevention docs to recommend the semantic selector pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split the FOUC test into two: one that always verifies critical styles
exist in <head>, and another that checks ordering with the external
stylesheet bundle but gracefully skips when CSS is inlined via
webpack's style-loader (stylesheet_pack_tag returns empty).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

/run-skipped-ci

@justin808
Copy link
Copy Markdown
Member Author

/run-skipped-tests

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 8, 2026

🚀 Full CI Mode Enabled

Triggered all workflows for full CI coverage

Note: Added the full-ci label to this PR. All future commits will run the full CI suite (including minimum dependency tests) until the label is removed.

To disable full CI mode, use the /stop-run-skipped-ci command.

View progress in the Actions tab.

Address review feedback: the ordering test now asserts critical_pos
is not nil before comparing positions, preventing a nil comparison
error if the critical styles partial regresses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Feb 8, 2026

PR Review: Fix FOUC in Pro spec/dummy app by inlining critical CSS

Summary

This PR effectively addresses Flash of Unstyled Content (FOUC) in the Pro dummy app by implementing a critical CSS inlining strategy. The approach is well-designed and the documentation is excellent.

Strengths

1. Excellent Documentation

  • The _critical_styles.html.erb file has exemplary inline documentation explaining the problem, solution, reproduction steps, and maintenance guidelines
  • The troubleshooting docs clearly differentiate between two types of FOUC (SSR with auto_load_bundle vs. Tailwind/utility-first frameworks)
  • Step-by-step guidance makes it easy for developers to apply this pattern to their own apps

2. Smart Architectural Decision

  • Using semantic class names (.app-shell, .app-sidebar, .app-main) alongside Tailwind utilities is a best practice
  • This decouples the critical CSS from the Tailwind utility classes, making maintenance easier
  • The critical CSS won't need updates when Tailwind classes are added/removed, only when the fundamental layout structure changes

3. Minimal and Focused

  • The critical CSS includes only layout-affecting properties (display, flex, sizing, overflow)
  • Avoids over-engineering by not inlining component-level styles
  • The data-critical-styles attribute makes testing straightforward

4. Good Test Coverage

  • Two test cases verify the critical styles are present and positioned correctly
  • The second test gracefully skips if CSS is inlined via style-loader (good defensive programming)
  • Tests use semantic assertions

Areas for Consideration

1. Hard-coded Color Values

Location: react_on_rails_pro/spec/dummy/app/views/layouts/_critical_styles.html.erb:79-82

Observation: Colors are hard-coded hex values (#f1f5f9, #334155) rather than referencing Tailwind's color system.

Impact: If the Tailwind theme changes (e.g., customizing the slate color palette), the critical CSS won't match, causing a brief visual flash when the full CSS loads.

Recommendation: Document that these hex values correspond to Tailwind's default slate colors and add a maintenance note that they must be updated manually if the Tailwind theme is customized.

2. Missing Preload Hint

Observation: The PR description mentions "Add preload hint for the CSS bundle to speed up loading" but I don't see this change in the diff.

Question: Was this intentionally removed from the PR scope, or was it omitted from the diff?

3. Test Robustness

Location: react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb:51-52

The test skips if the stylesheet isn't found, but doesn't verify that critical styles actually prevent FOUC in the style-loader case. Consider adding a comment explaining that when style-loader inlines CSS, FOUC prevention is handled differently.

Security & Performance

Security: No concerns

  • Inline styles are static and don't include user-generated content
  • No XSS risk
  • No external resource loading

Performance: Good

  • Critical CSS is minimal (~0.5KB)
  • Eliminates render-blocking layout shift
  • Semantic selectors have good specificity without being overly specific

Code Quality

Strengths:

  • ERB comments are thorough and helpful
  • Code follows Rails conventions
  • Semantic HTML class naming is excellent
  • Critical CSS properties match Tailwind's output exactly

Final Recommendation: APPROVE with minor suggestions

This PR is well-executed and ready to merge. The suggestions above are optional improvements, not blockers.

Merge Checklist:

  • Code quality is high
  • Documentation is excellent
  • Tests cover the critical functionality
  • No security concerns
  • Performance impact is positive

Great work on this FOUC fix! The semantic selector pattern is a maintainable solution that other developers can easily follow.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 8, 2026

🐰 Bencher Report

Branchjg/fix-issue-2100
Testbedgithub-actions
Click to view all benchmark results
Benchmarkfailed_pct%p50_latencymsp90_latencymsp99_latencymsrpsreq/s
/: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
3,293.20📈 view plot
🚷 view threshold
3,793.46📈 view plot
🚷 view threshold
8,431.33📈 view plot
🚷 view threshold
2.65
/: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
27.35📈 view plot
🚷 view threshold
46.07📈 view plot
🚷 view threshold
443.31📈 view plot
🚷 view threshold
233.93
/apollo_graphql: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
44.82📈 view plot
🚷 view threshold
71.11📈 view plot
🚷 view threshold
610.45📈 view plot
🚷 view threshold
145.37
/async_components_demo: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
37.83📈 view plot
🚷 view threshold
51.84📈 view plot
🚷 view threshold
207.06📈 view plot
🚷 view threshold
231.84
/async_on_server_sync_on_client: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
19.58📈 view plot
🚷 view threshold
52.82📈 view plot
🚷 view threshold
213.94📈 view plot
🚷 view threshold
294.34
/async_on_server_sync_on_client_client_render: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
16.37📈 view plot
🚷 view threshold
26.55📈 view plot
🚷 view threshold
184.04📈 view plot
🚷 view threshold
454.74
/async_render_function_returns_component: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
19.62📈 view plot
🚷 view threshold
31.82📈 view plot
🚷 view threshold
221.79📈 view plot
🚷 view threshold
393.79
/async_render_function_returns_string: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
21.35📈 view plot
🚷 view threshold
30.07📈 view plot
🚷 view threshold
123.93📈 view plot
🚷 view threshold
409.11
/broken_app: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
337.03📈 view plot
🚷 view threshold
562.84📈 view plot
🚷 view threshold
3,831.56📈 view plot
🚷 view threshold
17.66
/broken_app: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
17.31📈 view plot
🚷 view threshold
26.39📈 view plot
🚷 view threshold
195.14📈 view plot
🚷 view threshold
440.91
/cached_react_helmet: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
13.57📈 view plot
🚷 view threshold
22.57📈 view plot
🚷 view threshold
217.74📈 view plot
🚷 view threshold
467.09
/cached_redux_component: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
14.07📈 view plot
🚷 view threshold
23.26📈 view plot
🚷 view threshold
227.20📈 view plot
🚷 view threshold
451.89
/client_side_hello_world: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
8.83📈 view plot
🚷 view threshold
17.15📈 view plot
🚷 view threshold
105.73📈 view plot
🚷 view threshold
762.45
/client_side_hello_world: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
17.58📈 view plot
🚷 view threshold
26.57📈 view plot
🚷 view threshold
187.08📈 view plot
🚷 view threshold
433.37
/client_side_hello_world_shared_store: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
11.26📈 view plot
🚷 view threshold
18.52📈 view plot
🚷 view threshold
67.07📈 view plot
🚷 view threshold
735.62
/client_side_hello_world_shared_store: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
19.02📈 view plot
🚷 view threshold
28.48📈 view plot
🚷 view threshold
206.64📈 view plot
🚷 view threshold
402.33
/client_side_hello_world_shared_store_controller: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
8.74📈 view plot
🚷 view threshold
21.49📈 view plot
🚷 view threshold
85.68📈 view plot
🚷 view threshold
736.76
/client_side_hello_world_shared_store_controller: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
20.20📈 view plot
🚷 view threshold
28.89📈 view plot
🚷 view threshold
119.18📈 view plot
🚷 view threshold
428.76
/client_side_hello_world_shared_store_defer: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
9.32📈 view plot
🚷 view threshold
18.40📈 view plot
🚷 view threshold
108.64📈 view plot
🚷 view threshold
719.26
/client_side_hello_world_shared_store_defer: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
17.64📈 view plot
🚷 view threshold
27.93📈 view plot
🚷 view threshold
193.52📈 view plot
🚷 view threshold
443.79
/client_side_log_throw: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
8.96📈 view plot
🚷 view threshold
17.28📈 view plot
🚷 view threshold
104.76📈 view plot
🚷 view threshold
769.90
/client_side_log_throw: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
16.61📈 view plot
🚷 view threshold
27.57📈 view plot
🚷 view threshold
152.46📈 view plot
🚷 view threshold
457.50
/client_side_manual_render: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
8.24📈 view plot
🚷 view threshold
19.93📈 view plot
🚷 view threshold
82.43📈 view plot
🚷 view threshold
790.63
/client_side_manual_render: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
17.11📈 view plot
🚷 view threshold
27.65📈 view plot
🚷 view threshold
134.02📈 view plot
🚷 view threshold
449.92
/client_side_rescript_hello_world: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
8.83📈 view plot
🚷 view threshold
17.34📈 view plot
🚷 view threshold
102.12📈 view plot
🚷 view threshold
761.65
/console_logs_in_async_server: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
2,118.76📈 view plot
🚷 view threshold
2,143.51📈 view plot
🚷 view threshold
21,192.07📈 view plot
🚷 view threshold
3.57
/context_function_return_jsx: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
8.48📈 view plot
🚷 view threshold
14.18📈 view plot
🚷 view threshold
137.72📈 view plot
🚷 view threshold
726.98
/context_function_return_jsx: Pro📈 view plot
🚷 view threshold
100.00📈 view plot
🚷 view threshold
15.15📈 view plot
🚷 view threshold
39.00📈 view plot
🚷 view threshold
170.56📈 view plot
🚷 view threshold
383.17
/css_modules_images_fonts_example: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
349.74📈 view plot
🚷 view threshold
431.50📈 view plot
🚷 view threshold
3,914.78📈 view plot
🚷 view threshold
23.50
/css_modules_images_fonts_example: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
20.91📈 view plot
🚷 view threshold
29.88📈 view plot
🚷 view threshold
117.85📈 view plot
🚷 view threshold
414.02
/empty: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
6.22📈 view plot
🚷 view threshold
8.06📈 view plot
🚷 view threshold
33.19📈 view plot
🚷 view threshold
1,433.01
/image_example: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
394.51📈 view plot
🚷 view threshold
447.69📈 view plot
🚷 view threshold
3,439.76📈 view plot
🚷 view threshold
23.05
/image_example: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
17.90📈 view plot
🚷 view threshold
28.75📈 view plot
🚷 view threshold
149.17📈 view plot
🚷 view threshold
433.19
/lazy_apollo_graphql: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
53.26📈 view plot
🚷 view threshold
81.97📈 view plot
🚷 view threshold
312.04📈 view plot
🚷 view threshold
160.26
/loadable: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
19.20📈 view plot
🚷 view threshold
30.99📈 view plot
🚷 view threshold
226.09📈 view plot
🚷 view threshold
404.12
/manual_render_test: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
7.88📈 view plot
🚷 view threshold
13.35📈 view plot
🚷 view threshold
141.28📈 view plot
🚷 view threshold
764.08
/posts_page: Pro📈 view plot
🚷 view threshold
100.00📈 view plot
🚷 view threshold
8.27📈 view plot
🚷 view threshold
12.70📈 view plot
🚷 view threshold
92.25📈 view plot
🚷 view threshold
962.76
/pure_component: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
347.22📈 view plot
🚷 view threshold
433.96📈 view plot
🚷 view threshold
3,832.90📈 view plot
🚷 view threshold
23.79
/pure_component: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
19.78📈 view plot
🚷 view threshold
27.79📈 view plot
🚷 view threshold
112.18📈 view plot
🚷 view threshold
442.66
/pure_component_wrapped_in_function: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
10.58📈 view plot
🚷 view threshold
17.41📈 view plot
🚷 view threshold
62.94📈 view plot
🚷 view threshold
778.85
/pure_component_wrapped_in_function: Pro📈 view plot
🚷 view threshold
100.00📈 view plot
🚷 view threshold
17.77📈 view plot
🚷 view threshold
25.60📈 view plot
🚷 view threshold
97.01📈 view plot
🚷 view threshold
489.12
/react_helmet: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
393.11📈 view plot
🚷 view threshold
442.89📈 view plot
🚷 view threshold
2,929.05📈 view plot
🚷 view threshold
23.24
/react_helmet: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
14.74📈 view plot
🚷 view threshold
20.86📈 view plot
🚷 view threshold
373.35📈 view plot
🚷 view threshold
316.88
/react_helmet_broken: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
294.85📈 view plot
🚷 view threshold
435.89📈 view plot
🚷 view threshold
4,324.71📈 view plot
🚷 view threshold
22.73
/react_helmet_broken: Pro📈 view plot
🚷 view threshold
100.00📈 view plot
🚷 view threshold
17.30📈 view plot
🚷 view threshold
25.65📈 view plot
🚷 view threshold
184.73📈 view plot
🚷 view threshold
443.38
/react_router: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
361.94📈 view plot
🚷 view threshold
455.61📈 view plot
🚷 view threshold
4,280.87📈 view plot
🚷 view threshold
22.37
/react_router: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
14.56📈 view plot
🚷 view threshold
26.77📈 view plot
🚷 view threshold
224.16📈 view plot
🚷 view threshold
465.62
/redis_receiver: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
78.69📈 view plot
🚷 view threshold
143.19📈 view plot
🚷 view threshold
526.97📈 view plot
🚷 view threshold
103.59
/render_js: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
368.40📈 view plot
🚷 view threshold
414.83📈 view plot
🚷 view threshold
2,672.62📈 view plot
🚷 view threshold
24.79
/render_js: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
17.54📈 view plot
🚷 view threshold
27.48📈 view plot
🚷 view threshold
248.45📈 view plot
🚷 view threshold
430.95
/rendered_html: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
384.99📈 view plot
🚷 view threshold
432.83📈 view plot
🚷 view threshold
2,802.96📈 view plot
🚷 view threshold
23.63
/rendered_html: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
17.86📈 view plot
🚷 view threshold
40.21📈 view plot
🚷 view threshold
204.34📈 view plot
🚷 view threshold
324.39
/rsc_posts_page_over_http: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
19.24📈 view plot
🚷 view threshold
22.97📈 view plot
🚷 view threshold
358.87📈 view plot
🚷 view threshold
297.71
/rsc_posts_page_over_redis: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
58.83📈 view plot
🚷 view threshold
90.48📈 view plot
🚷 view threshold
766.21📈 view plot
🚷 view threshold
116.25
/server_render_with_timeout: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
16.82📈 view plot
🚷 view threshold
26.09📈 view plot
🚷 view threshold
184.82📈 view plot
🚷 view threshold
446.39
/server_router: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
19.17📈 view plot
🚷 view threshold
31.14📈 view plot
🚷 view threshold
266.62📈 view plot
🚷 view threshold
386.75
/server_router_client_render: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
13.67📈 view plot
🚷 view threshold
22.83📈 view plot
🚷 view threshold
224.83📈 view plot
🚷 view threshold
451.92
/server_side_hello_world: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
241.52📈 view plot
🚷 view threshold
425.12📈 view plot
🚷 view threshold
4,586.76📈 view plot
🚷 view threshold
23.62
/server_side_hello_world: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
20.57📈 view plot
🚷 view threshold
28.94📈 view plot
🚷 view threshold
113.11📈 view plot
🚷 view threshold
424.36
/server_side_hello_world_es5: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
340.12📈 view plot
🚷 view threshold
427.52📈 view plot
🚷 view threshold
4,256.00📈 view plot
🚷 view threshold
23.68
/server_side_hello_world_es5: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
17.51📈 view plot
🚷 view threshold
27.31📈 view plot
🚷 view threshold
188.62📈 view plot
🚷 view threshold
428.83
🐰 View full continuous benchmarking report in Bencher

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 8, 2026

🐰 Bencher Report

Branchjg/fix-issue-2100
Testbedgithub-actions
Click to view all benchmark results
Benchmarkfailed_pct%p50_latencymsp90_latencymsp99_latencymsrpsreq/s
/server_side_hello_world_hooks: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
338.53📈 view plot
🚷 view threshold
449.09📈 view plot
🚷 view threshold
3,807.52📈 view plot
🚷 view threshold
17.68
/server_side_hello_world_hooks: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
17.31📈 view plot
🚷 view threshold
28.59📈 view plot
🚷 view threshold
143.87📈 view plot
🚷 view threshold
441.23
/server_side_hello_world_props: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
345.99📈 view plot
🚷 view threshold
434.50📈 view plot
🚷 view threshold
4,012.27📈 view plot
🚷 view threshold
23.65
/server_side_hello_world_shared_store: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
709.58📈 view plot
🚷 view threshold
956.10📈 view plot
🚷 view threshold
7,779.48📈 view plot
🚷 view threshold
11.42
/server_side_hello_world_shared_store: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
23.64📈 view plot
🚷 view threshold
32.49📈 view plot
🚷 view threshold
137.23📈 view plot
🚷 view threshold
373.44
/server_side_hello_world_shared_store_controller: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
550.48📈 view plot
🚷 view threshold
895.45📈 view plot
🚷 view threshold
8,862.38📈 view plot
🚷 view threshold
11.43
/server_side_hello_world_shared_store_controller: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
20.61📈 view plot
🚷 view threshold
30.76📈 view plot
🚷 view threshold
222.52📈 view plot
🚷 view threshold
378.31
/server_side_hello_world_shared_store_defer: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
685.68📈 view plot
🚷 view threshold
991.34📈 view plot
🚷 view threshold
7,255.70📈 view plot
🚷 view threshold
11.56
/server_side_hello_world_shared_store_defer: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
20.70📈 view plot
🚷 view threshold
32.99📈 view plot
🚷 view threshold
245.82📈 view plot
🚷 view threshold
378.62
/server_side_hello_world_with_options: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
350.39📈 view plot
🚷 view threshold
429.97📈 view plot
🚷 view threshold
4,022.45📈 view plot
🚷 view threshold
23.69
/server_side_hello_world_with_options: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
20.30📈 view plot
🚷 view threshold
34.95📈 view plot
🚷 view threshold
164.08📈 view plot
🚷 view threshold
371.71
/server_side_log_throw: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
351.97📈 view plot
🚷 view threshold
446.13📈 view plot
🚷 view threshold
3,826.96📈 view plot
🚷 view threshold
23.06
/server_side_log_throw: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
19.05📈 view plot
🚷 view threshold
28.86📈 view plot
🚷 view threshold
210.84📈 view plot
🚷 view threshold
401.72
/server_side_log_throw_plain_js: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
352.75📈 view plot
🚷 view threshold
441.58📈 view plot
🚷 view threshold
3,980.53📈 view plot
🚷 view threshold
23.16
/server_side_log_throw_plain_js: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
20.80📈 view plot
🚷 view threshold
28.93📈 view plot
🚷 view threshold
116.40📈 view plot
🚷 view threshold
421.12
/server_side_log_throw_raise: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
391.67📈 view plot
🚷 view threshold
441.62📈 view plot
🚷 view threshold
2,809.64📈 view plot
🚷 view threshold
23.29
/server_side_log_throw_raise: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
11.66📈 view plot
🚷 view threshold
15.88📈 view plot
🚷 view threshold
64.51📈 view plot
🚷 view threshold
746.29
/server_side_log_throw_raise_invoker: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
8.03📈 view plot
🚷 view threshold
16.86📈 view plot
🚷 view threshold
70.75📈 view plot
🚷 view threshold
863.96
/server_side_log_throw_raise_invoker: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
15.89📈 view plot
🚷 view threshold
25.15📈 view plot
🚷 view threshold
175.17📈 view plot
🚷 view threshold
492.00
/server_side_redux_app: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
362.23📈 view plot
🚷 view threshold
447.12📈 view plot
🚷 view threshold
4,210.24📈 view plot
🚷 view threshold
22.68
/server_side_redux_app: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
22.14📈 view plot
🚷 view threshold
31.02📈 view plot
🚷 view threshold
127.14📈 view plot
🚷 view threshold
395.93
/server_side_redux_app_cached: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
7.37📈 view plot
🚷 view threshold
15.14📈 view plot
🚷 view threshold
190.94📈 view plot
🚷 view threshold
715.15
/server_side_redux_app_cached: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
21.62📈 view plot
🚷 view threshold
30.33📈 view plot
🚷 view threshold
119.99📈 view plot
🚷 view threshold
402.64
/stream_async_components: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
19.25📈 view plot
🚷 view threshold
32.15📈 view plot
🚷 view threshold
151.24📈 view plot
🚷 view threshold
402.54
/turbo_frame_tag_hello_world: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
8.83📈 view plot
🚷 view threshold
17.03📈 view plot
🚷 view threshold
100.66📈 view plot
🚷 view threshold
768.49
/turbolinks_cache_disabled: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
10.66📈 view plot
🚷 view threshold
18.07📈 view plot
🚷 view threshold
65.98📈 view plot
🚷 view threshold
761.13
/turbolinks_cache_disabled: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
17.83📈 view plot
🚷 view threshold
29.88📈 view plot
🚷 view threshold
159.87📈 view plot
🚷 view threshold
423.07
/xhr_refresh: Core📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
751.40📈 view plot
🚷 view threshold
955.64📈 view plot
🚷 view threshold
3,268.00📈 view plot
🚷 view threshold
12.02
/xhr_refresh: Pro📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
20.31📈 view plot
🚷 view threshold
30.60📈 view plot
🚷 view threshold
279.35📈 view plot
🚷 view threshold
382.49
Pro Node Renderer: react_ssr (non-RSC)📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
5.08📈 view plot
🚷 view threshold
14.19📈 view plot
🚷 view threshold
19.33📈 view plot
🚷 view threshold
1,301.66
Pro Node Renderer: simple_eval (non-RSC)📈 view plot
🚷 view threshold
0.00📈 view plot
🚷 view threshold
4.93📈 view plot
🚷 view threshold
14.71📈 view plot
🚷 view threshold
20.77📈 view plot
🚷 view threshold
1,226.39
🐰 View full continuous benchmarking report in Bencher

@justin808 justin808 merged commit 546e985 into master Feb 8, 2026
18 checks passed
@justin808 justin808 deleted the jg/fix-issue-2100 branch February 8, 2026 21:50
justin808 added a commit that referenced this pull request Feb 8, 2026
## Summary
- Inline critical Tailwind CSS classes used in the layout structure to
prevent Flash of Unstyled Content (FOUC)
- Add preload hint for the CSS bundle to speed up loading

## Problem
The Pro `spec/dummy` app showed unstyled HTML on initial page load
before the CSS bundle finished loading. This created a poor first
impression when running `bin/dev`.

## Solution
Extract critical CSS to a separate `_critical_styles.html.erb` partial
for better maintainability. The partial includes:
- Layout structure (flex containers, sizing)
- Overflow handling
- Spacing
- Background colors
- Borders

## Verified Fix
Tested locally with Playwright using network throttling (20 Kbps):

**Without fix:** Single-column layout, no sidebar, no colors - classic
FOUC
**With fix:** Proper two-column layout, gray sidebar, correct structure
even during slow CSS load

Fixes #2100

## Test plan
- [x] Verified FOUC exists without the fix (Playwright screenshot during
throttled load)
- [x] Verified fix eliminates FOUC (layout renders correctly before CSS
loads)
- [ ] Manual testing with `bin/dev` and Slow 3G throttling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Expanded troubleshooting to distinguish FOUC subtypes, added “layout
jumps,” and added detailed guidance, examples, and maintenance tips for
utility‑first CSS frameworks recommending inlining critical CSS.

* **New Features**
* Added an inline critical‑CSS pattern and integration guidance plus
layout adjustments to render critical styles in the head to reduce FOUC
and initial layout shifts.

* **Tests**
* Added system tests verifying critical styles are inlined before the
main stylesheet to prevent FOUC.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pro spec/dummy app has severe FOUC (Flash of Unstyled Content)

1 participant