- name
- stacks-analytics
- description
- 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.
- license
- MIT
- compatibility
- Bun >= 1.3.0, TypeScript
- allowed-tools
- Read Edit Write Bash Grep Glob
# 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.
```typescript
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.
```typescript
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.
```typescript
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.
```typescript
import { generateGoogleAnalyticsScript } from '@stacksjs/analytics'
generateGoogleAnalyticsScript({ trackingId: 'G-XXXXXXXXXX', debug: false })
```
### Self-Hosted
A small first-party tracker that POSTs to your own endpoint.
```typescript
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
```typescript
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
```typescript
{
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: true` respects 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
- `trackOutboundLinks` adds click handlers to external `<a>` tags
- `capturePageviews` is server-side and independent of `driver`
- AnalyticsHQ custom events need no script: `data-analyticshq-event="Ticket click"` on any element, with other `data-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.
GitHub에서 보기