fix: disableOffers now suppresses product:price:amount in SSR#47
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Hi! I'm VTEX IO CI/CD Bot and I'll be helping you to publish your app! 🤖 Please select which version do you want to release:
And then you just need to merge your PR when you are ready! There is no need to create a release commit/tag.
|
|
Beep boop 🤖 I noticed you didn't make any changes at the
In order to keep track, I'll create an issue if you decide now is not a good time
|
There was a problem hiding this comment.
Pull request overview
Fixes an SSR-only bug where disableOffers was ignored and product:price:amount could still appear in the final server-rendered HTML due to multi-pass getDataFromTree + react-helmet accumulation.
Changes:
- Run the app settings query during SSR (
ssr: true) and expose the queryloadingstate fromuseAppSettings. - Gate
ProductOpenGraphrendering while settings are loading to prevent early Helmet registration. - Document the fix in the changelog (plus a minor GraphQL formatting change).
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| react/hooks/useAppSettings.ts | Enables SSR execution of the settings query and returns loading to callers. |
| react/ProductOpenGraph.tsx | Avoids rendering Helmet tags until settings are available, preventing stale SSR meta tags. |
| react/queries/getSettings.graphql | Minor formatting change. |
| CHANGELOG.md | Adds an Unreleased entry describing the SSR suppression fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
🔴 This branch breaks all 6 existing unit tests (
|
| Ref | Commit | Result |
|---|---|---|
master (this PR's base) |
6af9570 |
✅ 6 passed / 6 |
| this branch | 2495c312 |
❌ 6 failed / 6 — Unable to find an element by: [data-testid="…"] |
So this is a regression introduced by the PR, not pre-existing breakage.
Root cause. ProductOpenGraph now early-returns while settings are loading:
if (settingsLoading || !productContext?.product) {
return null
}The existing tests render the component with only a ProductContext.Provider and no Apollo mock for the getSettings query. Now that useAppSettings exposes loading from useQuery, settingsLoading stays true for the whole test, the component renders null, and every getByTestId(...) assertion fails.
This is specifically the gate — not a missing Apollo client. The harness does supply Apollo via MockedProvider; on master useQuery resolves to loading with no data and the component still renders fine. It only becomes fatal once the component gates rendering on settingsLoading.
Suggested resolution (pick one):
- If
ssr: truealone suppresses the tag, drop thesettingsLoadinggate — that restores the tests with no further change. Worth confirming on a freshly cache-busted / new workspace first, to rule out render-server SmartCache lag (cached SSR HTML can serve stale settings for up to ~30 min) as the reason anssr: true-only build appeared to still leak. - If the gate is genuinely needed, update the suite to wrap renders in a
MockedProviderthat resolvesgetSettings, and add a regression test assertingproduct:price:amountis omitted whendisableOffers: trueand present whenfalse— that is the behavior this PR fixes and it is currently untested.
Either way, the IO app test job needs to be green before merge.
- Gate settingsLoading only during SSR (!canUseDOM) to avoid blocking client renders and breaking tests that lack an Apollo settings mock - Export canUseDOM=true from the render-runtime mock so the guard is correctly bypassed in the test environment - Wrap JSON.parse in try/catch in useAppSettings to prevent a malformed settings payload from crashing SSR with a 500 - Add regression tests: disableOffers=true omits product:price:amount, disableOffers=false keeps it Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes prettier lint error in the disableOffers regression test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Your PR has been merged! App is being published. 🚀 After the publishing process has been completed (check #vtex-io-releases) and doing A/B tests with the new version, you can deploy your release by running:
After that your app will be updated on all accounts. For more information on the deployment process check the docs. 📖 |
Problem
The
disableOfferssetting was being ignored during SSR: theproduct:price:amountmeta tag still appeared in the server-rendered HTML even when the flag was set totrue.Root cause: The render-runtime SSR pipeline runs
getDataFromTree(react-apollo) in multiple passes before the finalrenderToString.react-helmetaccumulates all meta tags registered across every pass and never discards them —Helmet.rewind()at the end returns the union of all passes.The settings query was running with
ssr: false, sodisableOfferswas alwaysfalse(default) during SSR passes. On the pass where the product was already loaded, the price meta tag was registered in the Helmet state. Subsequent passes with the correctdisableOffers = truecould not remove it.This was not a problem for
vtex.structured-databecause it emits a<script>directly in the React tree — only the finalrenderToStringoutput matters, intermediategetDataFromTreepasses are discarded.Fix
Two changes:
useAppSettings.ts: changedssr: false→ssr: trueand exposedloadingfrom the query.ProductOpenGraph.tsx: gate the entire component onsettingsLoading— returnnullwhile settings are in flight. This prevents any Helmet registration beforedisableOffersis known.With
ssr: true,getDataFromTreeexecutes the settings query before the final render. On the first pass (settings loading), the component returnsnull— nothing is added to Helmet. On the resolved pass,disableOffershas the correct value and the price tag is either included or omitted accordingly.How to test it:
Workspace
Default behavior is still working in this workspace with the changes: