Getting Started
Blocks
About
The useIsClient hook is a simple utility that helps you determine if your React component is currently rendering on the client side. This is particularly useful in Next.js applications where components can render both on the server (SSR) and client side.
Features
- ✅ SSR Safe: Prevents hydration mismatches
- ✅ Simple API: Just returns a boolean value
- ✅ Lightweight: Minimal performance impact
- ✅ TypeScript: Fully typed
Use Cases
- Preventing hydration mismatches with dynamic content
- Conditionally rendering client-only components
- Accessing browser APIs safely
- Progressive enhancement patterns
Preview
Server rendering...
Loading client-side content...
"use client"
import { useEffect, useState } from "react"
import { Button } from "@/components/ui/button"
import { useIsClient } from "@/hooks/use-is-client"
export function UseIsClientDemo() {
const isClient = useIsClient()
const [count, setCount] = useState(0)
const [currentTime, setCurrentTime] = useState<string>("")
useEffect(() => {
if (isClient) {
const updateTime = () => {
setCurrentTime(new Date().toLocaleTimeString())
}
updateTime()
const interval = setInterval(updateTime, 1000)
return () => clearInterval(interval)
}
}, [isClient])
if (!isClient) {
return (
<div className="max-w-md rounded-lg border p-6">
<div className="mb-4 flex items-center gap-2">
<div className="h-2 w-2 animate-pulse rounded-full bg-yellow-500"></div>
<span className="text-muted-foreground text-sm">
Server rendering...
</span>
</div>
<p className="text-muted-foreground">Loading client-side content...</p>
</div>
)
}
return (
<div className="max-w-md space-y-4 rounded-lg border p-6">
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-green-500"></div>
<span className="text-sm font-medium text-green-600">
Client-side rendered!
</span>
</div>
<div className="space-y-2">
<p className="text-muted-foreground text-sm">
Current time: <span className="font-mono">{currentTime}</span>
</p>
<div className="flex items-center gap-2">
<Button onClick={() => setCount((c) => c + 1)} size="sm">
Count: {count}
</Button>
</div>
<p className="text-muted-foreground text-xs">
User Agent: {navigator.userAgent.slice(0, 50)}...
</p>
</div>
</div>
)
}
Installation
pnpm dlx shadcn@latest add https://ui.nowts.app/r/use-is-client.json
Usage
Basic usage
import { useIsClient } from "@/hooks/use-is-client"
export function ClientOnlyComponent() {
const isClient = useIsClient()
if (!isClient) {
return <div>Loading...</div>
}
return (
<div>
<p>This content only renders on the client!</p>
<p>Current URL: {window.location.href}</p>
</div>
)
}
Conditional rendering with browser APIs
import { useIsClient } from "@/hooks/use-is-client"
export function GeolocationComponent() {
const isClient = useIsClient()
const [location, setLocation] = useState<string | null>(null)
useEffect(() => {
if (isClient && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
setLocation(
`${position.coords.latitude}, ${position.coords.longitude}`
)
},
(error) => {
setLocation("Location unavailable")
}
)
}
}, [isClient])
if (!isClient) {
return <div>Checking location availability...</div>
}
return (
<div>
<h3>Your Location</h3>
<p>{location || "Getting location..."}</p>
</div>
)
}
Progressive enhancement
import { useIsClient } from "@/hooks/use-is-client"
export function EnhancedButton() {
const isClient = useIsClient()
const [interactionCount, setInteractionCount] = useState(0)
const handleClick = () => {
setInteractionCount((prev) => prev + 1)
// Client-only enhancements
if (isClient) {
// Haptic feedback on mobile
if ("vibrate" in navigator) {
navigator.vibrate(50)
}
// Analytics tracking
gtag?.("event", "button_click", {
interaction_count: interactionCount + 1,
})
}
}
return (
<button
onClick={handleClick}
className="rounded bg-blue-500 px-4 py-2 text-white"
>
Click me {isClient ? `(${interactionCount})` : ""}
</button>
)
}
Preventing hydration mismatches
import { useIsClient } from "@/hooks/use-is-client"
export function DynamicContent() {
const isClient = useIsClient()
// This would cause hydration mismatch without useIsClient
const randomId = Math.random().toString(36).substr(2, 9)
return (
<div>
<h2>Dynamic Content</h2>
{isClient ? <p>Random ID: {randomId}</p> : <p>Random ID: Loading...</p>}
</div>
)
}
Working with local storage
import { useIsClient } from "@/hooks/use-is-client"
export function UserPreferences() {
const isClient = useIsClient()
const [theme, setTheme] = useState<string>("light")
useEffect(() => {
if (isClient) {
const savedTheme = localStorage.getItem("theme") || "light"
setTheme(savedTheme)
}
}, [isClient])
const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light"
setTheme(newTheme)
if (isClient) {
localStorage.setItem("theme", newTheme)
}
}
if (!isClient) {
return <div>Loading preferences...</div>
}
return (
<div
className={`p-4 ${theme === "dark" ? "bg-gray-800 text-white" : "bg-white text-black"}`}
>
<h3>Theme: {theme}</h3>
<button onClick={toggleTheme}>
Switch to {theme === "light" ? "dark" : "light"} theme
</button>
</div>
)
}
Build Your SaaS in Days, Not Months
NOW.TS is the Next.js 15 boilerplate with everything you need to launch your SaaS—auth, payments, database, and AI-ready infrastructure.
Learn more about NOW.TS