Skip to content

Feat/statsig marketing ssr#2935

Merged
eldadfux merged 3 commits intomainfrom
feat/statsig-marketing-ssr
Apr 29, 2026
Merged

Feat/statsig marketing ssr#2935
eldadfux merged 3 commits intomainfrom
feat/statsig-marketing-ssr

Conversation

@eldadfux
Copy link
Copy Markdown
Member

What does this PR do?

(Provide a description of what this PR does.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

- Enhanced the `evaluateHeroDescriptionExperiment` function documentation to clarify the need for client-side exposure logging.
- Implemented `onMount` in the hero component to log exposure for the `best_description` experiment after SSR, ensuring accurate enrollment tracking in Statsig.

Made-with: Cursor
…support

- Added support for passing `userAgent` during Statsig client initialization to avoid bootstrap user mismatch warnings.
- Updated relevant functions and components to handle the new `statsigUserAgent` parameter, ensuring consistency across server and client initialization.

Made-with: Cursor
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 28, 2026

Greptile Summary

This PR threads the server request's User-Agent header through the SSR → bootstrap → client-init pipeline so the Statsig JS SDK's StatsigUser exactly matches the user object the server used in getClientInitializeResponse, eliminating "bootstrap user mismatch" SDK warnings. It also adds a client-side exposure call in hero.svelte so Statsig Pulse/Results records experiment enrollments (the server evaluates with disableExposureLogging: true). The approach is correct and consistent with the existing stable-ID bridging pattern already in place.

Confidence Score: 4/5

Safe to merge; changes are narrowly scoped to Statsig bootstrap alignment with no functional regressions.

Only P2 findings: stale module-level pending state (pre-existing pattern, harmless) and a minor readability note in hero.svelte. No logic errors or security concerns.

No files require special attention.

Important Files Changed

Filename Overview
src/lib/statsig/client.ts Adds pendingStatsigUserAgent module-level variable and threads it through initStatsig/runStatsigInit so the browser StatsigClient user object matches the SSR user used to generate the bootstrap payload.
src/lib/statsig/hero-statsig.server.ts Comment-only update clarifying that exposure logging is intentionally deferred to the client-side hero.svelte call.
src/routes/(marketing)/(components)/hero.svelte Adds onMount to log Statsig experiment exposure client-side after SSR renders the experiment value, ensuring Pulse/Results enrollments are recorded.
src/routes/(marketing)/+page.server.ts Returns statsigUserAgent (request user-agent) as page data so the client can initialize Statsig with the exact same user fields the server used for bootstrap generation.
src/routes/+layout.svelte Passes the new statsigUserAgent field from page data to initStatsig on every navigation.

Reviews (1): Last reviewed commit: "fix" | Re-trigger Greptile

Comment thread src/lib/statsig/client.ts
Comment on lines +238 to +241
pendingStatsigUserAgent =
typeof bootstrapUserAgent === 'string' && bootstrapUserAgent.length > 0
? bootstrapUserAgent
: undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Stale pending state on repeated initStatsig calls

When initStatsig is called on a navigation where the client is already fully bootstrapped (client != null && appliedBootstrapPayload != null), it sets pendingStatsigUserAgent (and the other two pending variables) but the code falls through to the final return whenStatsigNetworkReady() without triggering a new runStatsigInit. The stale value then sits in the module variable until the next call that does trigger a re-init. The same pattern exists for pendingBootstrapJson and pendingServerStableUserId, so this is consistent, but worth noting that on non-/ navigations back to /, the pending UA value is left unconsumed.

Comment on lines +27 to +33
* exposure when the user actually sees the hero. Reading `description` logs the exposure and
* keeps the rendered line as the SSR value (default matches props).
* @see https://docs.statsig.com/pulse (exposures enroll units in the experiment)
*/
onMount(() => {
if (!browser || ENV.TEST) return;
void whenStatsigReady().then((client) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Exposure logged with potentially stale subtitle fallback

client.getExperiment(...).get('description', subtitle) uses the subtitle prop as the Statsig fallback. If the client bootstrap assigns a different variant than what the server evaluated (e.g., bootstrap mismatch that this PR is trying to fix), the return value of get will differ from the rendered text, but the return value is intentionally discarded so the DOM isn't updated. This is correct for the exposure-only pattern, but the discarded return makes the call's intent non-obvious; a brief inline comment clarifying that the return value is deliberately ignored would help future readers.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@eldadfux eldadfux merged commit 37b01e7 into main Apr 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant