Serve it from your own domain
Load the script and send visits through your own domain, under neutral paths, with a recipe for Next.js, Vercel, Netlify, Cloudflare, Express, Nginx and Caddy.
Two paths on your own domain are all it takes:
| Path | What it does | Forwards to |
|---|---|---|
/s/x.js | the tracker script | https://stats.example.com/js/t.js |
/s/e | your visits | https://stats.example.com/api/e |
/s/h | heatmap reports (only if you use heatmaps) | https://stats.example.com/api/h |
The tag points at both:
<script
defer
data-site="tkb_a1b2c3d4"
data-domain="example.com"
data-api="/s/e"
src="/s/x.js">
</script>Nothing changes for what you have today: /js/… and /api/e on your trckable
server keep working exactly as before. These paths are an addition.
What the proxy has to do
- Pass the script through. It is public, so no key is needed.
- Pass each visit, and each heatmap report, through with two headers:
X-Trckable-Proxy-Key(the site's proxy key) andX-Trckable-Client-IP(the visitor's address).
trckable believes the address only together with the key. Without the key the header is ignored, so nobody can post visits that claim to come from another country. The key lives on your server, never in the browser (see the proxy key).
The recipes below use the trckable package for that, so there is one
implementation of the header handling, whatever you run. Install it with
npm install trckable, and set these where the proxy runs:
TRCKABLE_HOST=https://stats.example.com
TRCKABLE_PROXY_KEY=your-proxy-keyNext.js
// next.config.mjs: serves /s/x.js
import { withTrckable } from 'trckable/next'
export default withTrckable({})// app/s/e/route.ts: takes the visits
export { POST } from 'trckable/next'// app/s/h/route.ts: heatmap reports, if you use them
export { heatPost as POST } from 'trckable/next'// app/layout.tsx
import Script from 'next/script'
<Script src="/s/x.js" data-site="tkb_a1b2c3d4" data-domain="example.com" data-api="/s/e" strategy="afterInteractive" />Vercel (not Next.js)
{
"rewrites": [
{ "source": "/s/x.js", "destination": "https://stats.example.com/js/t.js" },
{ "source": "/s/e", "destination": "/api/s/e" },
{ "source": "/s/h", "destination": "/api/s/h" }
]
}// api/s/e.ts: an Edge Function for the visits
import { proxy } from 'trckable/server'
export const config = { runtime: 'edge' }
export default proxy()// api/s/h.ts: the same for heatmap reports, if you use them
import { proxy } from 'trckable/server'
export const config = { runtime: 'edge' }
export default proxy({ target: 'heat' })Netlify
# _redirects: the script
/s/x.js https://stats.example.com/js/t.js 200// netlify/edge-functions/trckable.ts: the visits
import { proxy } from 'trckable/server'
export default proxy()
export const config = { path: '/s/e' }// netlify/edge-functions/trckable-heat.ts: heatmap reports, if you use them
import { proxy } from 'trckable/server'
export default proxy({ target: 'heat' })
export const config = { path: '/s/h' }Cloudflare Worker
import { proxy, scriptProxy } from 'trckable/server'
const events = proxy()
const heat = proxy({ target: 'heat' })
const script = scriptProxy()
export default {
fetch(request: Request, env: { TRCKABLE_HOST: string; TRCKABLE_PROXY_KEY: string }) {
const { pathname } = new URL(request.url)
if (pathname === '/s/x.js') return script(request)
if (pathname === '/s/e') return events(request)
if (pathname === '/s/h') return heat(request)
return fetch(request)
},
}Set TRCKABLE_HOST as a variable and TRCKABLE_PROXY_KEY as a secret, and put
the Worker on the routes example.com/s/x.js, example.com/s/e and example.com/s/h.
Express
import { proxy, scriptProxy, nodeHandler } from 'trckable/server'
app.get('/s/x.js', nodeHandler(scriptProxy()))
app.post('/s/e', nodeHandler(proxy()))
app.post('/s/h', nodeHandler(proxy({ target: 'heat' })))nodeHandler also works with Connect and a plain node:http server. It takes
the visitor's address from the connection (req.ip, which follows Express's
trust proxy setting), never from a header the visitor sent. Behind a load
balancer, set app.set('trust proxy', …) so that address is the visitor's.
Nginx
location = /s/x.js {
proxy_pass https://stats.example.com/js/t.js;
proxy_ssl_server_name on;
}
location = /s/e {
proxy_pass https://stats.example.com/api/e;
proxy_ssl_server_name on;
proxy_set_header X-Trckable-Client-IP $remote_addr;
proxy_set_header X-Trckable-Proxy-Key your-proxy-key;
}
location = /s/h {
proxy_pass https://stats.example.com/api/h;
proxy_ssl_server_name on;
proxy_set_header X-Trckable-Client-IP $remote_addr;
proxy_set_header X-Trckable-Proxy-Key your-proxy-key;
}Caddy
handle /s/x.js {
rewrite * /js/t.js
reverse_proxy https://stats.example.com {
header_up Host stats.example.com
}
}
handle /s/e {
rewrite * /api/e
reverse_proxy https://stats.example.com {
header_up Host stats.example.com
header_up X-Trckable-Proxy-Key "your-proxy-key"
header_up X-Trckable-Client-IP {remote_host}
}
}
handle /s/h {
rewrite * /api/h
reverse_proxy https://stats.example.com {
header_up Host stats.example.com
header_up X-Trckable-Proxy-Key "your-proxy-key"
header_up X-Trckable-Client-IP {remote_host}
}
}Check it
npx trckable doctor --host https://stats.example.com --site tkb_a1b2c3d4 --url https://example.comOn trckable Cloud, the Check setup button does the same from our side, for any setup, your own proxy included: it loads your page, finds the tag, and confirms the script and a test visit arrive through your domain, and says what to fix when they do not. See Serve from your domain.
Same-origin proxy
Send events through your own domain. It is the difference between a seven-day cookie and a 400-day one, and between being blocked and being ordinary.
Landing page and app
A plain HTML landing page on example.com and a Next.js app on app.example.com, as one site: one visitor, one journey, and the sale credited to its source.