Skip to content

fix[sdks][personalization]: ENG-13927 dedupe personalization and A/B helper scripts across Content components - #4884

Open
floating-dynamo wants to merge 4 commits into
mainfrom
ENG-13927-dedup-scripts
Open

floating-dynamo wants to merge 4 commits into
mainfrom
ENG-13927-dedup-scripts

Conversation

@floating-dynamo

@floating-dynamo floating-dynamo commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

  • Every Content with a Variant Container or A/B test inlined its own copy of the helper scripts (~6.8KB each), so pages with a multiple content components shipped the same scripts several times.
  • Added an opt-in BuilderScripts wrapper that emits the helpers once for every Content inside it (nested wrappers emit nothing), and stopped inlined symbols from adding a second copy inside a single Content.

Link to JIRA ticket (if applicable):
https://builder-io.atlassian.net/browse/ENG-13927

Screenshot/Clip
Clip - https://clips.agent-native.com/share/7KXcQCxC20ir?ref=clip_share


Note

Medium Risk
Changes when variant/personalization init scripts run across SSR and hydration; mis-wrapping pages could leave helpers missing, while incorrect symbol inlining logic could skip scripts for bound symbols.

Overview
Adds an opt-in BuilderScripts wrapper that inlines the personalization (builderio-init-personalization-variants-fns) and A/B test (builderio-init-variants-fns) helper scripts once for all nested Content components, instead of repeating ~6.8KB per Content. Nested BuilderScripts emit nothing; context-based dedupe applies to React, Vue, Svelte, Solid, and Qwik—Next.js RSC, Angular, and React Native still render children only and each Content keeps emitting as before.

Content now skips those init scripts when a parent BuilderScripts has already set scriptsEmitted. Separately, symbol blocks pass isContentInlinedInParent so nested Content does not re-emit personalization helpers when the symbol JSON is already in the parent (bound/dynamic symbol content still fetches and may emit). Docs and React README describe the layout pattern; Qwik gets a default useContext for optional BuilderScripts.

Reviewed by Cursor Bugbot for commit 0d16e7e. Bugbot is set up for automated code reviews on this repo. Configure here.

@floating-dynamo
floating-dynamo requested a review from a team October 1, 2026 07:50
@floating-dynamo floating-dynamo self-assigned this Oct 1, 2026
@floating-dynamo
floating-dynamo requested review from AishwaryaParab and removed request for a team October 1, 2026 07:50
@changeset-bot

changeset-bot Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0d16e7e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
@builder.io/sdk-react Minor
@builder.io/sdk-angular Minor
@builder.io/sdk-react-nextjs Minor
@builder.io/sdk-qwik Minor
@builder.io/sdk-react-native Minor
@builder.io/sdk-solid Minor
@builder.io/sdk-svelte Minor
@builder.io/sdk-vue Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@nx-cloud

nx-cloud Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit a499dde

Command Status Duration Result
nx test @e2e/nextjs-sdk-next-app ✅ Succeeded 7m 36s View ↗
nx test @e2e/react-sdk-next-15-app ✅ Succeeded 5m 44s View ↗
nx test @e2e/angular-19-ssr ✅ Succeeded 5m 43s View ↗
nx test @e2e/angular-17 ✅ Succeeded 7m 8s View ↗
nx test @e2e/qwik-city ✅ Succeeded 7m 6s View ↗
nx test @e2e/react-sdk-next-pages ✅ Succeeded 5m 3s View ↗
nx test @e2e/nuxt ✅ Succeeded 6m 20s View ↗
nx test @e2e/angular-17-ssr ✅ Succeeded 5m 55s View ↗
Additional runs (38) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-10-01 09:51:03 UTC

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 using default effort and found 2 potential issues.

Fix All in Cursor

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

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 322b96d. Configure here.

Comment thread packages/sdks/src/components/builder-scripts.lite.tsx Outdated
builder-io-integration[bot]

This comment was marked as outdated.

@lihuelg

lihuelg commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

@floating-dynamo
bot found these, related to the failing CI

