Get found in search and see where sign-ups come from
@slashclone/website-traffic
Get your web app found in Google and AI search, and see which channels bring sign-ups. Sets up sitemap, robots, canonical URLs, share images, and structured data with a page checker, then consent-aware first-touch tracking from UTM tags and referral links, saved at sign-up. Includes a launch channel checklist. Engineering guidance, not legal advice.
- Activity
- New: be the first to try it
- Installs
- 0 installs
- Likes
- 0 likes
- Clones
- 0 clones
- 1// First-touch attribution, consent first. Framework free; adapt names to the project.
- 2//
- 3// 1. readLanding() runs once when the app loads in the browser. It only reads.
- 4// 2. The result stays in memory. Nothing is stored until analytics consent.
- 5// 3. When consent arrives, rememberFirstTouch() writes one short-lived first-party cookie,
- 6// unless one already exists (first touch wins).
- 7// 4. The sign-up handler on the server calls parseFirstTouch() on the cookie value,
- 8// saves the clean result with the new account, and clears the cookie.
- 10export const FIRST_TOUCH_COOKIE = "first_touch"; // pick your own name
- 11const MAX_AGE_SECONDS = 30 * 24 * 60 * 60; // about a month; keep it short
- 13export type FirstTouch = {
- 14 source?: string; // utm_source
- 15 medium?: string; // utm_medium
- 16 campaign?: string; // utm_campaign
- 17 ref?: string; // ?ref= creator handle or partner code
- 18 referrer?: string; // referring site's host only, never the full URL
- 19 landing?: string; // landing path only, never the query string
- 20};
- 22const TAG = /^[A-Za-z0-9._+\- ]{1,64}$/;
- 23const REF = /^[a-z0-9-]{1,39}$/; // match your handle rules
- 24const HOST = /^[a-z0-9.-]{1,253}$/;
- 25const PATH = /^\/[A-Za-z0-9/_.\-~]{0,199}$/;
- 27const tag = (v: unknown, re = TAG): string | undefined =>
- 28 typeof v === "string" && re.test(v.trim()) ? v.trim() : undefined;
- 30/** Read where this visit came from. Pure: stores nothing. */
- 31export function readLanding(href: string, referrerUrl: string): FirstTouch | null {
- 32 let url: URL;
- 33 try {
- 34 url = new URL(href);
- 35 } catch {
- 36 return null;
- 37 }
- 38 const q = url.searchParams;
- 39 let referrer: string | undefined;
- 40 try {
- 41 const host = referrerUrl
- 42 ? new URL(referrerUrl).hostname.toLowerCase().replace(/^www\./, "")
- 43 : "";
- 44 // Your own site is not a source.
- 45 if (host && host !== url.hostname.toLowerCase().replace(/^www\./, ""))
- 46 referrer = tag(host, HOST);
- 47 } catch {
- 48 referrer = undefined;
- 49 }
- 50 const touch: FirstTouch = {
- 51 source: tag(q.get("utm_source")),
- 52 medium: tag(q.get("utm_medium")),
- 53 campaign: tag(q.get("utm_campaign")),
- 54 ref: tag(q.get("ref")?.toLowerCase(), REF),
- 55 referrer,
- 56 landing: tag(url.pathname, PATH),
- 57 };
- 58 // A direct visit with no tags is not worth a cookie.
- 59 return touch.source || touch.ref || touch.referrer ? touch : null;
- 60}
- 62/** Call only after the visitor allows analytics. Never overwrites an earlier touch. */
- 63export function rememberFirstTouch(touch: FirstTouch | null): void {
- 64 if (!touch || typeof document === "undefined") return;
- 65 if (document.cookie.split("; ").some((c) => c.startsWith(`${FIRST_TOUCH_COOKIE}=`))) return;
- 66 const secure = location.protocol === "https:" ? "; Secure" : "";
- 67 const value = encodeURIComponent(JSON.stringify(touch));
- 68 document.cookie = `${FIRST_TOUCH_COOKIE}=${value}; Path=/; Max-Age=${MAX_AGE_SECONDS}; SameSite=Lax${secure}`;
- 69}
- 71/** Remove the cookie, for example when analytics consent is withdrawn. */
- 72export function forgetFirstTouch(): void {
- 73 if (typeof document === "undefined") return;
- 74 document.cookie = `${FIRST_TOUCH_COOKIE}=; Path=/; Max-Age=0; SameSite=Lax`;
- 75}
- 77/**
- 78 * Server side, at sign-up. The cookie is visitor input: re-validate every field,
- 79 * drop anything unexpected, and return null rather than throw.
- 80 */
- 81export function parseFirstTouch(raw: string | undefined): FirstTouch | null {
- 82 if (!raw || raw.length > 1024) return null;
- 83 let data: unknown;
- 84 try {
- 85 data = JSON.parse(decodeURIComponent(raw));
- 86 } catch {
- 87 return null;
- 88 }
- 89 if (!data || typeof data !== "object") return null;
- 90 const d = data as Record<string, unknown>;
- 91 const touch: FirstTouch = {
- 92 source: tag(d.source),
- 93 medium: tag(d.medium),
- 94 campaign: tag(d.campaign),
- 95 ref: tag(d.ref, REF),
- 96 referrer: tag(d.referrer, HOST),
- 97 landing: tag(d.landing, PATH),
- 98 };
- 99 return touch.source || touch.ref || touch.referrer ? touch : null;
- 100}
- 102// Browser wiring sketch:
- 103//
- 104// const landing = readLanding(location.href, document.referrer); // at module load
- 105// onConsentChange((c) => (c.analytics ? rememberFirstTouch(landing) : forgetFirstTouch()));
- 106//
- 107// Server wiring sketch (inside the sign-up handler, after the account is created):
- 108//
- 109// const touch = parseFirstTouch(cookies.get(FIRST_TOUCH_COOKIE));
- 110// if (touch) await saveSignupSource(user.id, touch);
- 111// cookies.delete(FIRST_TOUCH_COOKIE);
