@vulcantech/analytics
v0.1.1
Published
Lightweight first-party analytics tracker for Next.js — page views, custom events, form conversions. No cookies, no fingerprinting, <1KB gzipped.
Maintainers
Readme
@vulcantech/analytics
Lightweight first-party analytics for VulcanTech consumer sites — page views, custom events, and form/phone/email conversion tracking. No cookies, no fingerprinting, <1KB gzipped. Events are proxied through the site's own domain to vulcantech-cms.
Also provides CMS-driven third-party tracking tags (CMSTags): GA4 / Google Ads / Meta Pixel IDs are configured in the CMS and injected server-side, so consumer sites get "codeless" tag setup with no hand-rolled gtag/fbq wiring.
Exports:
AnalyticsTracker— client component; tracks page views on route change.trackEvent,trackFormView,trackFormSubmit,trackPhoneClick,trackEmailClick,track404,trackPageView— imperative trackers.@vulcantech/analytics/route— App Router proxy handler (POST,OPTIONS) for/api/a.@vulcantech/analytics/tags—CMSTagsserver component +fetchTrackingTags+ theTrackingTagstype.
Integration
Wire analytics into a consumer site (Next.js App Router):
1. Add the dependency:
npm install @vulcantech/analytics(Inside the VulcanTech monorepo, use the workspace wiring in MONOREPO.md instead.)
2. Set the env — see Required env below.
3. Mount AnalyticsTracker in the root layout (src/app/layout.tsx) — tracks page views on every route change:
import { AnalyticsTracker } from "@vulcantech/analytics";
// <body>
// <AnalyticsTracker />
// {children}
// </body>4. Re-export the proxy route src/app/api/a/route.ts — sends events to the CMS via the site's own domain (fails silently, never blocks the page):
export { POST, OPTIONS } from "@vulcantech/analytics/route";5. Mount CMSTags in the root layout (src/app/layout.tsx) — injects the project's GA4 / Google Ads / Meta Pixel tags from the CMS. It is an async server component; render it directly in <body>:
import { CMSTags } from "@vulcantech/analytics/tags";
// <body>
// <CMSTags />
// <AnalyticsTracker />
// {children}
// </body>CMSTags fetches once per ISR window (revalidate: 60, tag tracking-tags:{projectId}) and renders null when nothing is configured. It replaces any hand-rolled <GoogleAnalytics>, Google Ads gtag Script, and Meta Pixel fbq snippets — remove those from the layout when adopting it.
Required env
VULCANTECH_PROJECT_ID, VULCANTECH_API_KEY (required) · VULCANTECH_CMS_URL (optional, defaults to https://cms.vulcantech.io).
The API key must be granted the analytics:read scope in the CMS for CMSTags to resolve tag IDs (the /api/analytics/config endpoint). Without it (or with tags disabled) the CMS returns null and only the env fallbacks apply.
Env fallbacks (tags)
When the CMS omits a field (unreachable, unconfigured, or key lacks scope), each tag ID falls back to a public env var — CMS value wins when present:
| Tag | CMS field | Env fallback |
|---|---|---|
| GA4 | ga4Id | NEXT_PUBLIC_GA_ID |
| Google Ads | googleAdsId | NEXT_PUBLIC_GOOGLE_ADS_ID |
| Meta Pixel | metaPixelId | NEXT_PUBLIC_META_PIXEL_ID |
If all three end up empty, CMSTags renders nothing.
Per-app values
None beyond env — analytics and tags are fully config-driven from the CMS.
Verifying tags are live
- Use the CMS Integrations → Tracking Tags → Live Status panel: it fetches the deployed site and confirms each enabled tag ID is actually present in the HTML (green "Live" = the whole chain works). "Check again" re-probes.
- In-browser, verify via the Console, not by eyeballing the Elements DOM:
Object.keys(window.google_tag_manager) // includes both G-… and AW-… window.dataLayer.filter(a => a[0] === "config")
Notes
- The tracker skips all sends on localhost/dev hostnames; test conversions against a deployed URL.
CMSTagsnever throws — tag resolution failures are swallowed so a page build can never break on the CMS being down.- ISR invalidation: the CMS purges the
tracking-tags:{projectId}tag when the project's analytics config changes. No consumer redeploy is needed for CMS value changes — ISR picks them up within ~60s (or instantly if the site is registered as a revalidation consumer). - Google Ads shares the GA4 loader. There is one
gtag.jsrequest (keyed to the GA4 ID); the Ads ID rides on it via an inlinegtag('config','AW-…'). Don't expect a separategtag/js?id=AW-…request or a standalone DOM element for it. - Ad blockers strip
googletagmanager.com/facebook.netclient-side, so tags can look absent during manual inspection — verify in a clean browser.
