Overview
Measure how users engage with your content. Track reading time, scroll depth, video views, and content interactions to optimize your content strategy.Article & Blog Post Tracking
Track comprehensive reading analytics for blog posts and articles.import { useMentiqAnalytics, TrackView, TrackClick } from "mentiq-sdk";
import { useEffect, useState, useRef } from "react";
function BlogPost({ post }) {
const { track, page } = useMentiqAnalytics();
const [readingTime, setReadingTime] = useState(0);
const [scrollDepth, setScrollDepth] = useState(0);
const startTimeRef = useRef(Date.now());
useEffect(() => {
// Track page view
page({
title: post.title,
path: `/blog/${post.slug}`,
content_type: "article",
});
// Track article view
track("article_viewed", {
article_id: post.id,
article_title: post.title,
category: post.category,
author: post.author,
published_date: post.publishedAt,
word_count: post.wordCount,
estimated_reading_time: Math.ceil(post.wordCount / 200), // 200 words/min
});
}, [post, page, track]);
useEffect(() => {
// Track reading time
const interval = setInterval(() => {
const elapsed = Math.floor((Date.now() - startTimeRef.current) / 1000);
setReadingTime(elapsed);
// Track reading milestones
if ([30, 60, 120, 180].includes(elapsed)) {
track("reading_milestone", {
article_id: post.id,
seconds: elapsed,
scroll_depth: scrollDepth,
});
}
}, 1000);
return () => clearInterval(interval);
}, [post.id, scrollDepth, track]);
useEffect(() => {
// Track scroll depth
const handleScroll = () => {
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
const scrollTop = window.scrollY;
const depth = Math.round((scrollTop / (documentHeight - windowHeight)) * 100);
setScrollDepth(depth);
// Track scroll milestones
if ([25, 50, 75, 100].includes(depth) && depth > scrollDepth) {
track("article_scroll_depth", {
article_id: post.id,
depth_percent: depth,
reading_time: readingTime,
});
}
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [post.id, readingTime, scrollDepth, track]);
// Track when user leaves
useEffect(() => {
const handleBeforeUnload = () => {
track("article_exit", {
article_id: post.id,
reading_time: readingTime,
scroll_depth: scrollDepth,
completed: scrollDepth >= 90,
});
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, [post.id, readingTime, scrollDepth, track]);
const handleShare = (platform) => {
track("article_shared", {
article_id: post.id,
article_title: post.title,
platform,
reading_time: readingTime,
scroll_depth: scrollDepth,
});
};
const handleComment = () => {
track("article_comment_started", {
article_id: post.id,
reading_time: readingTime,
});
};
return (
<TrackView
event="article_content_viewed"
properties={{ article_id: post.id, title: post.title }}
threshold={0.5}
>
<article className="blog-post">
<header>
<h1>{post.title}</h1>
<div className="meta">
<span>{post.author}</span>
<span>{new Date(post.publishedAt).toLocaleDateString()}</span>
<span>{Math.ceil(post.wordCount / 200)} min read</span>
</div>
</header>
<div className="reading-progress">
<div>Reading time: {readingTime}s</div>
<div>Scroll: {scrollDepth}%</div>
</div>
<div
className="content"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
<footer>
<div className="share-buttons">
<TrackClick
event="share_button_clicked"
properties={{ platform: "twitter", article_id: post.id }}
>
<button onClick={() => handleShare("twitter")}>Share on Twitter</button>
</TrackClick>
<TrackClick
event="share_button_clicked"
properties={{ platform: "linkedin", article_id: post.id }}
>
<button onClick={() => handleShare("linkedin")}>Share on LinkedIn</button>
</TrackClick>
</div>
<div className="comments">
<TrackClick
event="comment_section_clicked"
properties={{ article_id: post.id }}
>
<button onClick={handleComment}>Leave a Comment</button>
</TrackClick>
</div>
</footer>
</article>
</TrackView>
);
}
Video Content Tracking
Track video views, engagement, and completion rates.import { useMentiqAnalytics } from "mentiq-sdk";
import { useRef, useEffect } from "react";
function VideoPlayer({ video }) {
const { track } = useMentiqAnalytics();
const videoRef = useRef<HTMLVideoElement>(null);
const trackingRef = useRef({
started: false,
quartiles: [false, false, false, false],
lastPosition: 0,
});
useEffect(() => {
const videoElement = videoRef.current;
if (!videoElement) return;
const handlePlay = () => {
if (!trackingRef.current.started) {
track("video_started", {
video_id: video.id,
video_title: video.title,
video_duration: videoElement.duration,
video_type: video.type,
});
trackingRef.current.started = true;
} else {
track("video_resumed", {
video_id: video.id,
current_time: videoElement.currentTime,
});
}
};
const handlePause = () => {
track("video_paused", {
video_id: video.id,
current_time: videoElement.currentTime,
percent_complete: (videoElement.currentTime / videoElement.duration) * 100,
});
};
const handleTimeUpdate = () => {
const duration = videoElement.duration;
const currentTime = videoElement.currentTime;
const percentComplete = (currentTime / duration) * 100;
// Track quartiles (25%, 50%, 75%, 100%)
const quartile = Math.floor(percentComplete / 25);
if (quartile > 0 && quartile <= 4 && !trackingRef.current.quartiles[quartile - 1]) {
trackingRef.current.quartiles[quartile - 1] = true;
track("video_progress", {
video_id: video.id,
quartile: quartile * 25,
current_time: currentTime,
});
}
};
const handleEnded = () => {
track("video_completed", {
video_id: video.id,
video_title: video.title,
total_watch_time: videoElement.duration,
});
};
const handleSeeked = () => {
track("video_seeked", {
video_id: video.id,
from_time: trackingRef.current.lastPosition,
to_time: videoElement.currentTime,
});
};
videoElement.addEventListener("play", handlePlay);
videoElement.addEventListener("pause", handlePause);
videoElement.addEventListener("timeupdate", handleTimeUpdate);
videoElement.addEventListener("ended", handleEnded);
videoElement.addEventListener("seeked", handleSeeked);
return () => {
videoElement.removeEventListener("play", handlePlay);
videoElement.removeEventListener("pause", handlePause);
videoElement.removeEventListener("timeupdate", handleTimeUpdate);
videoElement.removeEventListener("ended", handleEnded);
videoElement.removeEventListener("seeked", handleSeeked);
};
}, [video, track]);
const handleQualityChange = (quality) => {
track("video_quality_changed", {
video_id: video.id,
quality,
});
};
const handleFullscreen = () => {
track("video_fullscreen", {
video_id: video.id,
current_time: videoRef.current?.currentTime,
});
};
return (
<div className="video-player">
<video
ref={videoRef}
src={video.url}
controls
onDoubleClick={handleFullscreen}
>
Your browser does not support the video tag.
</video>
<div className="video-info">
<h3>{video.title}</h3>
<p>{video.description}</p>
</div>
</div>
);
}
Document & PDF Tracking
Track document downloads and views.import { useMentiqAnalytics, TrackClick } from "mentiq-sdk";
function DocumentLibrary({ documents }) {
const { track } = useMentiqAnalytics();
const handleDownload = (doc) => {
track("document_downloaded", {
document_id: doc.id,
document_title: doc.title,
document_type: doc.type,
file_size: doc.size,
category: doc.category,
});
// Trigger download
window.open(doc.url, "_blank");
};
const handlePreview = (doc) => {
track("document_previewed", {
document_id: doc.id,
document_title: doc.title,
document_type: doc.type,
});
};
return (
<div className="document-library">
<h2>Resource Library</h2>
<div className="documents-grid">
{documents.map((doc) => (
<div key={doc.id} className="document-card">
<div className="doc-icon">{doc.type.toUpperCase()}</div>
<h3>{doc.title}</h3>
<p>{doc.description}</p>
<div className="doc-meta">
<span>{formatFileSize(doc.size)}</span>
<span>{doc.downloads} downloads</span>
</div>
<div className="doc-actions">
<TrackClick
event="document_preview_clicked"
properties={{ document_id: doc.id, type: doc.type }}
>
<button onClick={() => handlePreview(doc)}>Preview</button>
</TrackClick>
<TrackClick
event="document_download_clicked"
properties={{ document_id: doc.id, type: doc.type }}
>
<button onClick={() => handleDownload(doc)}>Download</button>
</TrackClick>
</div>
</div>
))}
</div>
</div>
);
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
Newsletter & Email Tracking
Track newsletter signups and engagement.import { useMentiqAnalytics } from "mentiq-sdk";
import { useState } from "react";
function NewsletterForm({ source }) {
const { track } = useMentiqAnalytics();
const [email, setEmail] = useState("");
const [subscribed, setSubscribed] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
track("newsletter_signup_attempted", {
email_domain: email.split("@")[1] || "unknown",
source: source,
});
try {
await subscribeToNewsletter(email);
track("newsletter_signup_completed", {
email_domain: email.split("@")[1] || "unknown",
signup_source: source,
});
setSubscribed(true);
} catch (error) {
track("newsletter_signup_failed", {
error: error.message,
source: source,
});
}
};
const handleInputFocus = () => {
track("newsletter_input_focused", {
source: source,
});
};
if (subscribed) {
return (
<div className="success-message">
✅ Thank you for subscribing!
</div>
);
}
return (
<form onSubmit={handleSubmit} className="newsletter-form">
<h3>Subscribe to our newsletter</h3>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
onFocus={handleInputFocus}
placeholder="Enter your email"
required
/>
<button type="submit">Subscribe</button>
</form>
);
}
Content Search Tracking
Track search behavior and results.import { useMentiqAnalytics } from "mentiq-sdk";
import { useState, useEffect } from "react";
function ContentSearch() {
const { track } = useMentiqAnalytics();
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const [searching, setSearching] = useState(false);
const handleSearch = async (searchQuery) => {
if (!searchQuery.trim()) return;
setSearching(true);
track("content_search_initiated", {
query: searchQuery,
query_length: searchQuery.length,
});
try {
const searchResults = await searchContent(searchQuery);
setResults(searchResults);
track("content_search_completed", {
query: searchQuery,
results_count: searchResults.length,
has_results: searchResults.length > 0,
search_time_ms: Date.now() - startTime,
});
} catch (error) {
track("content_search_failed", {
query: searchQuery,
error: error.message,
});
} finally {
setSearching(false);
}
};
const handleResultClick = (result, position) => {
track("search_result_clicked", {
query: query,
result_id: result.id,
result_title: result.title,
result_type: result.type,
position: position,
total_results: results.length,
});
};
return (
<div className="content-search">
<input
type="search"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && handleSearch(query)}
placeholder="Search content..."
/>
<button onClick={() => handleSearch(query)} disabled={searching}>
{searching ? "Searching..." : "Search"}
</button>
{results.length > 0 && (
<div className="search-results">
<p>{results.length} results found</p>
{results.map((result, index) => (
<div
key={result.id}
className="result-item"
onClick={() => handleResultClick(result, index + 1)}
>
<h4>{result.title}</h4>
<p>{result.excerpt}</p>
</div>
))}
</div>
)}
</div>
);
}
Reading Progress Indicator
Show visual reading progress for better engagement.import { useState, useEffect } from "react";
import { useMentiqAnalytics } from "mentiq-sdk";
function ReadingProgressBar({ articleId }) {
const { track } = useMentiqAnalytics();
const [progress, setProgress] = useState(0);
useEffect(() => {
const handleScroll = () => {
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
const scrollTop = window.scrollY;
const scrollPercent = (scrollTop / (documentHeight - windowHeight)) * 100;
setProgress(Math.min(100, Math.max(0, scrollPercent)));
};
window.addEventListener("scroll", handleScroll);
handleScroll(); // Initial calculation
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useEffect(() => {
// Track completion when user reaches 90%
if (progress >= 90) {
track("article_completed", {
article_id: articleId,
completion_percent: progress,
});
}
}, [progress, articleId, track]);
return (
<div className="reading-progress-bar" style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "4px",
backgroundColor: "#e0e0e0",
zIndex: 9999,
}}>
<div style={{
width: `${progress}%`,
height: "100%",
backgroundColor: "#007bff",
transition: "width 0.1s ease",
}} />
</div>
);
}
Key Content Metrics
Engagement Rate
Time spent vs. estimated reading time
Scroll Depth
How far users scroll through content
Completion Rate
Percentage of users who finish content
Social Shares
Content shared across platforms
Content Events Reference
Article Events
Article Events
article_viewed- Article page loadedreading_milestone- Time milestones (30s, 60s, etc.)article_scroll_depth- Scroll milestones (25%, 50%, etc.)article_completed- User reached endarticle_shared- Content sharedarticle_comment_started- User began commenting
Video Events
Video Events
video_started- Video playback startedvideo_progress- Quartile milestonesvideo_paused- Video pausedvideo_completed- Video finishedvideo_seeked- User skipped forward/backward
Document Events
Document Events
document_previewed- Document preview openeddocument_downloaded- File downloadeddocument_download_clicked- Download button clicked
Search Events
Search Events
content_search_initiated- Search startedcontent_search_completed- Results returnedsearch_result_clicked- User clicked result
Best Practices
Track exit intent: Capture reading time and scroll depth when users leave to understand if they completed the content.
Don’t over-track: Avoid tracking every single scroll event. Use milestones (25%, 50%, 75%, 100%) to reduce event volume.
Next Steps
Session Recording
Watch how users interact with content
Heatmaps
Visualize clicks and scroll patterns