From ff57c9089f2c2c0afe362c9c9540893ba0364259 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Fri, 17 Apr 2026 13:13:40 -0400 Subject: [PATCH 1/3] feat(analytics): add UTMs to docs signup links (GRO-86) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So Growth can attribute new Arcade account creations back to docs — per-page campaigns for the three QuickStarts, generic `docs` elsewhere. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/_components/analytics.tsx | 21 ++++++++++++++++++- .../quickstarts/call-tool-agent/page.mdx | 2 +- .../quickstarts/call-tool-client/page.mdx | 2 +- .../mcp-server-quickstart/page.mdx | 2 +- app/layout.tsx | 2 +- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/app/_components/analytics.tsx b/app/_components/analytics.tsx index 404e9fc26..7d7eda152 100644 --- a/app/_components/analytics.tsx +++ b/app/_components/analytics.tsx @@ -8,12 +8,31 @@ export type LinkClickedProps = { linkLocation: string; children?: React.ReactNode; className?: string; + utmCampaign?: string; + utmMedium?: string; +}; + +const UTM_SOURCE = "docs"; +const DEFAULT_UTM_MEDIUM = "docs-cta"; +const DEFAULT_UTM_CAMPAIGN = "docs"; + +const buildRegisterHref = (utmMedium: string, utmCampaign: string): string => { + const base = getDashboardUrl("register"); + const separator = base.includes("?") ? "&" : "?"; + const params = new URLSearchParams({ + utm_source: UTM_SOURCE, + utm_medium: utmMedium, + utm_campaign: utmCampaign, + }); + return `${base}${separator}${params.toString()}`; }; export const SignupLink = ({ linkLocation, children, className, + utmCampaign = DEFAULT_UTM_CAMPAIGN, + utmMedium = DEFAULT_UTM_MEDIUM, }: LinkClickedProps) => { const trackSignupClick = (source: string) => { posthog.capture("Signup clicked", { @@ -24,7 +43,7 @@ export const SignupLink = ({ return ( trackSignupClick(linkLocation)} > {children} diff --git a/app/en/get-started/quickstarts/call-tool-agent/page.mdx b/app/en/get-started/quickstarts/call-tool-agent/page.mdx index 32621af24..8327ea7c0 100644 --- a/app/en/get-started/quickstarts/call-tool-agent/page.mdx +++ b/app/en/get-started/quickstarts/call-tool-agent/page.mdx @@ -20,7 +20,7 @@ Install and use the Arcade client to call Arcade Hosted Tools. -- An Arcade account +- An Arcade account - An [Arcade API key](/get-started/setup/api-keys) - The [`uv` package manager](https://docs.astral.sh/uv/getting-started/installation/) if you are using Python - The [`bun` runtime](https://bun.com/) if you are using TypeScript diff --git a/app/en/get-started/quickstarts/call-tool-client/page.mdx b/app/en/get-started/quickstarts/call-tool-client/page.mdx index 0375b7592..41198b1d4 100644 --- a/app/en/get-started/quickstarts/call-tool-client/page.mdx +++ b/app/en/get-started/quickstarts/call-tool-client/page.mdx @@ -35,7 +35,7 @@ Create a coding agent using an MCP Gateway to call tools from multiple MCP serve -- An Arcade account +- An Arcade account diff --git a/app/en/get-started/quickstarts/mcp-server-quickstart/page.mdx b/app/en/get-started/quickstarts/mcp-server-quickstart/page.mdx index a16612058..8d6a08b0c 100644 --- a/app/en/get-started/quickstarts/mcp-server-quickstart/page.mdx +++ b/app/en/get-started/quickstarts/mcp-server-quickstart/page.mdx @@ -158,7 +158,7 @@ $env:MY_SECRET_KEY="my-secret-value" ## Connect to Arcade to unlock authorized tool calling -Since the Reddit tool accesses information only available to your Reddit account, you'll need to authorize it. For this, you'll need to create an Arcade account and connect from the terminal, run: +Since the Reddit tool accesses information only available to your Reddit account, you'll need to authorize it. For this, you'll need to create an Arcade account and connect from the terminal, run: ```bash arcade login diff --git a/app/layout.tsx b/app/layout.tsx index 633b33429..5b01b5d80 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -160,7 +160,7 @@ export default async function RootLayout({ } projectLink="https://github.com/ArcadeAI/arcade-mcp" > - + Date: Fri, 17 Apr 2026 13:24:41 -0400 Subject: [PATCH 2/3] refactor(analytics): prefix docs campaigns so startsWith("docs") catches all - Default utm_campaign=docs (generic docs pages) - QuickStarts use utm_campaign=docs-quickstart- - Drop default utm_medium (most SignupLinks are inline prose, not CTAs) - Navbar uses utm_medium=navbar (matches marketing site convention) Co-Authored-By: Claude Opus 4.7 (1M context) --- app/_components/analytics.tsx | 11 ++++++----- .../get-started/quickstarts/call-tool-agent/page.mdx | 2 +- .../get-started/quickstarts/call-tool-client/page.mdx | 2 +- .../quickstarts/mcp-server-quickstart/page.mdx | 2 +- app/layout.tsx | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/_components/analytics.tsx b/app/_components/analytics.tsx index 7d7eda152..8c6b5f1f9 100644 --- a/app/_components/analytics.tsx +++ b/app/_components/analytics.tsx @@ -13,17 +13,18 @@ export type LinkClickedProps = { }; const UTM_SOURCE = "docs"; -const DEFAULT_UTM_MEDIUM = "docs-cta"; const DEFAULT_UTM_CAMPAIGN = "docs"; -const buildRegisterHref = (utmMedium: string, utmCampaign: string): string => { +const buildRegisterHref = (utmCampaign: string, utmMedium?: string): string => { const base = getDashboardUrl("register"); const separator = base.includes("?") ? "&" : "?"; const params = new URLSearchParams({ utm_source: UTM_SOURCE, - utm_medium: utmMedium, utm_campaign: utmCampaign, }); + if (utmMedium) { + params.set("utm_medium", utmMedium); + } return `${base}${separator}${params.toString()}`; }; @@ -32,7 +33,7 @@ export const SignupLink = ({ children, className, utmCampaign = DEFAULT_UTM_CAMPAIGN, - utmMedium = DEFAULT_UTM_MEDIUM, + utmMedium, }: LinkClickedProps) => { const trackSignupClick = (source: string) => { posthog.capture("Signup clicked", { @@ -43,7 +44,7 @@ export const SignupLink = ({ return ( trackSignupClick(linkLocation)} > {children} diff --git a/app/en/get-started/quickstarts/call-tool-agent/page.mdx b/app/en/get-started/quickstarts/call-tool-agent/page.mdx index 8327ea7c0..0c56a689c 100644 --- a/app/en/get-started/quickstarts/call-tool-agent/page.mdx +++ b/app/en/get-started/quickstarts/call-tool-agent/page.mdx @@ -20,7 +20,7 @@ Install and use the Arcade client to call Arcade Hosted Tools. -- An Arcade account +- An Arcade account - An [Arcade API key](/get-started/setup/api-keys) - The [`uv` package manager](https://docs.astral.sh/uv/getting-started/installation/) if you are using Python - The [`bun` runtime](https://bun.com/) if you are using TypeScript diff --git a/app/en/get-started/quickstarts/call-tool-client/page.mdx b/app/en/get-started/quickstarts/call-tool-client/page.mdx index 41198b1d4..c4e01616f 100644 --- a/app/en/get-started/quickstarts/call-tool-client/page.mdx +++ b/app/en/get-started/quickstarts/call-tool-client/page.mdx @@ -35,7 +35,7 @@ Create a coding agent using an MCP Gateway to call tools from multiple MCP serve -- An Arcade account +- An Arcade account diff --git a/app/en/get-started/quickstarts/mcp-server-quickstart/page.mdx b/app/en/get-started/quickstarts/mcp-server-quickstart/page.mdx index 8d6a08b0c..52e7e6811 100644 --- a/app/en/get-started/quickstarts/mcp-server-quickstart/page.mdx +++ b/app/en/get-started/quickstarts/mcp-server-quickstart/page.mdx @@ -158,7 +158,7 @@ $env:MY_SECRET_KEY="my-secret-value" ## Connect to Arcade to unlock authorized tool calling -Since the Reddit tool accesses information only available to your Reddit account, you'll need to authorize it. For this, you'll need to create an Arcade account and connect from the terminal, run: +Since the Reddit tool accesses information only available to your Reddit account, you'll need to authorize it. For this, you'll need to create an Arcade account and connect from the terminal, run: ```bash arcade login diff --git a/app/layout.tsx b/app/layout.tsx index 5b01b5d80..f9275167f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -160,7 +160,7 @@ export default async function RootLayout({ } projectLink="https://github.com/ArcadeAI/arcade-mcp" > - + Date: Fri, 17 Apr 2026 13:26:31 -0400 Subject: [PATCH 3/3] refactor(analytics): use URL constructor for SignupLink UTM assembly Co-Authored-By: Claude Opus 4.7 (1M context) --- app/_components/analytics.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/_components/analytics.tsx b/app/_components/analytics.tsx index 8c6b5f1f9..e9939f3f6 100644 --- a/app/_components/analytics.tsx +++ b/app/_components/analytics.tsx @@ -16,16 +16,13 @@ const UTM_SOURCE = "docs"; const DEFAULT_UTM_CAMPAIGN = "docs"; const buildRegisterHref = (utmCampaign: string, utmMedium?: string): string => { - const base = getDashboardUrl("register"); - const separator = base.includes("?") ? "&" : "?"; - const params = new URLSearchParams({ - utm_source: UTM_SOURCE, - utm_campaign: utmCampaign, - }); + const url = new URL(getDashboardUrl("register")); + url.searchParams.set("utm_source", UTM_SOURCE); + url.searchParams.set("utm_campaign", utmCampaign); if (utmMedium) { - params.set("utm_medium", utmMedium); + url.searchParams.set("utm_medium", utmMedium); } - return `${base}${separator}${params.toString()}`; + return url.toString(); }; export const SignupLink = ({