em-content-insights
v1.2.0
Published
Privacy-first post analytics for EmDash CMS. Per-post views, read rate, time on page, and referrer tracking — all self-hosted.
Maintainers
Readme
em-content-insights
Privacy-first post analytics for EmDash CMS. Know how your content performs without compromising your readers' privacy.
No cookies. No fingerprinting. No third-party services. All data stays in your EmDash instance.
What you get
- Views per post with unique visitor approximation
- Read rate based on real scroll depth and attention time
- Scroll depth milestones (25%, 50%, 75%, 100%)
- Engaged views (10s+ active attention with interaction)
- Recirculation tracking (clicks to internal links)
- Time on page via active attention tracking (visible + focused, 60s inactivity timeout)
- Referrer breakdown showing where your traffic comes from
- Trend indicators comparing current period vs previous
- Admin dashboard widget with top posts at a glance
- Full analytics page with timeseries charts, scroll depth funnel, top posts table, and referrer pie chart
Quick start
1. Install
npm install em-content-insights2. Register the plugin
// astro.config.mjs
import { postAnalytics } from "em-content-insights";
export default defineConfig({
integrations: [
emdash({
plugins: [postAnalytics()],
}),
],
});3. Add the beacon to your layout
---
// src/layouts/Base.astro (or your base layout)
import BeaconScript from "em-content-insights/astro";
---
<html>
<body>
<slot />
<BeaconScript />
</body>
</html>That's it. The <BeaconScript /> component inlines a lightweight tracking script (~1.5KB) that automatically detects post content and starts tracking.
Configuration
The plugin works out of the box with zero configuration. Optionally customize the path prefix:
postAnalytics({
pathPrefix: "/blog/", // default: "/posts/"
})Or change it at runtime from the admin UI under Settings > Post Analytics > Post URL Prefix.
How it works
Tracking
- The
<BeaconScript />component renders an inline script on every page - On page load, it sends a
viewevent vianavigator.sendBeacon() - As the reader scrolls, it sends
scrollevents at 25%, 50%, 75%, and 100% depth milestones - After 10 seconds of active attention with interaction, it sends an
engagedevent - When the reader has scrolled 75%+ and spent enough time reading (estimated from content length), it sends a
readevent - If the reader clicks an internal link, it sends a
recircevent - When the reader leaves, it sends a
pingwith total active attention time
All events hit a public plugin route (POST /_emdash/api/plugins/post-analytics/track) that validates the payload, filters bots, and stores the data.
Storage
The plugin uses a hybrid storage model:
- Raw events (
eventscollection) preserve full granularity for future analysis - Daily aggregates (
daily_statscollection) power fast admin queries
The admin UI reads from daily aggregates, so dashboard performance is constant regardless of traffic volume.
Privacy
| Concern | How it's handled |
|---------|-----------------|
| Cookies | None |
| IP addresses | Hashed with a daily-rotating salt. Raw IPs are never stored. |
| Fingerprinting | None |
| Cross-page tracking | None. Each pageview is independent. |
| Do Not Track | Honored. If navigator.doNotTrack === "1", the beacon does nothing. |
| Bot filtering | User-Agent based filtering for known crawlers |
Admin UI
Dashboard widget
Shows on the EmDash admin home page:
- Total views and visitors for the last 7 days with trend indicators
- Top 5 posts ranked by views
Analytics page
Accessible from the admin sidebar under Post Insights:
- Date range selector (7 / 30 days)
- Stat cards: total views, unique visitors, read rate, avg time on page
- Timeseries chart with views, engaged views, and reads over time
- Scroll depth funnel chart
- Top posts table sortable by views, visitors, read rate, and avg time
- Referrer pie chart
- Country breakdown (when running on Cloudflare)
API routes
All routes are available at /_emdash/api/plugins/post-analytics/.
| Route | Auth | Method | Description |
|-------|------|--------|-------------|
| track | Public | POST | Receives beacon events |
| stats | Admin | GET | Aggregated stats. Params: pathname, days |
| top-posts | Admin | GET | Ranked posts. Params: days, limit |
Requirements
- EmDash
^0.1.0 - Astro
^6.0.0 - No external services or API keys required
Development
git clone https://github.com/facuzarate04/em-content-insights.git
cd em-content-insights
npm install
npm testRunning tests
npm test # single run
npm run test:watch # watch modeProject structure
src/
index.ts # Plugin descriptor (build time)
sandbox-entry.ts # Plugin definition: hooks, routes, admin UI (runtime)
beacon.ts # Client-side tracking script generator
astro/
BeaconScript.astro # Drop-in Astro component for the tracking beacon
__tests__/ # Vitest test suiteCapabilities
| Capability | Purpose |
|-----------|---------|
| read:content | Resolve post titles for admin display |
| network:fetch | Reserved for future license validation |
| page:inject | Auto-inject beacon via page:fragments (when supported by EmDash runtime) |
