Use when adding analytics to a Stacks application - configuring Fathom, Plausible, Google Analytics or self-hosted analytics, generating tracking scripts, privacy-friendly analytics setup, or the analytics configuration. Covers @stacksjs/analytics and config/analytics.ts.
Install
npx skillscat add stacksjs/stacks/stacks-analytics Install via the SkillsCat registry.
Stacks Analytics
Analytics drivers: AnalyticsHQ, Fathom, Plausible, Google Analytics and self-hosted.
Key Paths
- Core package:
storage/framework/core/analytics/src/ - Drivers:
storage/framework/core/analytics/src/drivers/ - Registry (dispatch on the configured driver):
storage/framework/core/analytics/src/registry.ts - Configuration:
config/analytics.ts
Driver registry
Read config/analytics.ts and render the driver it names. This is the entry
point; reach for an individual driver only when you need one specific script
regardless of config.
import { generateAnalyticsScript, getAnalyticsHead } from '@stacksjs/analytics'
import { analytics } from '@stacksjs/config'
// HTML you can drop into a layout <head>
const script = generateAnalyticsScript(analytics)
// Or the [tag, attributes] pairs a docs `head` array takes
const head = getAnalyticsHead(analytics)The registry is loud on purpose. A driver value with no implementation throws,
and so does a selected third-party driver missing its config (the message names
the exact key to set). An app with no driver defaults to AnalyticsHQ. It emits
nothing until drivers.analyticshq.siteId is set.
Nothing injects the script for you - a framework that silently posted pageviews
to a third party would be the wrong default. Render it yourself in the layout
that should carry it.
Drivers
Fathom
Hosted tracker script. Requires a Fathom account; compliance depends on the complete application and provider setup.scriptUrl points the tag at your own origin so a content blocker does not drop it.
import { generateFathomScript } from '@stacksjs/analytics'
generateFathomScript({
siteId: 'ABCDEFGH',
honorDnt: true, // -> data-honor-dnt="true"
spa: true, // -> data-spa="auto"
scriptUrl: undefined, // defaults to Fathom's CDN
})Plausible
Cookie-free, hosted or self-hosted. hashMode and trackLocalhost pick the
script variant (script.hash.js, script.local.js); scriptUrl overrides the
computed URL entirely, which is how you point at a self-hosted install.
import { generatePlausibleScript } from '@stacksjs/analytics'
generatePlausibleScript({ domain: 'example.com', hashMode: true })Google Analytics
GA4 via gtag.js. Emits the loader plus the inline js / config bootstrap;debug: true sets debug_mode so the property shows up in DebugView.
import { generateGoogleAnalyticsScript } from '@stacksjs/analytics'
generateGoogleAnalyticsScript({ trackingId: 'G-XXXXXXXXXX', debug: false })Self-Hosted
A small first-party tracker that POSTs to your own endpoint.
import { generateSelfHostedScript, getSelfHostedAnalyticsHead } from '@stacksjs/analytics'
const script = generateSelfHostedScript({
siteId: 'ABCDEF',
apiEndpoint: 'https://analytics.myapp.com/api/event',
honorDnt: true, // respect Do Not Track
trackHashChanges: false, // track hash URL changes
trackOutboundLinks: true // track external link clicks
})
const headConfig = getSelfHostedAnalyticsHead({
siteId: 'ABCDEF',
apiEndpoint: 'https://analytics.myapp.com/api/event'
})Driver config interfaces
interface FathomConfig {
siteId: string // Fathom site ID
scriptUrl?: string // serve the script first-party
honorDnt?: boolean // respect Do Not Track
spa?: boolean // re-record pageviews on client routing
}
interface PlausibleConfig {
domain: string // the site's domain
scriptUrl?: string // self-hosted Plausible or a proxy
trackLocalhost?: boolean // keep localhost pageviews
hashMode?: boolean // count hash routes
}
interface GoogleAnalyticsConfig {
trackingId: string // GA4 measurement ID
debug?: boolean // debug_mode -> DebugView
}
interface AnalyticsHqConfig {
siteId: string // the data-site id AnalyticsHQ minted
scriptUrl?: string // self-hosted instance or verified custom domain
respectDnt?: boolean // false counts DNT/GPC visitors (skipped by default)
vitals?: boolean // false stops Core Web Vitals beacons
}
interface SelfHostedConfig {
siteId: string // unique site identifier
apiEndpoint: string // analytics API URL
honorDnt?: boolean // respect Do Not Track header
trackHashChanges?: boolean // track SPA hash navigation
trackOutboundLinks?: boolean // track clicks to external sites
}config/analytics.ts
{
driver: 'analyticshq', // 'analyticshq' | 'fathom' | 'plausible' | 'google-analytics' | 'self-hosted'
drivers: {
analyticshq: { siteId: '' }, // empty means no tracking tags
googleAnalytics: { trackingId: '' },
fathom: { siteId: '' },
plausible: { domain: '' },
selfHosted: { siteId: '', apiEndpoint: '' },
}
}First-party pageview capture
Separate from the drivers, and no script at all: capturePageviews: true has the
stx servers record page GETs into analytics_events, which is what feeds the
native /analytics/pages, /referrers and /devices dashboards.
Dashboard Integration
Analytics dashboard at /dashboard/analytics displays:
- Page views and visitor counts
- Traffic sources
- Popular pages
- Engagement metrics
Gotchas
- Selecting a driver does not inject anything - call
generateAnalyticsScript()in your layout - A misconfigured third-party driver throws rather than emitting nothing; the message names the config key
- AnalyticsHQ is the first-party default and emits nothing until its site id is set
honorDnt: truerespects browser Do Not Track settings- Attribute values are escaped, and inline script values are JS-escaped, to prevent XSS
- Fathom is a paid service - self-hosted is free but requires infrastructure
trackOutboundLinksadds click handlers to external<a>tagscapturePageviewsis server-side and independent ofdriver- AnalyticsHQ custom events need no script:
data-analyticshq-event="Ticket click"on any element, with otherdata-analyticshq-*attributes as properties
Environment and capture boundaries
enabled:false returns no tags. When environments is explicitly supplied,
getAnalyticsHead runs the shared integration gate and forwards the resolved label
to AnalyticsHQ. If environments is absent, the current registry preserves its
older ungated behavior; the shipped config explicitly lists production/staging.
Do not assume every caller gets that allowlist when constructing config itself.
First-party capturePageviews is separate from script injection and the remote
driver gate. recordPageview(req) writes qualifying GET page requests to
analytics_events; isPageviewRequest filters assets/API/internal paths and obvious
bots/monitors. classifyAgent/referrerHost are coarse helpers, not unique visitor
identification. Capture failures are swallowed so a statistic cannot break a
page. Apply the table/model migration before relying on the dashboard data.
Source: storage/framework/core/analytics/src/registry.ts, capture.ts and
drivers/. Tests: environment-gate.test.ts, capture.test.ts and drivers.test.ts.
A configured script does not certify analytics consent/privacy compliance or
prove a provider received an event. Read stacks-env for the shared gate.