Render by device. Survive the server.
Device-class rendering for React, with no hydration mismatch and iPadOS detected as a tablet rather than a desktop.
Five view components and a hook. The detector itself is a pure function you can call from server code, so the first paint can already be correct.
- React 17 · 18 · 19
- zero runtime dependencies
- ESM + CJS
- sideEffects: false
Five views, one import
import { MobileView, DesktopView, IOSView, AndroidView, TabletView }
from "react-device-detector";
<AndroidView>only on Android</AndroidView>
<IOSView>only on iOS</IOSView>
<MobileView>only on mobile</MobileView>
<TabletView>only on tablets</TabletView>
<DesktopView>only on desktop</DesktopView>
Or branch in JavaScript
const { isMobile, isDetecting } = useDevice();
if (isDetecting) return <Skeleton />;
return isMobile ? <MobileNav /> : <DesktopNav />;
isDetecting exists so you can render a skeleton rather than guess.
Correct on the first paint
The main entry ships "use client", so importing the views into a Server
Component works as-is. To skip the wait for hydration, detect from the request
user-agent instead. The pure detector lives at react-device-detector/server
and carries no "use client", so server code can call it.
// app/layout.tsx
import { headers } from "next/headers";
import { detectDevice } from "react-device-detector/server";
import { DeviceProvider } from "react-device-detector";
const ua = (await headers()).get("user-agent") ?? "";
const device = detectDevice({ userAgent: ua, maxTouchPoints: 0 });
<DeviceProvider value={device}>{children}</DeviceProvider>
DeviceInfo is a plain object of booleans, so it serializes cleanly across the
Server/Client boundary.
The detection gap, stated plainly
A component cannot know the device during server rendering.
There is no device to inspect, only an HTTP request. Views therefore render
null until detection completes — which is precisely why v1’s flash of
wrong-device content is gone. Two ways to handle that window:
<DesktopView renderWhileDetecting>…</DesktopView> // render during detection
setServerDevice(detectDevice({ userAgent, maxTouchPoints: 0 })); // or seed it
Header-based detection cannot see maxTouchPoints, so an iPadOS 13+ device
looks like a Mac to the server. The client corrects it immediately after hydration. If
iPad accuracy on the first paint matters, use
<DesktopView renderWhileDetecting={false}> and let the client decide.
Accuracy
User-agent detection is a heuristic, not a fact. UA spoofing defeats it, so do browsers that freeze or reduce their UA string, and so do devices that do not exist yet.
Where a CSS media query (pointer: coarse, min-width) can answer
your question, prefer it — a media query describes the actual viewport instead of guessing
from a string.
This library is the right tool when you need to branch in JavaScript on device class and accept a heuristic’s error rate.
API
| Export | Type | Notes |
|---|---|---|
MobileView DesktopView IOSView AndroidView TabletView |
components | accept renderWhileDetecting |
useDevice() |
hook | isMobile, isDesktop, isIOS, isAndroid, isTablet, isDetecting |
detectDevice({ userAgent, maxTouchPoints }) |
function | pure; usable on the server |
setServerDevice(info) |
function | seed the value for a correct first paint |
Upgrading from v1
v1 carried two bugs serious enough to require a breaking change: every SSR page had a
hydration mismatch, and modern iPads were detected as desktops. v2’s only peer
dependency is react.
The full migration notes are in the README.