A production-ready solution for detecting screen sizes in Next.js applications. This hybrid approach combines the benefits of server-side hints with client-side precision while maintaining optimal performance.
Using headers() in Next.js to detect screen sizes causes the entire application to opt into dynamic rendering, which:
- β Breaks static generation (SSG/ISR)
- β Disables CDN caching
- β Reduces performance significantly
- β Only provides limited device hints
This repository demonstrates a hybrid approach that:
- β
Maintains static generation - No
headers()usage - β Excellent performance - Static HTML + progressive enhancement
- β CDN cacheable - Full static optimization benefits
- β Exact measurements - Real pixel dimensions + device hints
- β Server component support - Works with React Server Components
- β No hydration mismatch - Progressive enhancement prevents issues
- Static HTML generated with sensible desktop defaults
- No dynamic APIs used, keeping everything static
- ISR/SSG works perfectly
- User-Agent detection for device hints
- Exact screen measurements via
window.innerWidth/innerHeight - Real-time updates on window resize
- Build-time defaults: Available immediately (SSR-safe)
- Device hints: From User-Agent parsing (after hydration)
- Exact measurements: Real pixel dimensions (after hydration)
-
Clone this repository
git clone https://github.com/your-username/nextjs-screen-size-solution.git cd nextjs-screen-size-solution -
Install dependencies
npm install # or yarn install # or pnpm install
-
Run the development server
npm run dev # or yarn dev # or pnpm dev
-
Open http://localhost:3000 to see the demo
- Add the provider to your root layout:
// app/layout.tsx
import { ScreenSizeProvider } from "../components/screen-size-provider";
import { getStaticScreenSizeHint } from "../lib/screen-size-hint";
export default function RootLayout({ children }) {
const staticHintPromise = getStaticScreenSizeHint();
return (
<html lang="en">
<body>
<ScreenSizeProvider staticHintPromise={staticHintPromise}>
{children}
</ScreenSizeProvider>
</body>
</html>
);
}- Use the hook in your components:
// components/your-component.tsx
"use client";
import { useScreenSize } from "./screen-size-provider";
export function YourComponent() {
const { buildTimeDefaults, client, serverHint, isHydrated } = useScreenSize();
// Choose the best data source for your needs
const currentDevice =
isHydrated && serverHint ? serverHint : buildTimeDefaults;
return (
<div>
{currentDevice.isMobile ? <MobileLayout /> : <DesktopLayout />}
<p>Screen width: {client.width}px</p>
<p>Device type: {currentDevice.deviceType}</p>
</div>
);
}Server components can use static hints for conditional rendering:
// components/server-component.tsx
import { getStaticScreenSizeHint } from "../lib/screen-size-hint";
export async function ServerComponent() {
const hint = await getStaticScreenSizeHint();
return (
<div>
{hint.buildTimeDefaults.isDesktop ? (
<ComplexDesktopLayout />
) : (
<SimpleMobileLayout />
)}
</div>
);
}Returns an object with the following properties:
interface ScreenSizeContextType {
// Build-time defaults (available immediately)
buildTimeDefaults: {
isMobile: boolean;
isTablet: boolean;
isDesktop: boolean;
deviceType: "desktop";
};
// Client measurements (available after hydration)
client: {
width: number;
height: number;
isMobile: boolean;
isTablet: boolean;
isDesktop: boolean;
breakpoint: "sm" | "md" | "lg" | "xl" | "2xl";
};
// Runtime server hint (available after User-Agent detection)
serverHint: {
isMobile: boolean;
isTablet: boolean;
isDesktop: boolean;
deviceType: "mobile" | "tablet" | "desktop";
} | null;
isHydrated: boolean;
}Choose the appropriate data source based on your needs:
buildTimeDefaults: Use for SSR-safe rendering with sensible defaultsserverHint: Use for device-type detection after hydrationclient: Use for exact pixel measurements and responsive behavior
lib/screen-size-hint.ts- Static hint generation (no headers)components/screen-size-provider.tsx- Main provider with React'suse()hookapp/layout.tsx- Root layout setupcomponents/responsive-demo.tsx- Demo component showing all features
| Feature | Server-side headers() | Client-side Only | Our Hybrid Solution |
|---|---|---|---|
| Static Generation | β Broken | β Works | β Works |
| Performance | β Poor | β Good | β Excellent |
| Server Components | β Yes | β No | β Yes |
| Exact Dimensions | β No | β Yes | β Yes |
| Hydration Issues | β None | β None |
- Push your code to GitHub
- Connect your repository to Vercel
- Deploy with zero configuration
This solution works with any platform that supports Next.js:
- Netlify
- AWS Amplify
- Railway
- DigitalOcean App Platform
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with Next.js
- Uses React's experimental
use()hook - Inspired by the need for better screen size detection in SSG applications
Made with β€οΈ for the Next.js community