The code review found 3 issues on ENG-13927-dedup-scripts. The first two would break Qwik in production.

  1. High: content-variants.lite.tsx:55 throws in Qwik. Mitosis turns createContext into Qwik's createContextId, which drops the {scriptsEmitted: false} default. Qwik's useContext throws QError_notFoundContext when there's no provider and no default. So any without a BuilderScripts wrapper fails on render, including nested symbol variants. That covers every existing Qwik user. The useTarget guard doesn't help, because the useContext call is still emitted every time.
  2. High: builder-scripts.lite.tsx:38 has the same cause. The outermost BuilderScripts reads the parent context before anything provides it, so BuilderScripts itself throws in Qwik.
  3. Low: symbol.lite.tsx:134. isContentInlinedInParent={!!props.symbol?.content} assumes the symbol's JSON is inside the parent Content's JSON. A symbol whose content comes from a binding (state or data) isn't. In that case neither the parent nor the symbol emits the helper script, and the server-rendered personalization scripts fail with is not a function.

@floating-dynamo

Copy link
Copy Markdown
Contributor Author

@floating-dynamo bot found these, related to the failing CI

The code review found 3 issues on ENG-13927-dedup-scripts. The first two would break Qwik in production.

  1. High: content-variants.lite.tsx:55 throws in Qwik. Mitosis turns createContext into Qwik's createContextId, which drops the {scriptsEmitted: false} default. Qwik's useContext throws QError_notFoundContext when there's no provider and no default. So any without a BuilderScripts wrapper fails on render, including nested symbol variants. That covers every existing Qwik user. The useTarget guard doesn't help, because the useContext call is still emitted every time.
  2. High: builder-scripts.lite.tsx:38 has the same cause. The outermost BuilderScripts reads the parent context before anything provides it, so BuilderScripts itself throws in Qwik.
  3. Low: symbol.lite.tsx:134. isContentInlinedInParent={!!props.symbol?.content} assumes the symbol's JSON is inside the parent Content's JSON. A symbol whose content comes from a binding (state or data) isn't. In that case neither the parent nor the symbol emits the helper script, and the server-rendered personalization scripts fail with is not a function.

Yes thanks for pointing out, looking into these

…cripts and keep helper script for bound symbol content

@builder-io-integration builder-io-integration Bot left a comment

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.

Builder reviewed your changes and found 2 potential issues 🔴

Review Details

Incremental Code Review Summary

This update adds a helper to detect whether Symbol content is embedded in its parent or supplied through bindings, with tests for both cases. It also adds a Qwik code-generation rewrite that gives the optional BuilderScriptsContext read a default value. The broader change continues to provide opt-in helper-script deduplication across nested Content trees.

The Symbol distinction is a useful refinement, but the update introduces two confirmed compatibility issues. Risk: Standard — this affects SSR behavior and public SDK exports. 🔴 HIGH: ContentVariants now unconditionally consumes React context despite being generated as an RSC server component. 🟡 MEDIUM: the React Native-specific export override does not expose the newly advertised BuilderScripts export. These should be addressed before merge.

🧪 Browser testing: Skipped — dev server unavailable; dependency setup fails while building isolated-vm, and no dev command is configured. Retried once; server remained stopped.

Comment thread packages/sdks/src/index-helpers/blocks-exports.ts
builder-io-integration[bot]

This comment was marked as outdated.

@builder-io-integration builder-io-integration Bot left a comment

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.

Builder reviewed your changes — no new findings

Review Details

Incremental Code Review Summary

This update adds the missing BuilderScripts import and export to the React Native-specific SDK entrypoint, resolving the prior React Native API-availability issue. Two independent code reviews found no new actionable defects in the current changes, including the React SSR-versus-hydration helper behavior. The React Native review thread has been resolved.

The prior RSC issue remains unchanged: ContentVariants is still an RSC server component that unconditionally invokes useContext, so its existing review comment remains open. I have not reposted it. Risk: Standard — the feature affects SSR script injection across SDK targets; the outstanding high-severity RSC finding remains blocking.

🧪 Browser testing: Skipped — setup fails building isolated-vm; proxy is stopped and no dev command is configured. Restart was attempted once and the server remained unavailable.

This branch has not been deployed

No deployments
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