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 |
The Value of Session Replay Data
Session replay tools answer questions that analytics dashboards cannot. A conversion funnel chart shows that 40% of users abandon the checkout flow at the payment step — but it doesn't show whether they're confused by the form layout, encountering a JavaScript error, or hitting a performance issue that makes the page feel unresponsive. Session recordings of users who abandoned at that step reveal the actual behavior: users clicking repeatedly on a disabled submit button (rage click), scrolling up and down looking for something they missed, or simply closing the tab after a 10-second wait for the page to respond. This qualitative behavioral data, combined with quantitative funnel metrics, enables specific UI fixes rather than guesses. The best use of session replay tools is a weekly review ritual where product and engineering teams watch recordings together and identify friction patterns.
Privacy Compliance and Data Governance
Session replay tools are among the most privacy-sensitive third-party scripts you can add to a website. All three platforms automatically mask password fields and common sensitive inputs, but the masking defaults differ. Hotjar masks all form inputs by default — you must explicitly mark fields as unmasked using data-hj-allow. FullStory uses a class-based masking system where elements with fs-exclude are not recorded, and the default recording mode can be configured to mask all text by default with selective unmasking using fs-unmask. Clarity masks common PII patterns automatically but requires custom configuration for domain-specific sensitive data. For GDPR compliance, all three require a lawful basis for processing — typically explicit consent via a cookie consent manager. Implement conditional loading that initializes the session replay script only after the user accepts analytics cookies.
Performance Impact Assessment
Session replay scripts are not free — each adds latency, JavaScript execution time, and data transfer overhead. Hotjar's tracking script is approximately 50 KB compressed and adds network requests for recording data. FullStory's script is similar in size but uses a more efficient event batching mechanism. Clarity's script is lighter at around 30 KB and uses Compression Streams to reduce the data sent to Microsoft's servers. All three use requestIdleCallback for non-critical processing to minimize main thread impact, but they do add CPU overhead during active recording — measurable as increased Interaction to Next Paint (INP) on pages with heavy user interaction. Use Lighthouse CI to measure the performance impact of these scripts before and after adding them to production.
TypeScript Integration
FullStory has the strongest TypeScript developer experience through @fullstory/browser, which ships its own TypeScript declarations. The typed user identification API requires property names to follow FullStory's naming convention — plan_str, signupDate_date, packagesTracked_int — where the suffix encodes the property type. This convention is enforced at runtime by FullStory's servers and surfaced through TypeScript string literal types. Hotjar and Clarity expose global functions through window.hj and window.clarity respectively — create typed wrappers in a lib/analytics.ts file that accepts typed parameters and calls the window function, encapsulating the type assertions in one place rather than scattering them across the codebase.
Sampling and Cost Management
For high-traffic sites, recording every session is both expensive and operationally unwieldy — a site with 100,000 daily sessions generates more recording data than any team can realistically review. Hotjar's plan-based session limits make sampling implicit. FullStory's enterprise pricing model charges per session, making sampling configuration critical for cost management — configure sampling rules to record 100% of sessions showing frustration signals (rage clicks, error clicks) and 10% of normal sessions. Clarity records all sessions for free, which sounds ideal but creates a storage and review burden — use Clarity's filtering interface to focus on sessions with specific events rather than browsing the full recording set. For conversion-critical flows like checkout and signup, consider 100% recording regardless of cost, since the insight value is highest.
Integration with Product Analytics Platforms
Session replay tools are most valuable when integrated with your product analytics platform. FullStory's integration with Amplitude and Mixpanel enables navigating from a funnel drop-off event in your analytics tool directly to a session recording of a user who dropped off — eliminating the gap between "what happened" (analytics) and "why it happened" (session replay). Hotjar integrates with GA4 and HubSpot, enabling CRM-level segmentation of recordings. Clarity integrates natively with Google Analytics 4, surfacing recording counts and frustration signal aggregates directly in the GA4 interface. This integration tier is where the "free" tools like Clarity show their limitations — the absence of an API means cross-platform correlation requires manual effort rather than automated linking.
Integrating Session Replay with Bug Tracking
The most valuable use of session replay data is correlating user-visible errors with the exact interaction sequence that caused them. FullStory's error tracking integration automatically links JavaScript exceptions (captured from window.onerror and window.onunhandledrejection) to the active session recording, so every Sentry or Datadog error event can include a direct link to the FullStory session replay showing the moments before and during the error. Hotjar's integration with error tracking tools is less direct — you can add custom events to the Hotjar timeline at the point of error (Hotjar.event('error_occurred')) and then filter recordings by that event to find error sessions. Clarity integrates with Azure Application Insights for error correlation, which is a natural pairing for Microsoft Azure-hosted applications. For all three tools, implement a consistent error event tagging strategy that marks sessions containing errors, rage clicks, and abandoned forms — this transforms session replay from a general exploration tool into a targeted debugging and UX improvement workflow.
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.
Compare analytics and developer tooling on PkgPulse →
See also: AVA vs Jest and PostHog vs Mixpanel vs Amplitude, Plausible vs Umami vs Fathom (2026).