Analytics — Matomo Auto-Provisioning

The platform runs a Matomo instance at engage.insureco.io. Declare one line in your catalog and the builder provisions a per-environment Matomo site and injects the tracking config into your pod. Add the @insureco/analytics component and you're tracking — no manual site creation, no hardcoded site IDs.

Quick start

  1. Declare analytics in catalog-info.yaml (frontend services only):
spec:
  analytics:
    enabled: true
  1. Install the SDK and add .npmrc:
npm install @insureco/analytics
# .npmrc
@insureco:registry=https://git.insureco.io/api/packages/insureco/npm/
  1. Drop the component into your root layout (a Server Component):
// app/layout.tsx
import { MatomoAnalytics } from '@insureco/analytics'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}
        <MatomoAnalytics />
      </body>
    </html>
  )
}
  1. tawa deploy --prod. Done.

What the builder does

On deploy, for nextjs / static services with spec.analytics.enabled: true:

  1. Resolves the service's public URL for the environment (custom domain if verified, else {service}.{env}.tawa.pro / {service}.tawa.pro).
  2. Finds or creates a Matomo site for that URL (idempotent — keyed by a stored record per environment, with a URL-lookup fallback so redeploys never create duplicates).
  3. Injects two runtime env vars into the pod:
VarExampleNotes
MATOMO_URLhttps://engage.insureco.ioMatomo instance base URL
MATOMO_SITE_ID218The provisioned site ID — public by design

Per-environment sites

Each environment gets its own Matomo site, because the site's main_url is the environment-specific hostname. Prod traffic, sandbox traffic, and UAT traffic are recorded under separate site IDs and never mix.

Frontend-only

Analytics is provisioned only for nextjs and static frameworks. A JS page tracker is meaningless for a backend API, so express / hono / fastify / worker services that declare spec.analytics get a build warning and no injection. (Server-side event tracking via Matomo's HTTP Tracking API is a possible future path, not covered here.)

Why runtime injection (not NEXT_PUBLIC_*)

MATOMO_SITE_ID is not a NEXT_PUBLIC_* variable. NEXT_PUBLIC_* values are inlined into the client bundle at Docker build time — but the builder doesn't know your site ID until the provisioning step, which runs after the image is built. The @insureco/analytics component sidesteps this by reading the value in a Server Component at request time and rendering it into the HTML. This is the same runtime-vs-build-time rule described in nextjs.md.

Custom events, goals, search

Client-side helpers — call them from 'use client' components:

'use client'
import { trackEvent, trackGoal, trackSiteSearch } from '@insureco/analytics'

trackEvent('Quote', 'Started', 'auto')
trackGoal(3, 49.99)                       // goal id 3, $49.99 revenue
trackSiteSearch('flood coverage', 'policies', 12)

Static (non-React) sites

static sites can't render the component. Build the snippet from the injected env and paste it into <head> at build time:

import { buildMatomoSnippet } from '@insureco/analytics'
const snippet = buildMatomoSnippet({ url: process.env.MATOMO_URL!, siteId: process.env.MATOMO_SITE_ID! })

Security

  • The Matomo admin token is a superuser credential. It lives ONLY on the builder (MATOMO_ADMIN_TOKEN) and is used solely to create sites. It is never injected into a service pod.
  • MATOMO_SITE_ID is safe to expose — site IDs appear in client-side tracking JS by design.

Common mistakes

  • Putting <MatomoAnalytics /> in a 'use client' file — it must be a Server Component so it can read process.env at runtime. Place it in app/layout.tsx.
  • Expecting analytics on a backend service — frontend-only.
  • Adding a NEXT_PUBLIC_MATOMO_SITE_ID config var by hand — unnecessary and wrong; the value is runtime-injected and read server-side.
  • Declaring spec.analytics but seeing no data locally — the env vars are only injected on deploy; getMatomoConfig() returns null in local dev, so the component renders nothing (by design).

Last updated: July 15, 2026