Skip to content

Commit 7bec5e5

Browse files
Prevent FOUC during client hints reload with improved stop mechanism
Co-authored-by: me <me@kentcdodds.com>
1 parent 8ba5da9 commit 7bec5e5

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

FOUC_SOLUTION.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Flash of Unstyled Content (FOUC) Solution
2+
3+
## Problem Analysis
4+
5+
The issue was in the client hints library's reload mechanism. When client preferences (like color scheme, timezone, reduced motion) changed, the system would trigger a page reload using `window.location.reload()`. However, this simple approach caused a **Flash of Unstyled Content (FOUC)** because:
6+
7+
1. The reload was triggered but the browser continued processing the current HTML document
8+
2. Resource requests on the page got canceled during the reload process
9+
3. This led to partially rendered content without styles before the reload completed
10+
11+
## Root Cause
12+
13+
The problematic code was in `src/index.ts` at line 76-80:
14+
15+
```javascript
16+
if (cookieChanged) window.location.reload();
17+
```
18+
19+
This single line allowed the browser to continue rendering the current page state while initiating the reload, causing the visual flash.
20+
21+
## Solution Implemented
22+
23+
The solution involves a three-step approach to prevent any visual artifacts:
24+
25+
```javascript
26+
if (cookieChanged) {
27+
// Stop all resource loading and DOM processing to prevent FOUC
28+
if (window.stop) window.stop();
29+
30+
// Hide the page content immediately to prevent visual flicker
31+
const style = document.createElement('style');
32+
style.textContent = 'html { visibility: hidden !important; }';
33+
document.head.appendChild(style);
34+
35+
// Trigger the reload
36+
window.location.reload();
37+
}
38+
```
39+
40+
### Step-by-Step Breakdown
41+
42+
1. **Stop Processing**: `window.stop()` immediately halts all current resource loading and DOM processing
43+
2. **Hide Content**: Inject a style tag that makes the entire HTML document invisible using `visibility: hidden !important`
44+
3. **Reload**: Only then trigger the actual page reload
45+
46+
## Benefits of This Approach
47+
48+
- **Eliminates FOUC**: No flash of unstyled content during the transition
49+
- **Backward Compatible**: All existing tests pass without modification
50+
- **Graceful Degradation**: The `if (window.stop)` check ensures compatibility with browsers that might not support this method
51+
- **Immediate Effect**: The visibility hidden style is applied synchronously, preventing any visual artifacts
52+
53+
## Testing & Validation
54+
55+
- ✅ All existing unit tests pass
56+
- ✅ TypeScript compilation succeeds without errors
57+
- ✅ Solution is included in the compiled JavaScript output
58+
- ✅ Maintains the same core functionality while solving the visual issue
59+
60+
## Browser Compatibility
61+
62+
- `window.stop()` is supported in all modern browsers
63+
- The `visibility: hidden` CSS property is universally supported
64+
- The solution gracefully handles edge cases where `window.stop()` might not be available
65+
66+
This solution ensures a smooth user experience during client hint changes while maintaining the library's existing functionality and API.

src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,18 @@ function checkClientHints() {
8383
cookieChanged = true;
8484
}
8585
}
86-
if (cookieChanged) window.location.reload();
86+
if (cookieChanged) {
87+
// Stop all resource loading and DOM processing to prevent FOUC
88+
if (window.stop) window.stop();
89+
90+
// Hide the page content immediately to prevent visual flicker
91+
const style = document.createElement('style');
92+
style.textContent = 'html { visibility: hidden !important; }';
93+
document.head.appendChild(style);
94+
95+
// Trigger the reload
96+
window.location.reload();
97+
}
8798
}
8899
89100
checkClientHints();

0 commit comments

Comments
 (0)