Skip to content

Server Side Node

Akayashuu edited this page Sep 27, 2026 · 5 revisions

Server-side (Node)

Since 0.9.0, @vskstudio/takt-core ships a Node entry, @vskstudio/takt-core/server, that sends pageviews and events server to server (S2S). It covers what the browser never sees: visitors without JavaScript, crawlers, a payment webhook or a queued job.

The client applies the same payload rules as the browser SDK (event name, props, revenue and URL scrubbing go through the same code). It has no dependencies and relies on the native fetch of Node 18+.

npm install @vskstudio/takt-core

Never import @vskstudio/takt-core/server in code shipped to the browser: the API key would be exposed. In the browser, keep init() or a framework wrapper.

createServerTakt(options)

Create the client once, when the server starts, then reuse it:

import { createServerTakt } from '@vskstudio/takt-core/server'

export const takt = createServerTakt({
  domain: 'example.com',
  apiKey: process.env.TAKT_API_KEY
})
Option Type Default Role
domain string required Site identifier, identical to the domain of the site the API key is bound to. When empty, createServerTakt() throws.
apiKey string none Site API key carrying the events:write permission, sent as Authorization: Bearer
endpoint string https://taktlytics.com/api/event Full ingestion URL. Wins over scriptOrigin.
scriptOrigin string https://taktlytics.com Origin to derive the endpoint from (the origin followed by /api/event), for a first-party proxy
trackQuery boolean false Keeps the query string and hash of url and referrer
queryParams string[] none Allowlist of query parameters to keep
scrubUrl (url: string) => string none Custom URL scrubbing, takes precedence over trackQuery / queryParams
redactRoutes string[] none Route patterns sent in place of the real path of url and of a same-origin referrer, as in the browser (since 0.10.0)
strict boolean false Throws on a network error or on any answer other than 202
fetch typeof fetch global fetch fetch implementation to use (tests, custom HTTP agent)

endpoint and scriptOrigin resolve exactly as in createTakt(): endpoint is a full URL, not an origin.

pageview(options) and event(name, options)

Both methods return a Promise<void>:

interface ServerVisitor {
  ip?: string
  userAgent?: string
}

pageview(options?: {
  url?: string
  referrer?: string
  route?: string
  visitor?: ServerVisitor
}): Promise<void>

event(name: string, options?: {
  props?: Record<string, unknown>
  revenue?: { amount: string; currency: string }
  url?: string
  referrer?: string
  route?: string
  visitor?: ServerVisitor
}): Promise<void>

A server-rendered pageview, with Express:

import express from 'express'
import { takt } from './takt'

const app = express()

app.get('/pricing', async (req, res) => {
  await takt.pageview({
    url: `https://example.com${req.originalUrl}`,
    referrer: req.get('referer'),
    visitor: { ip: req.ip, userAgent: req.get('user-agent') }
  })
  res.render('pricing')
})

A purchase confirmed by a payment webhook, outside any visitor request:

await takt.event('Purchase', {
  props: { plan: 'pro' },
  revenue: { amount: '29.00', currency: 'EUR' },
  url: 'https://example.com/thanks'
})

What the client does for you

  • Authentication: apiKey is sent as Authorization: Bearer. The key must carry the events:write permission and be bound to the site whose domain matches domain exactly, otherwise the ingest answers 401. Without a key, the send takes the same path as a browser, and it is silently dropped if the site requires an API key.
  • Visitor: visitor.ip is sent as X-Forwarded-For and visitor.userAgent as User-Agent. Line breaks (CR/LF) are stripped from header values. Because the request is authenticated by an events:write key, the ingest derives the visitor and country from the forwarded IP (X-Takt-Client-IP, otherwise the first X-Forwarded-For entry), not from your application server's IP.
  • URL: without url, the event is attached to the site home (https://example.com/ for domain: 'example.com'), since the ingest rejects an event without an absolute URL. By default, the query string and hash of url and referrer are removed; trackQuery, queryParams and scrubUrl tune this as in the browser.
  • Payload: props are coerced to strings and capped (30 keys, 64-character keys, 1024-character values). A malformed revenue is dropped with a warning, and the event is still sent.
  • Names: an empty name or the reserved pageview name passed to event() always throws, even without strict. Use pageview() for pageviews.
  • Errors: by default, a network error or an answer other than 202 is swallowed, since analytics must never break your server. With strict: true, the promise rejects (handy in tests or in a job that should retry).

A 202 means "accepted", not "recorded": the ingest still filters crawlers.

Route redaction

The query and hash of url are removed, but the path is sent as is. Since 0.10.0, two levers keep a secret path segment out of analytics:

const takt = createServerTakt({
  domain: 'example.com',
  apiKey: process.env.TAKT_API_KEY,
  redactRoutes: ['/verify/[token]', '/reset/:code']
})

await takt.pageview({ url: 'https://example.com/verify/abc123' })

app.get('/orders/:id', async (req, res) => {
  await takt.pageview({
    url: `https://example.com${req.originalUrl}`,
    route: req.route.path
  })
  res.render('order')
})
  • redactRoutes takes the same patterns as the browser SDK ([param], [[optional]], [...rest], :param, :param?, *). The first call above sends https://example.com/verify/[token].
  • route on pageview() or event() replaces the path of that call only, typically with the route your server framework matched (/orders/:id). A same-origin referrer is then reduced to the site origin. Without route, redactRoutes applies.

See Privacy for the pattern syntax and the per-page stats trade-off.


Source: github.com/taktlytics/takt-core · npm @vskstudio/takt-core

Takt — @vskstudio/takt-core


React · Vue · Svelte wrappers: see the docs site.

Clone this wiki locally