SDK Reference

Simplist provides server-side analytics that are privacy-friendly, adblocker-proof, and GDPR compliant. Track page views, engagement metrics, and get detailed insights.

MethodDescriptionReturns
Track a new page viewPromise<PageViewResponse>
Update an existing page viewPromise<{ success: boolean }>
Get analytics statisticsPromise<ApiResponse<AnalyticsStats>>

Initialize tracking for a new page view. Call this when a user lands on an article page.

Method Signature
async client.analytics.track(data: PageViewData): Promise<PageViewResponse>

PropertyTypeDescriptionDefault
slug*stringArticle slug (required)-
sessionIdstringUnique session identifier-
pageUrlstringFull page URL-
pageTitlestringPage title-
referrerstringReferrer URL-
utmSourcestringUTM source-
utmMediumstringUTM medium-
utmCampaignstringUTM campaign-
screenWidthnumberScreen width in pixels-
screenHeightnumberScreen height in pixels-
timeOnPagenumberTime spent in seconds-
scrollDepthnumberScroll depth (0-100%)-
exitPositionnumberExit position (0-100%)-
bouncedbooleanWhether user bounced-
eventsPageEvent[]Custom events-
fetchGeobooleanFetch geolocation from IP-

PropertyTypeDescriptionDefault
id*stringPage view ID for updates-
success*booleanWhether tracking succeeded-

const response = await client.analytics.track({
  slug: 'getting-started',
  sessionId: generateSessionId(), // Your session ID logic
  pageUrl: 'https://myblog.com/blog/getting-started',
  pageTitle: 'Getting Started',
  referrer: document.referrer,
  screenWidth: window.innerWidth,
  screenHeight: window.innerHeight,
  timeOnPage: 0,
  scrollDepth: 0,
  fetchGeo: true
})

// Store the ID for later updates
const { pageViewId, visitorId, sessionId } = response

Update an existing page view with engagement metrics. Call this periodically or when the user leaves.

Method Signature
async client.analytics.update(pageViewId: string, data: Partial<PageViewData>): Promise<{ success: boolean }>

// Update every 30 seconds
setInterval(async () => {
  await client.analytics.update(pageViewId, {
    timeOnPage: getTimeOnPage(),
    scrollDepth: getScrollDepth(),
    events: getCustomEvents()
  })
}, 30000)

// Final update on page unload
window.addEventListener('beforeunload', async () => {
  await client.analytics.update(pageViewId, {
    timeOnPage: getTimeOnPage(),
    scrollDepth: getScrollDepth(),
    exitPosition: getScrollDepth(),
    bounced: getTimeOnPage() < 5
  })
})

Retrieve analytics statistics for your project.

Method Signature
async client.analytics.getStats(options?: { days?: number }): Promise<ApiResponse<AnalyticsStats>>

PropertyTypeDescriptionDefault
daysnumberNumber of days to include30

PropertyTypeDescriptionDefault
period*{ days: number, startDate: string, endDate: string }--
summary*{ totalViews: number, uniqueVisitors: number, avgViewsPerVisitor: number }--
requestSource{ sdk: number, direct: number }Percentage breakdown-
topArticles*Array<{ articleId: string, title: string, slug: string, views: number }>--
topCountries*Array<{ country: string, views: number }>--

const { data: stats } = await client.analytics.getStats({ days: 7 })

console.log(`Period: ${stats.period.days} days`)
console.log(`Total views: ${stats.summary.totalViews}`)
console.log(`Unique visitors: ${stats.summary.uniqueVisitors}`)
console.log(`Avg views per visitor: ${stats.summary.avgViewsPerVisitor.toFixed(2)}`)

console.log('\nTop 5 Articles:')

stats.topArticles.slice(0, 5).forEach((article, i) => {
  console.log(`${i + 1}. ${article.title} (${article.views} views)`)
})

console.log('\nTop Countries:')

stats.topCountries.forEach(country => {
  console.log(`- ${country.country}: ${country.views} views`)
})

Track custom events like button clicks, video plays, or form submissions.

PropertyTypeDescriptionDefault
type*stringEvent type (e.g., 'click', 'video_play')-
dataRecord<string, any>Event-specific data-
positionnumberScroll position when event occurred-
elementstringElement identifier-
timeOffsetnumberTime since page load (ms)-

const events: PageEvent[] = [
  {
    type: 'button_click',
    element: 'cta-button',
    position: 45,
    timeOffset: 15000,
    data: { buttonText: 'Get Started' }
  },
  {
    type: 'video_play',
    element: 'intro-video',
    position: 30,
    timeOffset: 8000,
    data: { videoId: 'intro-123', duration: 120 }
  },
  {
    type: 'link_click',
    element: 'external-link',
    data: { href: 'https://example.com', text: 'Learn more' }
  }
]

await client.analytics.track({
  slug: 'my-article',
  events
})

"use client"

import { SimplistClient } from "@simplist.blog/sdk"
import { useEffect, useState } from "react"
import { v4 as uuid } from "uuid"

export function AnalyticsTracker({ slug }: { slug: string }) {
  const [pageViewId, setPageViewId] = useState<string | null>(null)
  const [startTime] = useState(Date.now())

  useEffect(() => {
    // Uses SIMPLIST_API_KEY from environment variables
    const client = new SimplistClient()

    // Get or create session ID
    const getSessionId = () => {
      let sessionId = sessionStorage.getItem('simplist_session')
      if (!sessionId) {
        sessionId = uuid()
        sessionStorage.setItem('simplist_session', sessionId)
      }
      
      return sessionId
    }

    // Initial tracking
    const initTracking = async () => {
      const response = await client.analytics.track({
        slug,
        sessionId: getSessionId(),
        pageUrl: window.location.href,
        pageTitle: document.title,
        referrer: document.referrer,
        screenWidth: window.innerWidth,
        screenHeight: window.innerHeight,
        timeOnPage: 0,
        scrollDepth: 0,
        fetchGeo: true
      })
      
      setPageViewId(response.id)
    }

    initTracking()

    // Update metrics every 30 seconds
    const updateInterval = setInterval(async () => {
      if (!pageViewId) return

      const timeOnPage = Math.floor((Date.now() - startTime) / 1000)
      const scrollDepth = Math.round(
        (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100
      )

      await client.analytics.update(pageViewId, {
        timeOnPage,
        scrollDepth
      })
    }, 30000)

    // Final update on unmount
    return () => {
      clearInterval(updateInterval)

      if (pageViewId) {
        const timeOnPage = Math.floor((Date.now() - startTime) / 1000)
        const scrollDepth = Math.round(
          (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100
        )

        client.analytics.update(pageViewId, {
          timeOnPage,
          scrollDepth,
          exitPosition: scrollDepth,
          bounced: timeOnPage < 5
        })
      }
    }
  }, [slug, pageViewId, startTime])

  return null
}

These SDK methods use the following API endpoints:

See the REST API documentation for direct HTTP access.

  • No cookies: All tracking is server-side
  • Anonymous: No personal data collected
  • IP-based geolocation: Optional and anonymized
  • GDPR compliant: Data retention policies in place
  • Adblocker-proof: Server-side tracking works everywhere

Quick Help

Use useEffect to call track() on mount, or create a Server Action wrapper. The SDK is server-side only, but you can send data from the client to a server endpoint that calls the SDK.
Only call update() when the user leaves the page to send final metrics (time on page, scroll depth). Call track() once on page load to create the initial pageview.
Yes! Server-side tracking in Next.js Server Components works without client JavaScript. Analytics are captured during SSR, making them reliable and ad-blocker-proof.

Command Palette

Search for a command to run...