Final Screen Size Solution
Static generation + Progressive enhancement + No hydration mismatch
Solution Comparison
Server-side headers() vs Our hybrid approach
| Feature | Server-side headers() (Initial Problem) | Our Hybrid Solution (Current Implementation) |
|---|---|---|
| Static Generation (SSG/ISR) | Broken headers() makes entire app dynamic | Works No headers() usage = truly static |
| Build Time Generation | None No static HTML generated | Static HTML Generated with sensible defaults |
| Performance | Poor Dynamic rendering on every request | Excellent Static serving + progressive enhancement |
| CDN Caching | Disabled Dynamic pages can't be cached | Enabled Static pages cached globally |
| Server Components Support | Yes Can use headers() directly | Yes Can use static hints via Promise |
| Hydration Mismatch | None Server and client have same data | None Progressive enhancement prevents mismatch |
| Screen Size Accuracy | Limited Only device type hints from User-Agent | Exact Device hints + exact pixel measurements |
| Resize Updates | No Static after initial render | Real-time Updates on window resize events |
| Development Experience | Complex Must understand dynamic rendering implications | Simple Works like any other React context |
| Overall Recommendation | Avoid Breaks static optimization | Recommended Best of both worlds |
❌ Server-side headers() Problems:
- • Breaks static generation completely
- • Poor performance (dynamic rendering)
- • No CDN caching benefits
- • Limited screen size information
✅ Our Hybrid Solution Benefits:
- • Maintains static generation
- • Excellent performance
- • Full CDN caching support
- • Exact screen measurements + device hints
📋 Implementation Walkthrough
Here's how our hybrid solution works step-by-step, with actual code snippets from the implementation:
Step 1: Static Hint Generation
No headers() usage = Keeps everything static
// lib/screen-size-hint.ts
export interface ScreenSizeHint {
buildTimeDefaults: {
isMobile: boolean
isTablet: boolean
isDesktop: boolean
deviceType: "desktop"
}
timestamp: number
}
export async function getStaticScreenSizeHint(): Promise<ScreenSizeHint> {
// ✅ NO headers() usage - this keeps it static!
return {
buildTimeDefaults: {
isMobile: false,
isTablet: false,
isDesktop: true,
deviceType: "desktop", // Safe default for build time
},
timestamp: Date.now(),
}
}Key: This function returns sensible defaults without using any dynamic APIs like headers(). This ensures the entire app remains statically generated.
Step 2: Provider with React's use() Hook
Progressive enhancement from static to dynamic
// components/screen-size-provider.tsx
"use client"
import { createContext, useContext, useEffect, useState } from "react"
import { use } from "react" // ✨ React's use() hook
import type { ScreenSizeHint } from "../lib/screen-size-hint"
export function ScreenSizeProvider({
children,
staticHintPromise, // ✨ Promise from server
}: {
children: React.ReactNode
staticHintPromise: Promise<ScreenSizeHint>
}) {
// ✨ use() unwraps the Promise from the server
const staticData = use(staticHintPromise)
const [isHydrated, setIsHydrated] = useState(false)
const [serverHint, setServerHint] = useState(null)
const [clientSize, setClientSize] = useState({
width: 1024, height: 768,
isMobile: false, isTablet: false, isDesktop: true,
breakpoint: "lg" as const,
})
useEffect(() => {
// 🔍 Get device hints from User-Agent (client-side)
const userAgent = navigator.userAgent
const isMobile = /Mobile|Android|iPhone/.test(userAgent)
const isTablet = /iPad|Tablet/.test(userAgent)
const isDesktop = !isMobile && !isTablet
setServerHint({ isMobile, isTablet, isDesktop,
deviceType: isMobile ? "mobile" : isTablet ? "tablet" : "desktop"
})
// 📏 Get exact client measurements
function updateClientSize() {
const width = window.innerWidth
const height = window.innerHeight
// ... calculate breakpoints and device types
setClientSize({ width, height, isMobile: width < 768, /* ... */ })
}
updateClientSize()
setIsHydrated(true)
window.addEventListener("resize", updateClientSize)
return () => window.removeEventListener("resize", updateClientSize)
}, [])
return (
<ScreenSizeContext.Provider value={{
buildTimeDefaults: staticData.buildTimeDefaults, // ✅ Available immediately
client: clientSize, // ✅ Available after hydration
serverHint, // ✅ Available after User-Agent detection
isHydrated
}}>
{children}
</ScreenSizeContext.Provider>
)
}Key: The use() hook unwraps the Promise from the server, providing immediate access to static defaults while progressively enhancing with User-Agent hints and exact client measurements.
Step 3: Root Layout Setup
Passing the Promise to the client provider
// app/layout.tsx
import { ScreenSizeProvider } from "../components/screen-size-provider"
import { getStaticScreenSizeHint } from "../lib/screen-size-hint"
export default function RootLayout({ children }: { children: React.ReactNode }) {
// ✨ Create the Promise on the server (but it's static!)
const staticHintPromise = getStaticScreenSizeHint()
return (
<html lang="en">
<body>
{/* ✨ Pass the Promise to the client provider */}
<ScreenSizeProvider staticHintPromise={staticHintPromise}>
{children}
</ScreenSizeProvider>
</body>
</html>
)
}Key: The server creates a Promise with static defaults and passes it to the client provider. No dynamic APIs are used, so the layout remains statically generated.
Step 4: Using the Hook in Components
Choose the best data source for your needs
// components/adaptive-component.tsx
"use client"
import { useScreenSize } from "./screen-size-provider"
export function AdaptiveComponent() {
const { buildTimeDefaults, client, serverHint, isHydrated } = useScreenSize()
// 🎯 Choose the best data source for your use case:
// Option 1: Use server hint when available, fallback to build defaults
const currentDevice = isHydrated && serverHint ? serverHint : buildTimeDefaults
// Option 2: Wait for exact client measurements
const exactSize = isHydrated ? client : { width: 1024, isMobile: false }
// Option 3: Progressive enhancement - start with defaults, upgrade to hints
const deviceType = serverHint?.deviceType || buildTimeDefaults.deviceType
return (
<div>
{currentDevice.isMobile ? (
<MobileLayout />
) : currentDevice.isTablet ? (
<TabletLayout />
) : (
<DesktopLayout />
)}
<div>Current width: {exactSize.width}px</div>
<div>Device type: {deviceType}</div>
</div>
)
}Key: Components can choose between build-time defaults (immediate), server hints (after User-Agent detection), or exact client measurements (after hydration) based on their specific needs.
Step 5: Server Component Usage
Server components can use static hints
// components/server-component.tsx
import { getStaticScreenSizeHint } from "../lib/screen-size-hint"
export async function ServerComponent() {
// ✅ Server components can use the static hint!
const hint = await getStaticScreenSizeHint()
return (
<div>
{hint.buildTimeDefaults.isDesktop ? (
<div>
<h2>Desktop-optimized content</h2>
<ComplexDataVisualization />
</div>
) : (
<div>
<h2>Mobile-optimized content</h2>
<SimpleCardLayout />
</div>
)}
<p>Generated at: {new Date(hint.timestamp).toLocaleString()}</p>
</div>
)
}Key: Server components can make rendering decisions based on static defaults, then client components can enhance the experience with real device detection.
🔄 How It Works: Timeline
The progressive enhancement process
Build Time
Static HTML generated with desktop defaults (isMobile: false, isDesktop: true). // Remember: this is config and it can be tailored to our needs (based on sessions data).
Server Response
Static HTML served instantly from CDN with build-time defaults
Client Hydration
React hydrates, use() hook unwraps Promise, User-Agent detection begins
Device Detection
User-Agent parsed, exact screen measurements taken, components re-render with real data
Runtime Updates
Window resize events update measurements in real-time
✅ How This Solution Works
Build Time:
- Static HTML generated with desktop defaults
- No headers() usage = truly static
- ISR/SSG works perfectly
- Fast CDN serving
Runtime:
- User-Agent detection for device hints
- Exact screen measurements
- Real-time resize updates
- Progressive enhancement
Server Component with Static Hints
This server component can make rendering decisions based on build-time defaults.
💡 This content was rendered on the server with static defaults, then enhanced on the client with real device detection.
Adaptive Content
🖥️ Desktop Layout
Optimized for desktop with mouse interactions
Build Time (Static)
Device: desktop
Mobile: No
Desktop: Yes
✅ Statically generated
User-Agent Hint (Loading...)
Detecting...
Client Measurements (Loading...)
Width: 1024px
Height: 768px
Breakpoint: lg
Mobile: No
📏 Exact measurements
🎯 Key Benefits
Performance
- Static HTML serving
- CDN cacheable
- Fast initial load
- Progressive enhancement
Developer Experience
- Server components work
- No hydration mismatch
- TypeScript support
- Easy to use hook
Functionality
- Device type detection
- Exact screen dimensions
- Responsive breakpoints
- Resize event handling