Skip to content
SlashClone

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.

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

Cookies that keep you signed in and remember these choices are always on. Everything else is off until you turn it on.