Repository navigation
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-coreNever import
@vskstudio/takt-core/serverin code shipped to the browser: the API key would be exposed. In the browser, keepinit()or a framework wrapper.
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.
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'
})-
Authentication:
apiKeyis sent asAuthorization: Bearer. The key must carry theevents:writepermission and be bound to the site whose domain matchesdomainexactly, otherwise the ingest answers401. 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.ipis sent asX-Forwarded-Forandvisitor.userAgentasUser-Agent. Line breaks (CR/LF) are stripped from header values. Because the request is authenticated by anevents:writekey, the ingest derives the visitor and country from the forwarded IP (X-Takt-Client-IP, otherwise the firstX-Forwarded-Forentry), not from your application server's IP. -
URL: without
url, the event is attached to the site home (https://example.com/fordomain: 'example.com'), since the ingest rejects an event without an absolute URL. By default, the query string and hash ofurlandreferrerare removed;trackQuery,queryParamsandscrubUrltune this as in the browser. -
Payload:
propsare coerced to strings and capped (30 keys, 64-character keys, 1024-character values). A malformedrevenueis dropped with a warning, and the event is still sent. -
Names: an empty name or the reserved
pageviewname passed toevent()always throws, even withoutstrict. Usepageview()for pageviews. -
Errors: by default, a network error or an answer other than
202is swallowed, since analytics must never break your server. Withstrict: true, the promise rejects (handy in tests or in a job that should retry).
A
202means "accepted", not "recorded": the ingest still filters crawlers.
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')
})-
redactRoutestakes the same patterns as the browser SDK ([param],[[optional]],[...rest],:param,:param?,*). The first call above sendshttps://example.com/verify/[token]. -
routeonpageview()orevent()replaces the path of that call only, typically with the route your server framework matched (/orders/:id). A same-originreferreris then reduced to the site origin. Withoutroute,redactRoutesapplies.
See Privacy for the pattern syntax and the per-page stats trade-off.
Source: github.com/taktlytics/takt-core · npm @vskstudio/takt-core
- Home
- The-Snippet
- Installation
- Configuration
- Vanilla-JS
- Events-and-Payload
- Privacy
- Widgets
- Server-Side-Node
- API-Reference
React · Vue · Svelte wrappers: see the docs site.