feat: forward known utm params from homepage to get-started CTA links - #549
Merged
Merged
Conversation
A visitor landing on the homepage with utm params lost that attribution at the CTA step: the in-page hrefs were bare, so `/get-started` had nothing to read off its own request URL when capturing `homepage:cta_click`. `getServerSideProps` now re-encodes the five recognised utm params into a query string and passes it down to both CTA links. Only the known keys are forwarded — unknown query params are never reflected into on-page links. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JdRBhYmUnmC9vbc7fcgXdC
railway-app
Bot
temporarily deployed
to
docs.page / docs.page-pr-549
September 8, 2026 08:40
Destroyed
|
🚅 Deployed to the docs.page-pr-549 environment in docs.page
|
|
|
Ehesp
approved these changes
Sep 11, 2026
Ehesp
marked this pull request as ready for review
September 11, 2026 14:19
claude Bot
pushed a commit
that referenced
this pull request
Sep 11, 2026
Resolves the conflict with the homepage work that landed on main since this branch was last updated, in particular #549 (utm forwarding to the get-started CTA), which touched both files this PR also changes. Conflicts resolved: - app/src/lib/utm.ts — both sides hoisted the same five-key array into a UTM_KEYS const. Kept it exported, because this PR's hero imports it to forward utm params onto the copy beacon, and merged both comments so the const names all three consumers. main's utmQueryString() is kept as-is. - app/src/components/homepage/hero.tsx — auto-merged, verified by hand. main added the utmQuery prop and interpolated it into the get-started href; this PR restacked the CTA area into one centred column. Both survive: Hero takes utmQuery, the Link uses it, and Terminal still sits above the button. - app/src/components/homepage/source-files.json — regenerated with `bun run generate:source-files` rather than resolved by hand; picks up the five source files main added. bun.lock is deliberately left at the merge result. Running `bun install` re-resolved `@types/bun` (a "latest" spec) and corrected a packages/cli version that is already stale on main — both unrelated to this merge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BgG4JVuadUeZGtry1yFFd5
claude Bot
pushed a commit
that referenced
this pull request
Sep 11, 2026
Merge main (9a37953) into cursor/404-shader-dissolve-7370. The only conflict was app/src/components/homepage/footer.tsx, where two orthogonal changes to the same region collided: - This PR (#542) wraps the closer block in `{showCloser ? ... : null}` and adds a `showCloser` prop, so the site 404 can reuse Footer without the "Bring your docs into the agentic age" CTA. - #549 on main adds a `utmQuery` prop and appends it to the /get-started href so UTM params are forwarded from the homepage. Both are kept: the closer stays wrapped in `showCloser`, and the CTA href keeps the `${utmQuery}` suffix. `utmQuery` is typed optional with a `""` default rather than required. Footer has two callers that pass no UTM — `docs.tsx` (pre-existing on main) and the new `not-found.tsx` — and the closer is not rendered at all when `showCloser` is false, so there is no UTM to forward in those cases. app/src/lib/types.ts and app/src/pages/[[...path]].tsx merged cleanly; both sides' changes there are in disjoint regions and are preserved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EF9amQgkVoieaefXVoHRXz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Elliot Hesp · Slack thread
Summary
Before: a visitor arrives at
docs.page/?utm_source=twitter&utm_campaign=launch. The page view is captured with those params, but the two "Get started" buttons render as bare/get-startedlinks. When the visitor clicks,/get-startedsees no utm params on its own request URL, sohomepage:cta_clicklands with no attribution — the campaign gets credit for the visit but not for the click.After: the same visit renders both buttons as
/get-started?utm_source=twitter&utm_campaign=launch, so the click event carries the same attribution as the page view and a flight's page-view → CTA-click funnel reconciles in PostHog.Why: without it, paid and referral traffic cannot be attributed past the first hop, which makes the homepage CTA conversion rate unmeasurable per campaign.
How:
app/src/lib/utm.tsgainsutmQueryString(url), which reuses the existingutmProperties()helper so the five recognised param names stay a single source of truth, and re-encodes them viaURLSearchParams(empty string when none are present). The homepage branch ofgetServerSidePropsinapp/src/pages/[[...path]].tsxcomputes it once and passes it as autmQueryprop down to the hero and footer CTAs. Only the five known keys are forwarded — the inbound query string is never passed through wholesale, so no arbitrary param can be reflected into an on-page link, and the CTA href prefix stays a hardcoded relative path./get-starteditself is unchanged; it already reads utm params off its own request URL. No new dependencies, no client-side PostHog.Scope
app/(hosted site, MCP, Ask AI)packages/cli/packages/mdx-bundler/docs/(product documentation)Type of change
Test plan
bun run checkpasses locally (andbiome ci .)bun test— 147 pass, 0 failbunx tsc --noEmit -p app/tsconfig.json— cleancd app && bun run build— production build succeedsdocs/(if user-facing) — n/a, internal analytics plumbingNew unit tests in
app/src/lib/utm.test.tscover no params, a subset, all five, unknown params dropped, and values needing encoding (theutm_campaignfield has historically carried injection-probe strings, so the encoding case asserts the value cannot break out of the link).Notes for reviewers
Prop drilling is two levels deep (
Homepage→Hero/Footer); a context felt like more machinery than the one string warrants, but happy to change it.Worth a check on the Railway preview with
?utm_source=x&utm_campaign=yappended, confirming both button hrefs carry the params and that a param that is not one of the five is dropped.🤖 Generated with Claude Code
https://claude.ai/code/session_01JdRBhYmUnmC9vbc7fcgXdC
Generated by Claude Code