Hotjar vs FullStory vs Microsoft Clarity: Session Replay and Heatmaps (2026)
TL;DR
Hotjar is the product experience insights platform — session recordings, heatmaps, feedback widgets, surveys, funnels, used by product teams to understand user behavior. FullStory is the digital experience intelligence platform — session replay, frustration signals, product analytics, error tracking, enterprise-grade search and segmentation. Microsoft Clarity is the free session replay tool — heatmaps, session recordings, rage click detection, scroll maps, completely free with no traffic limits. In 2026: Hotjar for product teams wanting insights + feedback, FullStory for enterprise digital experience analytics, Clarity for free session replay at any scale.
Key Takeaways
- Hotjar: Session replay + heatmaps + surveys + feedback widgets
- FullStory: Session replay + product analytics + frustration detection + error correlation
- Microsoft Clarity: Free session replay + heatmaps + rage click detection, no traffic limits
- Hotjar has the best user feedback tools (surveys, polls, widgets)
- FullStory has the most powerful search and segmentation
- Clarity is 100% free with unlimited sessions
Hotjar
Hotjar — product experience insights:
Setup
<!-- Hotjar tracking code: -->
<script>
(function(h,o,t,j,a,r){
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
h._hjSettings={hjid:YOUR_SITE_ID,hjsv:6};
a=o.getElementsByTagName('head')[0];
r=o.createElement('script');r.async=1;
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
a.appendChild(r);
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
</script>
Next.js integration
// app/components/HotjarAnalytics.tsx
"use client"
import { useEffect } from "react"
import { usePathname } from "next/navigation"
declare global {
interface Window {
hj: (...args: any[]) => void
}
}
export function HotjarAnalytics({ siteId }: { siteId: number }) {
useEffect(() => {
// Initialize Hotjar:
(function(h: any, o: any, t: string, j: string, a?: any, r?: any) {
h.hj = h.hj || function() { (h.hj.q = h.hj.q || []).push(arguments) }
h._hjSettings = { hjid: siteId, hjsv: 6 }
a = o.getElementsByTagName("head")[0]
r = o.createElement("script")
r.async = 1
r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv
a.appendChild(r)
})(window, document, "https://static.hotjar.com/c/hotjar-", ".js?sv=")
}, [siteId])
return null
}
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<HotjarAnalytics siteId={Number(process.env.NEXT_PUBLIC_HOTJAR_ID)} />
{children}
</body>
</html>
)
}
Events, user identification, and surveys
// Identify user:
window.hj("identify", "user-123", {
email: "royce@example.com",
plan: "pro",
signup_date: "2026-01-15",
})
// Track custom events:
window.hj("event", "package_compared")
window.hj("event", "report_downloaded")
window.hj("event", "subscription_started")
// Trigger survey programmatically:
window.hj("trigger", "comparison-feedback")
// Tag recordings:
window.hj("tagRecording", ["pro-user", "comparison-page"])
// State change (for SPAs):
window.hj("stateChange", "/packages/react")
// React hook:
function useHotjar() {
return {
identify: (userId: string, attributes: Record<string, any>) => {
if (typeof window !== "undefined" && window.hj) {
window.hj("identify", userId, attributes)
}
},
event: (eventName: string) => {
if (typeof window !== "undefined" && window.hj) {
window.hj("event", eventName)
}
},
trigger: (surveyId: string) => {
if (typeof window !== "undefined" && window.hj) {
window.hj("trigger", surveyId)
}
},
}
}
function ComparisonPage() {
const hotjar = useHotjar()
useEffect(() => {
hotjar.event("comparison_page_viewed")
}, [])
return (
<div>
<ComparisonTable />
<button onClick={() => hotjar.trigger("comparison-feedback")}>
Give Feedback
</button>
</div>
)
}
Feedback widgets
// Hotjar Feedback widget (configured in dashboard):
// - Incoming Feedback: always-visible widget for user comments
// - Surveys: multi-step questionnaires
// - User Interviews: recruit users for research
// Trigger specific feedback:
window.hj("trigger", "nps-survey") // Net Promoter Score
window.hj("trigger", "exit-intent") // When user leaves
window.hj("trigger", "post-purchase") // After conversion
// Control recording:
window.hj("trigger", "start_recording") // Force start
// Session recordings auto-capture:
// - Rage clicks
// - U-turns (back-and-forth navigation)
// - Error clicks
FullStory
FullStory — digital experience intelligence:
Setup
// Using @fullstory/browser:
import * as FullStory from "@fullstory/browser"
FullStory.init({
orgId: process.env.NEXT_PUBLIC_FULLSTORY_ORG_ID!,
devMode: process.env.NODE_ENV === "development",
})
Next.js integration
// app/components/FullStoryAnalytics.tsx
"use client"
import { useEffect } from "react"
import * as FullStory from "@fullstory/browser"
export function FullStoryAnalytics() {
useEffect(() => {
FullStory.init({
orgId: process.env.NEXT_PUBLIC_FULLSTORY_ORG_ID!,
devMode: process.env.NODE_ENV === "development",
})
}, [])
return null
}
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<FullStoryAnalytics />
{children}
</body>
</html>
)
}
User identification and events
import * as FullStory from "@fullstory/browser"
// Identify user:
FullStory.identify("user-123", {
displayName: "Royce",
email: "royce@example.com",
plan_str: "pro",
signupDate_date: new Date("2026-01-15"),
packagesTracked_int: 25,
})
// Track custom events:
FullStory.event("Package Compared", {
packages_strs: ["react", "vue"],
comparisonType_str: "downloads",
resultCount_int: 2,
})
FullStory.event("Report Downloaded", {
format_str: "pdf",
packageCount_int: 10,
})
FullStory.event("Subscription Started", {
plan_str: "pro",
price_real: 29.99,
billingCycle_str: "monthly",
})
// Set page properties:
FullStory.setProperties("page", {
pageName_str: "Package Comparison",
category_str: "analytics",
})
// Consent management:
FullStory.consent(true) // User consented
FullStory.consent(false) // Revoke consent
FullStory.restart() // Restart after consent
// Get session URL (for support tickets):
const sessionUrl = FullStory.getCurrentSessionURL(true) // true = with timestamp
console.log(`Support replay: ${sessionUrl}`)
Frustration signals and search
import * as FullStory from "@fullstory/browser"
// FullStory automatically detects:
// - Rage Clicks: rapid clicking on same element
// - Dead Clicks: clicks that produce no response
// - Error Clicks: clicks that trigger JS errors
// - Thrashed Cursor: rapid mouse movement (confusion)
// React error boundary integration:
class FullStoryErrorBoundary extends React.Component {
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// FullStory automatically captures errors,
// but you can add custom context:
FullStory.event("React Error Boundary", {
error_str: error.message,
component_str: errorInfo.componentStack || "unknown",
})
}
render() {
return this.props.children
}
}
// Search API (server-side):
// FullStory provides powerful search:
// - Find sessions with rage clicks on checkout button
// - Find sessions where users hit errors after comparing packages
// - Find sessions by user attributes (plan, signup date)
// - Combine with custom events for precise segment analysis
// Session replay link for customer support:
function SupportWidget() {
const getReplayLink = () => {
const url = FullStory.getCurrentSessionURL(true)
navigator.clipboard.writeText(url || "")
}
return (
<button onClick={getReplayLink}>
Copy Session Link for Support
</button>
)
}
Microsoft Clarity
Microsoft Clarity — free session replay:
Setup
<!-- Clarity tracking code: -->
<script type="text/javascript">
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "YOUR_PROJECT_ID");
</script>
Next.js integration
// app/components/ClarityAnalytics.tsx
"use client"
import { useEffect } from "react"
declare global {
interface Window {
clarity: (...args: any[]) => void
}
}
export function ClarityAnalytics({ projectId }: { projectId: string }) {
useEffect(() => {
(function(c: any, l: any, a: string, r: string, i: string, t?: any, y?: any) {
c[a] = c[a] || function() { (c[a].q = c[a].q || []).push(arguments) }
t = l.createElement(r)
t.async = 1
t.src = "https://www.clarity.ms/tag/" + i
y = l.getElementsByTagName(r)[0]
y.parentNode.insertBefore(t, y)
})(window, document, "clarity", "script", projectId)
}, [projectId])
return null
}
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<ClarityAnalytics projectId={process.env.NEXT_PUBLIC_CLARITY_PROJECT_ID!} />
{children}
</body>
</html>
)
}
Custom tags and events
// Identify user:
window.clarity("identify", "user-123", "session-456", "comparison-page")
// Custom tags (filter recordings by tag):
window.clarity("set", "plan", "pro")
window.clarity("set", "page_type", "comparison")
window.clarity("set", "user_segment", "power_user")
// Custom events:
window.clarity("event", "Package Compared")
window.clarity("event", "Report Downloaded")
window.clarity("event", "Subscription Started")
// Consent management:
window.clarity("consent") // Mark session as consented
// Upgrade session (ensure this session is recorded):
window.clarity("upgrade", "high-value-session")
// React hook:
function useClarity() {
return {
identify: (userId: string, sessionId?: string) => {
if (typeof window !== "undefined" && window.clarity) {
window.clarity("identify", userId, sessionId)
}
},
set: (key: string, value: string) => {
if (typeof window !== "undefined" && window.clarity) {
window.clarity("set", key, value)
}
},
event: (name: string) => {
if (typeof window !== "undefined" && window.clarity) {
window.clarity("event", name)
}
},
upgrade: (reason: string) => {
if (typeof window !== "undefined" && window.clarity) {
window.clarity("upgrade", reason)
}
},
}
}
function ComparisonPage({ userId }: { userId: string }) {
const clarity = useClarity()
useEffect(() => {
clarity.identify(userId)
clarity.set("page_type", "comparison")
}, [userId])
const handleCompare = () => {
clarity.event("compare_clicked")
clarity.upgrade("conversion-event") // Ensure this session is saved
}
return <button onClick={handleCompare}>Compare</button>
}
Clarity features (auto-detected)
// Microsoft Clarity automatically detects and surfaces:
// 1. Rage Clicks — rapid repeated clicks on the same area
// → Indicates frustration with unresponsive UI elements
// 2. Dead Clicks — clicks that produce no page change or response
// → Indicates confusing UX where users expect interactivity
// 3. Quick Backs — users navigate to a page then immediately return
// → Indicates misleading links or wrong content
// 4. Excessive Scrolling — scrolling significantly past main content
// → Indicates users can't find what they're looking for
// 5. Scroll Depth — how far users scroll on each page
// → Heatmap shows where attention drops off
// Clarity Dashboard provides:
// - Session recordings (filtered by frustration signals)
// - Click heatmaps
// - Scroll heatmaps
// - Insights dashboard (automatic AI insights)
// - Google Analytics integration
// - Copilot AI summaries of session patterns
// No API access — dashboard-only analytics
// No traffic limits — records all sessions for free
// GDPR compliant — no personal data collection by default
Feature Comparison
| Feature | Hotjar | FullStory | Microsoft Clarity |
|---|---|---|---|
| Session replay | ✅ | ✅ | ✅ |
| Heatmaps (click) | ✅ | ✅ | ✅ |
| Heatmaps (scroll) | ✅ | ✅ | ✅ |
| Rage click detection | ✅ | ✅ | ✅ |
| Dead click detection | ✅ | ✅ | ✅ |
| Frustration scoring | ✅ (basic) | ✅ (advanced) | ✅ (basic) |
| Custom events | ✅ | ✅ (typed) | ✅ (tags) |
| User identification | ✅ | ✅ | ✅ |
| Surveys/feedback | ✅ (built-in) | ❌ | ❌ |
| Product analytics | ✅ (funnels) | ✅ (advanced) | ❌ |
| Error correlation | ❌ | ✅ | ❌ |
| Search & segmentation | ✅ (basic) | ✅ (powerful) | ✅ (basic) |
| API access | ✅ | ✅ | ❌ |
| AI insights | ❌ | ✅ | ✅ (Copilot) |
| GA integration | ❌ | ✅ | ✅ |
| Data export | ✅ | ✅ | ❌ |
| Free tier | 35 daily sessions | 1K sessions/mo | ✅ (unlimited) |
| Pricing | From $39/month | Custom pricing | Free |
When to Use Each
Use Hotjar if:
- Want session replay combined with user feedback tools
- Need surveys, feedback widgets, and NPS built-in
- Building product insights for product/UX teams
- Want heatmaps + recordings + feedback in one tool
Use FullStory if:
- Need enterprise-grade digital experience analytics
- Want powerful search and segmentation across sessions
- Need error correlation with session replays
- Building data-driven product teams with advanced analytics
Use Microsoft Clarity if:
- Want completely free session replay with no limits
- Need basic heatmaps and rage/dead click detection
- Starting with UX analytics and don't need advanced features
- Want AI-powered session summaries (Copilot)
Methodology
Feature comparison based on Hotjar, FullStory, and Microsoft Clarity platforms and pricing as of March 2026.