A small, config-driven redirect service for growth links on Cloudflare Workers. It makes campaign-attribution rules explicit at the edge and can emit privacy-minimized click events to Cloudflare Analytics Engine.
Short links often start as a spreadsheet and quietly become production
infrastructure. auto-redirect makes the important behavior explicit:
- links live in a typed configuration file;
- only absolute HTTPS destinations are accepted;
- campaign defaults are applied without overwriting destination or visitor data;
- redirect semantics are selected per link (
302or307); - click analytics are optional and do not automatically collect request-derived identifiers such as IP address, user agent, cookie, or referrer.
GET /launch?utm_source=partner
|
v
match "launch" in src/links.ts
|
v
validate destination + merge query parameters
|
+----> optional Analytics Engine event
|
v
307 https://example.com/product?plan=pro&utm_medium=redirect&utm_campaign=product-launch&utm_source=partner
The merge order is:
- destination query parameters;
- configured defaults, only when a key is still missing;
- incoming visitor query parameters, which replace configured defaults but cannot replace parameters already fixed by the destination URL.
This lets a link lock a parameter such as plan=pro, provide sensible UTM
defaults, and still preserve a partner-provided utm_source. Repeated incoming
values are forwarded in order, and an explicit empty incoming value replaces a
configured default.
npm install
npm test
npm run typecheck
npm run build
npm run devLocal development requires Node.js 22 or newer.
Then try:
curl -i 'http://localhost:8787/launch?utm_source=partner'
curl -i 'http://localhost:8787/health'Edit src/links.ts to define your own links:
export const links = defineLinks({
launch: {
destination: "https://example.com/product?plan=pro",
status: 307,
defaults: {
utm_source: "shortlink",
utm_medium: "redirect",
utm_campaign: "product-launch",
},
},
});Slugs must contain at most 96 ASCII letters, numbers, _, or -; health is
reserved. Destinations must use HTTPS. Invalid configuration fails fast when
the Worker starts without echoing the destination URL.
| Request | Response |
|---|---|
GET /health |
200 JSON health response |
GET /:slug |
configured 302 or 307 redirect |
HEAD /:slug |
same redirect without analytics |
| unknown slug | 404 JSON error |
| unsupported method | 405 JSON error with Allow: GET, HEAD |
| nested path, duplicate slash, or trailing slash | 404 JSON error |
All responses include Cache-Control: no-store. Redirects also include
Referrer-Policy: no-referrer.
Analytics are disabled unless the optional CLICKS Analytics Engine binding is
configured. Each successful GET redirect writes one data point containing:
- slug;
- destination hostname;
- redirect status;
- timestamp.
The service deliberately does not record IP addresses, full destination
URLs, query strings, user agents, cookies, or referrers. The recorded slug and
destination hostname are operator-controlled metadata, so do not encode email
addresses, account IDs, or other personal data in either. HEAD requests are
excluded so health checks and link unfurlers do not inflate click counts.
To enable events, create an Analytics Engine dataset binding named CLICKS in
wrangler.jsonc, then deploy. The checked-in configuration leaves the binding
commented out so the Worker runs without additional Cloudflare resources.
The core URL and routing logic is pure and is tested independently from the Cloudflare runtime. The Worker adapter only resolves bindings and schedules the optional analytics write.
npm test # Vitest suite
npm run typecheck # TypeScript, including Workers types
npm run build # Wrangler dry-run bundleCI runs all three checks on pushes and pull requests.
- Authenticate Wrangler with your Cloudflare account.
- Change the Worker name in
wrangler.jsoncif needed. - Optionally add the
CLICKSAnalytics Engine binding. - Run
npm run deploy. - Add a custom domain or route in Cloudflare.
No secrets are required by the application.