Fix FOUC in Pro spec/dummy app by inlining critical CSS#2336
Fix FOUC in Pro spec/dummy app by inlining critical CSS#2336
Conversation
WalkthroughAdds 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Code Review: Fix FOUC in Pro spec/dummy app✅ SummaryThis 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
🔍 Code Quality ObservationsPositive:
Minor Suggestions:
🧪 Testing RecommendationsBefore merging, please verify:
🚨 Potential IssuesNone identified. This is a low-risk change with clear benefits. 📊 Performance ImpactExpected improvements:
🔒 Security ConsiderationsNo security concerns. The inlined styles are static CSS, no dynamic content or user input. 📝 Suggestions for Future Enhancement
✅ RecommendationAPPROVE - This PR is ready to merge after manual testing confirms the FOUC is resolved. The solution is:
Next Steps:
📚 References |
Greptile OverviewGreptile SummaryThis PR addresses FOUC in the Pro dummy app by adding a 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
Important Files Changed
Sequence DiagramsequenceDiagram
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)
|
26c6ac6 to
bd94ab9
Compare
Code Review - PR #2336✅ Overall AssessmentThis is a well-implemented solution to the FOUC issue. The approach is sound, well-documented, and follows best practices for critical CSS inlining. 🎯 Strengths
💡 Suggestions for Improvement1. Consider Using
|
3abe80f to
952508e
Compare
Code Review - PR #2336: Fix FOUC in Pro spec/dummy app✅ Overall AssessmentThis 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. 🎯 Strengths1. Excellent Documentation ⭐
2. Clean Implementation
3. Performance Optimization
4. Cross-linking
|
There was a problem hiding this comment.
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.
| **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) | ||
|
|
There was a problem hiding this comment.
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.
Code Review - PR #2336: Fix FOUC in Pro spec/dummy app✅ Overall AssessmentThis is a well-executed fix for a real UX problem. The solution is clean, well-documented, and follows best practices for critical CSS inlining. 🎯 Strengths1. Excellent Documentation
2. Maintainable Implementation
3. Performance Optimization
4. Cross-Linking Documentation
🔍 Potential Issues & Suggestions1. Missing Test Coverage
|
Code Review - PR #2336: Fix FOUC in Pro spec/dummy app✅ Overall AssessmentThis 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. 🎯 Strengths1. Excellent Problem-Solution Documentation
2. Maintainable Implementation
3. Performance Optimization
4. Correct Technical Approach
🔍 Areas for Improvement1. Missing Test Coverage
|
Code Review: Fix FOUC in Pro spec/dummy app by inlining critical CSSOverall, 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. ✅ Strengths1. Excellent Documentation
2. Minimal and Focused
3. Performance Optimizations
4. Comprehensive Troubleshooting Guide
🔍 Potential Issues & Suggestions1. Missing Automated Testing
2. CSS Duplication Concerns
3. Maintenance Risk
4. Minor Documentation Suggestion app/views/layouts/_critical_styles.html.erbBut the actual file is in the Pro spec/dummy app: Consider clarifying this is an example path for user applications, not the literal path in this repo. 🔒 Security Review✅ No security concerns identified
⚡ Performance Review✅ Excellent performance improvements
🧪 Test CoverageRecommendation: Add a Playwright E2E test that:
📝 Code Quality✅ Excellent
🎯 Final VerdictRecommendation: Approve with minor suggestions This PR successfully solves issue #2100 and provides valuable documentation for the community. The implementation is production-ready. Before merging:
Nice work! This is exactly the kind of thoughtful, well-documented fix that improves both the codebase and developer experience. 🎉 |
Code Review - PR #2336: Fix FOUC in Pro spec/dummy app by inlining critical CSSStrengths1. Excellent Documentation
2. Smart Architecture
3. Good Test Coverage
Issues Found1. Missing Preload Hint (Major) 2. Hardcoded Dimensions 3. Test Could Be More Robust Performance ConsiderationsPositive:
Potential Optimization:
Security ConsiderationsNo security concerns found. The inline CSS is static and doesnt include user-generated content. Test Coverage AssessmentGood:
Could Improve:
RecommendationsMust Fix:
Should Fix: Nice to Have: Overall AssessmentThis 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. |
|
@greptile, review this PR. |
|
Claude finished @justin808's task in 1m 31s —— View job Code Review: Fix FOUC in Pro spec/dummy app by inlining critical CSSSummaryThis 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) Strengths1. Excellent Implementation Architecture ⭐The use of semantic selectors ( <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
The
3. Comprehensive Test Coverage ✅Added automated tests in
4. Minimal and Focused CSSThe critical CSS includes only layout-critical properties:
Total size: ~40 lines of actual CSS (excluding comments) — won't significantly impact HTML payload. Issues Found🔴 CRITICAL: Broken Documentation Links (CI Failure)Location: Problem: The PR uses absolute GitHub URLs pointing to the - [`_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 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. Minor Observations (Non-Blocking)1. Missing Preload Hint?The PR description mentions:
However, I don't see a <%= 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 RobustThe 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_posEnhancement 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 3. Hardcoded Pixel Values
min-width: 400px;
max-width: 400px;These match the Tailwind classes 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 RecommendationsBefore Merge (Required)
Before Merge (Nice to Have)
Future Enhancements (Optional)
Final Verdict✅ APPROVE with one required fix (documentation links). This is excellent work that:
Once the CI link check is resolved, this is ready to merge! 🚀 Real-world example links: See the implementation files: |
| 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, |
There was a problem hiding this comment.
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.
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>
9706a98 to
62b0b60
Compare
|
/run-skipped-ci |
|
/run-skipped-tests |
|
🚀 Full CI Mode Enabled ✅ Triggered all workflows for full CI coverage Note: Added the To disable full CI mode, use the 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>
PR Review: Fix FOUC in Pro spec/dummy app by inlining critical CSSSummaryThis 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. Strengths1. Excellent Documentation
2. Smart Architectural Decision
3. Minimal and Focused
4. Good Test Coverage
Areas for Consideration1. Hard-coded Color ValuesLocation: 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 HintObservation: 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 RobustnessLocation: 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 & PerformanceSecurity: No concerns
Performance: Good
Code QualityStrengths:
Final Recommendation: APPROVE with minor suggestionsThis PR is well-executed and ready to merge. The suggestions above are optional improvements, not blockers. Merge Checklist:
Great work on this FOUC fix! The semantic selector pattern is a maintainable solution that other developers can easily follow. |
|
| Branch | jg/fix-issue-2100 |
| Testbed | github-actions |
Click to view all benchmark results
| Benchmark | failed_pct | % | p50_latency | ms | p90_latency | ms | p99_latency | ms | rps | req/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 |
## 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>
Summary
Problem
The Pro
spec/dummyapp showed unstyled HTML on initial page load before the CSS bundle finished loading. This created a poor first impression when runningbin/dev.Solution
Extract critical CSS to a separate
_critical_styles.html.erbpartial for better maintainability. The partial includes: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
bin/devand Slow 3G throttling🤖 Generated with Claude Code
Summary by CodeRabbit
Documentation
New Features
Tests