Skip to content

feat: support static client component rendering#1227

Merged
joker23 merged 2 commits intomainfrom
skz/sdk-1946/refactor-noop-client
Mar 27, 2026
Merged

feat: support static client component rendering#1227
joker23 merged 2 commits intomainfrom
skz/sdk-1946/refactor-noop-client

Conversation

@joker23
Copy link
Copy Markdown
Contributor

@joker23 joker23 commented Mar 26, 2026

To support static client rendering, we will need to introduce a proper noop client for the client sdk since that will be ran on server side during build time.

Note

Medium Risk
Changes createClient behavior when window is undefined and alters server-side initialization/error semantics, which could affect SSR/static rendering and early flag reads. Risk is moderate due to potential subtle differences in hydration-time behavior and expected initialization state.

Overview
Enables static/SSR rendering of React client components by having createClient return a dedicated createNoopClient stub when window is undefined, avoiding runtime errors during build-time rendering.

Adds createNoopClient(bootstrap?), which can ad-hoc return flag values from provided bootstrap data (excluding $ metadata), reports initialization as complete only when bootstrap is provided, and otherwise stays initializing with no initialization error. Updates and expands tests to cover the new noop client behavior and bootstrap-based variation results.

Written by Cursor Bugbot for commit 2998425. This will update automatically on new commits. Configure here.


Open with Devin

@github-actions
Copy link
Copy Markdown
Contributor

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 25661 bytes
Compressed size limit: 29000
Uncompressed size: 126143 bytes

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 31371 bytes
Compressed size limit: 34000
Uncompressed size: 111380 bytes

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

@launchdarkly/js-client-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 36775 bytes
Compressed size limit: 38000
Uncompressed size: 201898 bytes

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

@launchdarkly/browser size report
This is the brotli compressed size of the ESM build.
Compressed size: 179146 bytes
Compressed size limit: 200000
Uncompressed size: 828569 bytes

@joker23
Copy link
Copy Markdown
Contributor Author

joker23 commented Mar 26, 2026

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

addHook: noop,
waitForInitialization: noopPromise,
setStreaming: noop,
start: noopPromise,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Noop client methods resolve to undefined breaking API contract

Medium Severity

The noopPromise (() => Promise.resolve()) is used for start, waitForInitialization, and flush, which all have non-void return types in the LDClient interface. start() and waitForInitialization() are typed to return Promise<LDWaitForInitializationResult>, and flush() returns Promise<{ error?: Error; result: boolean }>. Resolving to undefined means any code that accesses .status or .result on the resolved value will throw a TypeError at runtime. The old noop client returned proper result objects (e.g., { status: 'failed', error } for start/waitForInitialization). The as unknown as LDReactClient cast hides this mismatch from the compiler.

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

per previous comments - this should not be an issue... we can fix this if static rendering breaks.

@joker23 joker23 force-pushed the skz/sdk-1946/refactor-noop-client branch from 2998425 to 3d2d627 Compare March 26, 2026 16:11
@joker23 joker23 marked this pull request as ready for review March 26, 2026 16:28
@joker23 joker23 requested a review from a team as a code owner March 26, 2026 16:28
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

@joker23 joker23 force-pushed the skz/sdk-1946/refactor-noop-client branch from 3d2d627 to 82d02de Compare March 27, 2026 16:59
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

// static rendering which will attempt to render client code during build time. In these cases,
// we will need to use the noop client to avoid errors.
if (typeof window === 'undefined') {
return createNoopClient();
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.

🔴 Bootstrap data is never passed to createNoopClient during SSR

When running server-side (e.g., Next.js SSR/static rendering), createClient at LDReactClient.tsx:52 calls createNoopClient() with no arguments, discarding any bootstrap data the user provided. The bootstrap data flows through createLDReactProvider (LDReactProvider.tsx:123-129) into client.start({ bootstrap }), but the noop client's start is a no-op (() => Promise.resolve()) that ignores its arguments.

As a result, during SSR:

  • isReady() returns false instead of true
  • getInitializationState() returns 'initializing' instead of 'complete'
  • All variation hooks (via useVariationCore.ts:14,27-30) see ready === false and return default values instead of bootstrap flag values

createNoopClient was clearly designed to support bootstrap (it accepts a bootstrap parameter, extracts flags, sets hasBootstrap, and conditions isReady/getInitializationState on it), but no code path ever supplies it.

SSR flow showing bootstrap data loss

In createLDReactProvider (LDReactProvider.tsx:122-132):

  1. createClient(id, ctx, ldOptions) → detects SSR → returns createNoopClient() (no bootstrap)
  2. client.start({ bootstrap: data }) → noop start ignores the argument
  3. Bootstrap data is lost; noop client behaves as if no bootstrap was provided
Prompt for agents
In packages/sdk/react/src/client/LDReactClient.tsx, the SSR path at line 52 calls createNoopClient() without any bootstrap data. The fix needs to propagate bootstrap data from createLDReactProvider to createNoopClient during SSR. There are several approaches:

1. Add a bootstrap parameter to createClient's options (e.g., extend LDReactClientOptions or add a separate parameter), and pass it through to createNoopClient when SSR is detected. Then in createLDReactProvider (LDReactProvider.tsx:125), pass the bootstrap data via createClient.

2. Alternatively, detect SSR in createLDReactProvider itself (before calling createClient) and create the noop client directly with the bootstrap data, bypassing createClient entirely.

The key requirement is that when bootstrap data is available during SSR, createNoopClient(bootstrap) must be called instead of createNoopClient().
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@joker23 joker23 merged commit 6b3a100 into main Mar 27, 2026
42 checks passed
@joker23 joker23 deleted the skz/sdk-1946/refactor-noop-client branch March 27, 2026 17:15
@github-actions github-actions bot mentioned this pull request Mar 27, 2026
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.

2 participants