Skip to main content

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_viewed - Article page loaded
  • reading_milestone - Time milestones (30s, 60s, etc.)
  • article_scroll_depth - Scroll milestones (25%, 50%, etc.)
  • article_completed - User reached end
  • article_shared - Content shared
  • article_comment_started - User began commenting
  • video_started - Video playback started
  • video_progress - Quartile milestones
  • video_paused - Video paused
  • video_completed - Video finished
  • video_seeked - User skipped forward/backward
  • document_previewed - Document preview opened
  • document_downloaded - File downloaded
  • document_download_clicked - Download button clicked
  • content_search_initiated - Search started
  • content_search_completed - Results returned
  • search_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

Build docs developers (and LLMs) love