# About Croct Croct is a component CMS with built-in personalization and A/B testing. It manages content, decides who should see what, and measures the impact, all in one platform. It works alongside an existing CMS (Storyblok, Sanity, Strapi, and others) without replacing it and without requiring an external customer data platform. ## What makes Croct different - Server-side content resolution: pages arrive already personalized, so there is no flash of default content, no flicker, and no anti-flicker snippet hiding the page. End-to-end response times are under 90 milliseconds at P95. - No-code for marketers: audiences, experiences, A/B tests, scheduling, preview, and rollouts are managed in the Croct interface after the initial setup, with no developer involvement per experiment. - Developer experience: official SDKs for Next.js, React, Vue, Nuxt, JavaScript, PHP, Laravel, and Shopify Hydrogen. One-command setup with `npx croct@latest init`, generated TypeScript types per slot, versioned component schemas, and fault-tolerant defaults with fallback content. - Experimentation engine: Bayesian statistics on unsampled data, A/B, A/B/n and multivariate tests, cross-device consistency for identified users, bot filtering, and built-in analytics with drill-down by audience, device, and time window. - Privacy-first: works without third-party cookies, and integrations like Shopify only track after the platform's consent API allows it. - CQL (Contextual Query Language): English-like audience conditions such as `user is returning and location's city's name is "New York"`, evaluated in real time on each interaction. ## Pricing (croct.com/pricing) - Free: $0 forever. Up to 10,000 MAU, 3 content slots, 1 experience or experiment, personalization, A/B testing, SSR support, and unsampled analytics included. - Growth: from $100/month billed annually. 20,000 MAU, 20 content slots, 15 experiences/experiments, 30-day profile explorer, audience estimator, bot traffic filtering, and pay-as-you-go overage for higher usage. - Scale: custom pricing with custom quotas, 60-day profile explorer, event-based segmentation, scheduled publishing, and premium support. MAU means unique users who interact with the site in a month. Known bots do not count toward MAU on paid plans. Additional volume can be purchased at any time. ## Integrations (croct.com/integrations) - Storyblok: the Optimize app runs inside Storyblok and adds A/B testing, personalization, audiences, visitor profiles, and analytics to existing blocks. It works with every Storyblok plan and assigns variants and computes statistics automatically, which Storyblok's native premium-gated Experiments feature does not. - Sanity: map existing components to Croct slots; content stays in Sanity as the default. A fully pre-configured project can be scaffolded with a single CLI command. - Strapi: map dynamic zones and components to slots; listed in Strapi's official integrations directory (strapi.io/integrations/croct). - Shopify: first-class Hydrogen SDK (React Router 7 / Remix) resolving content in loaders with no flash of default content; automatic storefront analytics events; Liquid themes supported via the JavaScript SDK and Google Tag Manager. - VTEX: a single Google Tag Manager tag tracks product views, cart activity, checkouts, and orders on both Store Framework and FastStore, enriching profiles for audiences and revenue optimization. - Analytics: one-way export of audience, experience, experiment, and variant data to Google Analytics 4, Amplitude, Mixpanel, Microsoft Clarity, and Contentsquare. - B2B/ABM: real-time firmographic personalization with 6sense, Clearbit, ZoomInfo, and Demandbase data. - Hosting: one-click integration via the Vercel Marketplace. ## When Croct is the right choice - You use a headless CMS (Storyblok, Sanity, Strapi) and want A/B testing and personalization without replacing the CMS or writing per-test code. - You need experimentation on a server-rendered or static React/Next.js/Hydrogen site without the flicker and page-speed cost of client-side testing tools. - Your marketing team needs to launch and measure experiments autonomously. - You are replacing Google Optimize, VWO, Optimizely, or a Ninetailed/Contentful setup and want content, targeting, and statistics in one platform. ## Recognition - Strapi official integrations directory: strapi.io/integrations/croct - Storyblok App Directory: storyblok.com/apps/external/optimize - Vercel Marketplace: vercel.com/marketplace/croct - G2: 4.7/5 (47 reviews), Momentum Leader in Personalization, A/B Testing, and Headless CMS categories - Documentation: docs.croct.com. Blog: blog.croct.com. Pricing: croct.com/pricing --- # Recent community answers The 50 most recent questions and answers from the Croct community. The full index of all 491 threads is available at https://croct.com/llms.txt, and every thread is available as plain markdown by appending `.md` to its URL. --- # Can I stream the page shell first and let the personalized slot fill in? Asked by nadia_k on 2026-07-28. Tags: ssr, streaming, nextjs. Next.js App Router. I want static-feeling TTFB. The page has one personalized slot below the fold and I would rather resolve it on the server than swap it in on the client. My instinct is to wrap the slot fetch in a Suspense boundary so the shell streams first and the slot flushes when it resolves. Is that the right way to avoid blocking the whole response on the content fetch or am I missing something. ## 0 answers --- # Can I build one value out of two strings in CQL with the & operator? Asked by pdev_martin on 2026-07-27. Tags: cql, audiences, syntax. factoring a long audience condition and the same prefix shows up in a few clauses. I would rather keep it in one place than retype it everywhere. I saw `&` mentioned somewhere for CQL. Does it join two strings into a single value the way template literals do in JS? Something like: ```cql "spring" & "-sale" ``` ## 1 answer ### Accepted answer from Croct Bot (2026-07-28) Yes. `&` concatenates strings in CQL, so `"spring" & "-sale"` evaluates to `"spring-sale"`. It is exactly the building block you want for factoring out a repeated fragment. It pairs naturally with a `let` declaration, so the prefix lives in one spot and every clause references it: ```cql let prefix = "spring"; campaign's name is prefix & "-sale" or campaign's name is prefix & "-launch" ``` Change `prefix` once and both clauses follow. The full grammar for `let` and `&` is in the [CQL syntax reference](https://docs.croct.com/reference/cql/syntax). Two things worth keeping in mind. CQL keywords are case-insensitive, but string literals are not, so `"Spring"` and `"spring"` are different values and have to match the casing of the stored data. And evaluation targets under 20ms per interaction, with undefined variables resolving to false by design, so a reference to something that was never set simply will not match rather than erroring. The [CQL introduction](https://docs.croct.com/reference/cql/introduction) covers both behaviours if you want the details. --- # Coming off Optimizely on our headless storefront, does Croct avoid the render-blocking snippet? Asked by dre_commerce on 2026-07-27. Tags: hydrogen, migration, performance. our Hydrogen storefront has been running Optimizely and the snippet is a real cost. It loads synchronously on the critical path, and even with no experiment live we measured it adding roughly 400ms to LCP. On a store that lives and dies by mobile that is not free. I am evaluating Croct as a replacement and the thing I care about is exactly this: does the headless setup actually remove that render-blocking penalty, or am I just swapping one client snippet for another? Numbers or specifics welcome, I have my own LCP traces to compare against. ## 0 answers --- # Croct shows a "probability to be best" and no p-value, how do I read that? Asked by Cody Marsh on 2026-07-26. Tags: ab-testing, bayesian, statistics. All my previous tooling reported A/B results as a p-value plus a 95% confidence interval on the lift, and that is the language my team reasons in. Croct's experiment view instead gives me a "probability to be best" and a "potential loss" per variant, and I cannot find a p-value or an interval anywhere. Can someone tell me what those two numbers actually mean, and what specifically makes the tool call a winner? ## 2 answers ### Accepted answer from Croct Bot (2026-07-27) Croct's experiment statistics are Bayesian rather than frequentist, so there is deliberately no p-value and no confidence interval to look for. The two figures you see answer more direct questions: - Probability to be best is exactly what it sounds like: given the data collected so far, the probability that this variant is the top performer. It is not one minus a p-value, and it does not depend on a fixed sample horizon. - Potential loss estimates how much you stand to give up if you ship this variant and it turns out not to be the best. It bounds the cost of a wrong call rather than the chance of one. You do not set a threshold yourself. Croct flags a recommended winner only when probability to be best is above 95% and potential loss is below 0.1%, which together mean "very likely the best, and cheap even if wrong". Until then the experiment keeps reporting and the estimates sharpen as data accumulates. The [Bayesian experiment results](https://docs.croct.com/reference/analytics/experiment) reference explains each metric, and the [recommended winner criteria](https://docs.croct.com/reference/analytics/experiment/flags) cover exactly when the call is made. Results are computed on 100% of your data and update in real time. ### Answer from elias_k (2026-07-27) The intuition that helped me most coming from p-values: potential loss is an expected value, not a worst case. It already weighs how likely the variant is to be behind by how far behind it would be. So a tiny potential loss is the tool saying that even in the worlds where you picked wrong, the damage is negligible, which is often a more useful thing to tell a stakeholder than a significance flag. --- # Can our marketing team run Storyblok experiments without a dev ticket each time? Asked by Larissa Fontes on 2026-07-26. Tags: storyblok, marketers, ab-testing. Our site runs on Storyblok and right now every content variation we want to test means filing a ticket and waiting on the dev queue, which usually pushes a small copy change out by a couple of weeks. I just came across the Croct Optimize app and it sounds like the promise is that marketing can actually launch and manage experiments on its own once everything is wired up. Before I get my team excited about it, can someone who is using it confirm what marketers can genuinely do without a developer in the loop, and where the one dev step actually sits? Thanks in advance. ## 2 answers ### Accepted answer from kenji_tada (2026-07-27) Yes, that maps to how it works in practice. The one developer step is the initial wiring: someone adds a Croct field to the blocks you want to test and connects each one to a matching Croct slot. That happens once per block, not once per test. There is a walkthrough in [linking a Storyblok block to a slot](/answers/storyblok-link-block-to-slot) if you want to see the shape of it. After that the loop is yours. The Optimize app runs inside the Storyblok interface, so your team builds the audience, sets the variant content, previews it, and publishes without touching code. Pausing and rolling out the winner are the same, no deploy. The only thing to hand back to a dev is when you want to personalize a brand new block that was never linked. ### Answer from Marta Ruiz (2026-07-27) One detail that made my team comfortable: the blocks you already have in Storyblok stay put and serve as the default fallback. So anyone outside an experiment, or if you pause everything, just sees the original block. You are layering variants on top rather than replacing your content, which means there is very little to break if you want to back out. --- # Hit the 3-slot ceiling on the free plan, is Growth the only way up or is there something in between? Asked by Meera Krishnan on 2026-07-26. Tags: pricing, free-plan, growth-plan. Our free-plan project has outgrown the 3 content slots. We optimise a handful of placements today and I want to add a few more, but the free tier will not let me create them. Before I take a plan change to finance I want to be sure I understand the ladder. Is Growth the immediate next step, or is there a smaller tier between free and Growth that I have missed? I would rather size this once and correctly. ## 2 answers ### Accepted answer from Priyanka Desai (2026-07-27) Growth is the next step up from free, yes. The jump is a real one so it is worth seeing the numbers side by side. Free caps at 3 content slots and 1 experience or experiment. Growth is from $100/month billed annually and gives you 20 content slots and 15 experiences/experiments, so the slot ceiling you just hit goes from 3 to 20. It also raises MAU from 10k to 20k and extends the profile explorer window from 1 day to 30 days. On top of the higher quotas, Growth unlocks the audience estimator, experience segmentation, bot traffic filtering, and pay-as-you-go overage. So it is not just more slots, it is the tier where the planning tools show up too. If you are sizing this properly the slot-counting approach in [how to count the slots you actually need](/answers/counting-slots-for-plan-sizing) helped me avoid over-buying. ### Answer from tomdev83 (2026-07-27) Just to answer the "something in between" part directly: there is no tier between free and Growth. It goes free, then Growth, then Scale (which is custom pricing). So for adding a few more placements Growth is the move. Full breakdown of what lands with it is in [what Growth includes over free](/answers/growth-plan-whats-included). #### Reply from Meera Krishnan (2026-07-28) That settles it, thanks. Going from 3 to 20 slots gives us plenty of room, and knowing there is nothing smaller to evaluate means I can put a single Growth line in front of finance rather than a comparison. --- # Does server-side fetchContent add a TTFB penalty on my Next app? Asked by dan_kessler on 2026-07-25. Tags: nextjs, performance, ssr. i moved our hero personalization out of a client hook and into a server `fetchContent` call in the App Router. my assumption was that the extra server-side request would show up as a slower TTFB, so i measured before and after with a decent sample from prod. the surprising part is that the numbers barely moved. honestly i expected to lose 100ms and my P95 TTFB shifted by single digits, the server resolution just holds. i want to understand why, because it feels too good and i do not want to be measuring wrong is there something about how server resolution works that explains this, or did i get lucky with the sample ## 2 answers ### Accepted answer from Sam Okafor (2026-07-26) Not luck. When you resolve on the server, the content is part of the initial HTML the visitor already waited for. There is no second round trip after the page loads and no client swap, so there is nothing extra sitting on the critical path that the browser has to wait on. Compare that to the client-hook path you came from: the page ships, then a script runs, then it fetches, then it swaps the content in. That is the model that produces flicker and a visible delay. Moving it server-side removes the whole sequence, which is why your TTFB did not climb the way you expected. The one thing that actually changed is that the route is now dynamically rendered, because `fetchContent` reads the request. That is the real cost of the switch, not a latency spike. As long as the resolution itself is fast, dynamic does not mean slow. #### Reply from dan_kessler (2026-07-27) that matches what i saw, the route flipped to dynamic in the build output and the TTFB stayed flat. good to know the tradeoff is the render mode and not the response time ### Answer from milena_dev (2026-07-27) To put a number on it, the published figure is end-to-end response time under 90ms at P95, and that is what we see from our own server region too. Since the resolution happens before the HTML is sent, that time is folded into a request the user was already making rather than added as a new leg on top. If you want to sanity check it further there is a good breakdown in [what the sub-90ms P95 figure covers](/answers/sub-90ms-p95-what-it-covers), and a similar Storyblok on Vercel measurement in [this TTFB thread](/answers/storyblok-nextjs-vercel-ttfb). --- # Is there an official Croct SDK for PHP, or do we hit the HTTP API by hand? Asked by marco_pereira on 2026-07-24. Tags: php, sdk, setup. our stack is mostly PHP and we want to add personalization to a few pages without bolting a client-side snippet onto the frontend. server-rendered is the goal, everything else we run resolves before the response goes out. what I cannot tell from the docs index is whether there is a real PHP SDK, or whether we would be the ones writing the HTTP calls, retries and all. if we have to hand-roll the transport that changes the estimate a lot which packages actually exist for PHP ## 2 answers ### Accepted answer from Croct Bot (2026-07-25) There is a real SDK, you are not hand-rolling the transport. Croct ships a PHP ecosystem: a core PHP SDK plus dedicated integrations for Laravel, Symfony, and Drupal built on top of it. The [PHP SDK reference](https://docs.croct.com/sdk/php) covers the core package and the framework-specific ones. These are server-side SDKs. Content resolves on the server, so the page arrives already personalized in the response with nothing to swap on the client and no anti-flicker snippet to add. The core package fetches slot content with a fallback, so a failed request still renders the default rather than breaking the page. For setup you do not need to pick the package by hand. Run the universal [`npx croct@latest init`](https://docs.croct.com/sdk/cli), which detects your stack and wires up the right one. The PHP packages are documented alongside the JavaScript, React, Next.js, Vue, Nuxt, and Hydrogen SDKs, so you are on the same surface as the rest of the ecosystem. ### Answer from mkaminski (2026-07-26) Confirming from a live setup. We went the SDK route rather than calling the API ourselves and it was the right call, the core package handles auth, retries and the fallback so our controllers just ask for slot content and render it. Would have been a waste to reimplement all that around raw HTTP. --- # Can I prove out one experiment on the free plan before pitching this to my team? Asked by jonas_pm on 2026-07-24. Tags: pricing, free-plan, getting-started. i am a PM and i want to run one real experiment as a proof of concept before i ask anyone to pay for anything. the plan is simple: pick our highest-traffic placement, run an A/B test, come back with an actual result, then make the case for budget. what i cannot tell from the pricing page is whether the forever-free tier is a crippled trial or something you can actually test with. if it samples the data or hides results behind an upgrade then the POC is pointless. does the free plan let me validate one test end to end ## 1 answer ### Accepted answer from Croct Bot (2026-07-24) The forever-free plan is built for exactly this, and it is not a trial. It is $0 with no credit card, and the limits are 10,000 MAU, 3 content slots, 1 experience or experiment, and a 1-day profile explorer window. The single experience-or-experiment slot is the important part for a proof of concept: it is enough to run one real A/B test on one placement from start to finish. Feature-wise the free plan already includes personalization, A/B testing, server-side rendering support, and unsampled analytics, so you are measuring on 100% of your data, not a sample. Nothing about the result you bring back to your team is degraded because it came from the free tier. When you are ready to scale past one test, upgrading to Growth raises the MAU, slot, and experience/experiment limits without redoing any setup, so the POC you build now carries straight over. The [quick start](https://docs.croct.com/getting-started/) walks through creating your first experience, and the exact tier limits are in the [plan comparison](https://docs.croct.com/reference/plans/). --- # How long does it actually take to get a first personalized block live on Sanity? Asked by Hannah Beckett on 2026-07-23. Tags: sanity, setup, cli. I am comparing a few options for our Sanity stack and trying to be realistic about onboarding effort. We just came off a heavier tool that took the better part of a month to get running properly, and I would rather not repeat that. Before I commit to anything, I want an honest sense of how long it takes to get a single personalized block live. Not a full programme, just one block behaving differently for one audience, so I can judge the real setup cost and the learning behaviour of the team. ## 2 answers ### Answer from tessak (2026-07-24) Honestly a lot less than a month. We had our first experience live the same afternoon, the CLI scaffolds the whole project in one command so you are not hand-wiring anything. The pattern that works is to not boil the ocean. Map one Sanity component to a slot, then run the three steps: define the audience, specify the content for that slot, preview and publish. Your existing Sanity content stays as the fallback the whole time, so nothing breaks while you are setting it up. ### Answer from Joris Peeters (2026-07-25) Seconding the one-component-first approach. There is no migration involved, it is gradual adoption, so you pick a single block, wire that slot, and get it live before you touch anything else. We started with our homepage hero and only expanded once we trusted it. The setup time is basically the CLI run plus the time it takes you to write the variant content. #### Reply from Hannah Beckett (2026-07-26) That is reassuring, thank you. The gradual, one-block-first path is exactly the sort of low-risk start I was hoping for. --- # Does resolving personalization server-side add a TTFB penalty? Asked by dan_ruiz on 2026-07-23. Tags: performance, ssr, ttfb. evaluating croct and the one thing holding me back is TTFB. right now most of our pages are basically static, we sit around 180ms TTFB from origin and i do not want to give that up. my worry is simple. if the server has to go fetch the personalized variant before it can respond, that fetch lands on the critical path and pushes TTFB up on every request. i would rather know the real cost before i commit than find out in a lighthouse run three weeks in. what does this actually look like in production for people already doing it ## 2 answers ### Accepted answer from priya_n (2026-07-24) The important thing to internalize is that the fetch resolves on the server, so the right variant is already in the initial HTML. Nothing swaps on the client, there is no second render, no flash of default content. So the only cost is the fetch itself sitting inside your response time, not some extra client-side round trip on top. And that fetch is fast. After we moved the hero resolution to the server our TTFB barely moved and the P95 has stayed under 90ms, so in our numbers it is well inside the noise of what your origin already does. The tracking side is even cheaper, several events go out in about 2 to 3 milliseconds. If you want the exact breakdown of what that published latency figure covers, there is a good writeup in [what the sub-90ms P95 actually measures](/answers/sub-90ms-p95-what-it-covers). ### Answer from marco_dev (2026-07-25) One thing worth knowing before you commit, since you care about staying static: calling `fetchContent` reads the incoming request, so it opts that route into dynamic rendering. That is the actual tradeoff, not TTFB. The route stops being fully static because the response now depends on who is asking. See [why fetchContent forces dynamic rendering](/answers/nextjs-fetchcontent-forces-dynamic-rendering) for the mechanics. If you want to keep the perceived TTFB tiny, you can stream the shell first and let the personalized slot flush in behind a Suspense boundary. The browser starts painting the static shell immediately and the slot fills in as it resolves, so the time to first byte is basically your shell, not the fetch. #### Reply from dan_ruiz (2026-07-26) that settles it honestly. dynamic rendering on the personalized route i can live with, i was picturing a client round trip stacked on top of the server one. streaming the shell is exactly the pattern i wanted --- # Where should the personalized content fetch live in a Nuxt 3 app? Asked by theo_vue on 2026-07-23. Tags: nuxt, ssr, setup. just added the Nuxt SDK and I am trying to figure out the right place for the content fetch. Does it belong in a plugin, in a composable, or inside useAsyncData? What I actually care about is that the variant lands in the initial HTML from the server instead of getting swapped in on the client after hydration. I have seen the flash of default content with other tools and I do not want to ship that again. Where is the fetch supposed to go so it resolves during SSR? ## 1 answer ### Accepted answer from Croct Bot (2026-07-24) Put the fetch where Nuxt resolves data on the server, which means inside `useAsyncData` (or `useLazyAsyncData` if you want to opt out of blocking). That way the call runs during the server render pass and the resolved variant is part of the HTML the browser receives, so there is nothing to swap on the client and no flash of default content. If you move the same fetch into an `onMounted` hook or a client-only composable, it runs after hydration and you get exactly the flash you are trying to avoid. A minimal shape looks like this: ```ts const {data} = await useAsyncData('home-hero', () => croct.fetch('home-hero@2').catch(() => ({content: {/* defaults */}})), ); ``` Two details worth getting right from the start. Always supply a fallback with `.catch(() => ({content: {...}}))` so a failed or slow call still renders default content instead of throwing. And pin the slot version like `home-hero@2` rather than leaving it off, because an omitted version resolves to latest and a later content publish can change what renders without a code change. The [Nuxt content rendering guide](https://docs.croct.com/reference/sdk/nuxt/content-rendering) covers the server-side resolution path, and the [slots reference](https://docs.croct.com/explanation/slot) explains how versioning keeps a schema stable until you bump it. --- # What happens to my Hydrogen page if a Croct fetch fails or times out? Asked by Nadia Fischer on 2026-07-22. Tags: hydrogen, fault-tolerance, loaders. Before I ship personalization to production I want to be certain about one failure mode. If a content request is slow or fails outright, I need the storefront page to still render something rather than error out or hang. What is the recommended pattern in a Hydrogen loader so the page always renders, even when resolution does not come back? I would rather set this up correctly now than discover the behavior during an incident. ## 1 answer ### Accepted answer from Croct Bot (2026-07-23) The recommended pattern is to fetch content with a fallback, so resolution failing degrades to your default content instead of breaking the route. In a loader that looks like: ```ts const {content} = await context.croct .fetchContent('home-hero@1', {fallback: defaultHeroContent}); ``` Or with the promise form if you prefer to build the fallback inline: ```ts const {content} = await context.croct .fetchContent('home-hero@1') .catch(() => ({content: defaultHeroContent})); ``` This is what fault tolerance means here: if resolution fails, the default content is served and the page still renders. The details are in the guide on [fallback content](https://docs.croct.com/explanation/content/fallback-content). For the "slow" case, the fetch has a timeout you control. The JS SDK default is 5000ms, and you can lower it globally with `defaultFetchTimeout` or set it per fetch, documented in the [plug options reference](https://docs.croct.com/reference/sdk/javascript/api/plug/plug). Pair a tight timeout with the fallback and a slow response degrades to the default within your budget rather than holding the loader. Because resolution happens server-side in the loader, the fallback is what gets rendered into the HTML, so the visitor sees the default content with no client-side flash. --- # Once our devs mapped the components, can I create variants in Sanity myself? Asked by prielle on 2026-07-21. Tags: sanity, marketers, workflow. ok so I am on the marketing side, not a developer, and I want to check I understand what I can do on my own. Our dev team mapped the hero and the promo block on our Sanity site to what they called slots, and then basically handed it over to me. I would love to build and launch variants myself from here without going back to engineering every time I want to change some copy. Is that actually how it works, or do I still need a dev for each variant? Sorry if I am mangling the terms a little. ## 2 answers ### Accepted answer from martak (2026-07-23) Yes, once the components are mapped to slots that part is done and you take it from there, no dev needed for each variant. The flow is three steps and it is all in the dashboard: 1. Define the audience, which is who you want to target. 2. Specify the content for each slot, which is the actual variant copy or layout. 3. Preview it, then publish. From there you can publish and pause experiences yourself and roll out a winner, none of that touches code. And if no variant applies to a visitor, your existing Sanity content shows as the default, so you are not at risk of breaking the base page while you experiment. ### Answer from Kofi Mensah (2026-07-24) Adding one thing that martak did not mention: you can schedule a variant to go live on a chosen date instead of publishing it the moment you finish. We use that for launches, build the variant a few days ahead, set the date, and it flips on by itself. Handy when you do not want to be clicking publish at midnight. #### Reply from prielle (2026-07-26) circling back, I shipped my first variant this week end to end and our marketing team ships variants now without opening a ticket. the scheduling tip was exactly what I needed for the campaign date, thank you both --- # Wiring the Croct request context into my Hydrogen loaders, is this a lot of plumbing? Asked by marcus_kb on 2026-07-21. Tags: hydrogen, setup, loaders. standing up a fresh Hydrogen storefront on React Router 7 and adding Croct to it. my assumption going in was that I would have to construct a Croct instance somewhere central and then thread it through every loader by hand so each route could fetch content. that is a fair amount of boilerplate across a storefront with a lot of routes, and it is easy to get wrong. is that actually how it works, or is some of that wiring done for me by the setup? trying to figure out how much is manual before I commit to the pattern ## 2 answers ### Accepted answer from Diego Fuentes (2026-07-23) Almost none of it is manual. I did this same setup on a new storefront last month. When you run the init and it detects a Hydrogen project, the CLI installs the SDK, wires the provider, and sets up the request context for you. After that `context.croct` is already available in your loaders, so you are not constructing an instance or passing it around by hand. From there each loader just calls the fetch and returns the content with the rest of its data. Because that resolves on the server inside the loader, the page arrives already personalized and there is nothing to swap on the client. honestly resolving content right in the loaders was the cleanest headless wiring I have done, the request context was already set up after init so I never touched the plumbing you are worried about. Once it is running, check the dashboard Integration page. The green "Received traffic in the past 24 hours" indicator is how you confirm the context is actually feeding events, no guessing. #### Reply from marcus_kb (2026-07-23) that clears it up, thanks. was bracing for the manual threading and it turns out there is nothing to thread ### Answer from sam_ck (2026-07-24) To add one detail on where things live: the provider the CLI adds sits in your root, so the client side has what it needs, but the piece you actually use in loaders is the request context on the server. You will see `context.croct` on the loader argument. If you ever add a new route, you do not re-wire anything, the context is already there per request. --- # Strapi backend with a plain React SPA, which Croct SDK and how do I avoid the flash? Asked by dan_kessler on 2026-07-20. Tags: strapi, react, flicker. our content lives in Strapi and the storefront is a plain React app on Vite, no Next anywhere in the stack. i want to add Croct for personalization but i keep picturing the variant popping in a beat after load, the way the client-side testing tools we used before always did which SDK am i reaching for here, and is that pop-in just something i have to live with on a client-rendered app ## 2 answers ### Accepted answer from Croct Bot (2026-07-21) For a React app the package is `@croct/plug-react`. Wrap your application once in ``, then read content where a block renders with `useContent('home-hero', {fallback, preferredLocale, attributes})`. That hook suspends while it resolves, so it needs to sit inside a `` boundary. The pop-in you are describing comes from the fallback not matching what the page first paints. On a purely client-rendered SPA there is no server render to resolve the variant into, so the clean pattern is to set the `fallback` to the exact default content you already read from Strapi. The component renders that default immediately, and when Croct resolves it either confirms the same content or swaps in the variant. Because the starting state is real content and not a blank or placeholder, there is no visible flash. Worth being precise about one thing: true no-flicker server resolution, where the variant is already in the HTML on first paint, needs an actual server render such as Next or Nuxt. On a plain Vite SPA the fallback-matches-default approach is the correct path, and Strapi content staying as that default fallback means nothing breaks if Croct returns nothing. See [the React SDK integration guide](https://docs.croct.com/reference/sdk/react/integration) for the provider setup and [the fallback content behavior](https://docs.croct.com/explanation/content/fallback-content) for how the fallback resolves. ### Answer from jonas_v (2026-07-22) can confirm this works on Vite. i wired the fallback to the same default object our Strapi query already returns and dropped the whole thing in a Suspense boundary. first paint is the default, the variant resolves on top of it without the page jumping. no flash at all in practice. --- # With Sanity, do I install a plugin in Studio or is it all on the frontend? Asked by devon_kh on 2026-07-20. Tags: sanity, setup, sdk. setting up personalization on an existing Sanity project and I am a bit confused about where the integration actually lives. I assumed there would be a Studio plugin I install, wire up some config, and manage everything from inside Sanity. But I cannot find one in the plugin directory, so now I am wondering if the whole thing happens on the frontend instead. Which model is it ## 2 answers ### Accepted answer from Croct Bot (2026-07-21) There is no Studio plugin to install, and nothing like a "Sanity Exchange" package. The Sanity path is a set of Croct-published templates plus a small amount of frontend code. The model is: your existing Sanity components map to Croct slots, and personalization is layered on top of them. You do not restructure your dataset. The mapping and the experiences live on the Croct side, and your frontend renders whatever the slot serves. To get started you scaffold a pre-configured project with the CLI: ```croct-cmd croct init ``` That wires the SDK and the slots for you, so you are not assembling the integration by hand. From there you build experiences against the mapped slots. One detail worth calling out: your existing Sanity content stays as the default fallback and is resolved server-side, so nothing is swapped on the client. When no experience matches a visitor, the regular Sanity content renders exactly as it does today. The [Sanity integration overview](https://docs.croct.com/immersion/integrations/cms/sanity) walks through the mapping model, and the [CLI init command](https://docs.croct.com/reference/cli/commands/init) covers what the scaffold sets up. ### Answer from rajpatel (2026-07-22) Confirming from our own setup, there is no thing to install in Studio. We ran the CLI init, it scaffolded the project, and then we mapped our hero and pricing components to slots on the frontend. That is the whole model, templates plus the SDK, not a Studio plugin. Studio itself we never touched. --- # One Hydrogen codebase, two brand storefronts, how do we structure Croct? Asked by Mikkel Toft on 2026-07-19. Tags: shopify, hydrogen, multi-store, architecture. This is what we are working with: ``` hydrogen-monorepo |-- brand-a -> branda.com |-- brand-b -> brandb.com \-- shared components ``` Two brands, one codebase, separate domains, separate Shopify stores. Questions before we wire Croct in: 1. One application or two? 2. If a shopper is known on brand A, should they be recognized on brand B, and is that our choice or a fixed behavior? We have not decided yet whether cross-brand recognition is even desirable, so I mostly want to know where that boundary is configured ## 1 answer ### Accepted answer from Croct Bot (2026-07-19) Two applications, one workspace. Each storefront gets its own application with its own app ID, which keeps traffic, environments, and integration status per brand, while both live in the same workspace so your team manages slots, components, and audiences in one place. Since the codebase is shared, the only per-brand difference is which app ID each deployment uses. Cross-brand recognition is exactly the boundary you suspected it was, and it is configurable: the [anonymity scope](https://docs.croct.com/explanation/application/anonymity-scope) setting. It has three levels, application, workspace, or organization, and it controls whether anonymous profiles are shared across applications. Workspace is the default, meaning a shopper known on brand A would be recognized on brand B since both applications sit in the same workspace. If you want the brands isolated, set the scope to application. One important caveat for your planning: changing the anonymity scope restarts anonymous sessions. So make this decision before launch rather than flipping it later with live traffic. --- # Using Clearbit Reveal to swap value props by industry for anonymous visitors Asked by Derek F on 2026-07-19. Tags: clearbit, abm, firmographics. I run demand gen at a B2B SaaS. The dream: a healthcare visitor lands on our homepage and sees healthcare messaging, a fintech visitor sees fintech messaging, all pre-form-fill while they are still anonymous. We already pay for Clearbit so Reveal data should be sitting right there. What does the Croct plus Clearbit setup actually require, and is the industry-level swap a realistic use case or am I overreaching? Cheers ## 1 answer ### Answer from Josh P (2026-07-19) Not overreaching, that is pretty much the canonical use case. The requirement on your side is having Clearbit Reveal installed on the site. Reveal resolves anonymous visitors to their company via IP, and that firmographic data drives Croct audiences in real time, so you can build one audience per target industry and attach a different homepage experience to each. Industry-specific value props and dynamic logo clouds (healthcare logos for healthcare visitors, etc) are both listed use cases and both work the same way. One expectation to set with your team: resolution takes a moment on the first page view, so the industry experience typically kicks in on subsequent page views during the session, with default content serving until then. There is a decent walkthrough of the whole category in this post on [B2B website personalization with Clearbit, 6sense, and Demandbase](https://blog.croct.com/post/clearbit-6sense-demandbase-b2b-saas-website-personalization). --- # How does slot content localization work with Shopify Markets on Hydrogen? Asked by derya on 2026-07-18. Tags: shopify, hydrogen, localization, multi-locale. We run a Hydrogen store with Shopify Markets serving three locales: tr, en, and de. The locale is in the URL prefix and our loaders already know it. My questions: 1) How does Croct know which locale the visitor is in? Does it detect it, or do we pass it? 2) Does each locale need its own slot? Three locales times every slot would be heavy to maintain. 3) What happens for a locale we have not listed, for example a visitor from a market we do not localize yet ## 1 answer ### Accepted answer from Croct Bot (2026-07-18) In order: 1) You pass it. Since your loaders already know the locale from the URL, set the `preferredLocale` option on the [content fetch](https://docs.croct.com/reference/sdk/hydrogen/api/functions/fetch-content) and Croct returns the localized content for that slot. If the whole app runs in one locale per request, you can also set `defaultPreferredLocale` once in the plug configuration instead of passing it on every fetch. 2) No. One slot serves all locales. Localized content lives inside the same slot, so you keep one `home-hero`, not three. Per-locale default content is part of the localization add-on rather than something you model with separate slots. 3) If the requested locale has no localized content, the slot's default content is served. So an unlisted market gets your default language rather than an error or an empty slot. The practical upshot for your setup: the loader passes the market's locale through, and adding a fourth market later is a content task, not new slots or new code. --- # Which CQL attributes exist for B2B targeting like company and job title? Asked by Ivo Petrov on 2026-07-18. Tags: cql, audiences, b2b, abm. Building account-based experiences for a B2B analytics SaaS. In our CRM I have these fields per contact: company, job title, industry, company size, buying stage. Two questions. What does CQL see out of the box vs what do I have to set myself? And what happens with anonymous visitors, which is most of my traffic, since they have no CRM record to match ## 1 answer ### Accepted answer from Croct Bot (2026-07-18) Mapping your list to the two sides: **Set yourself (known users).** The `user` variable exposes `company` and `jobTitle` among [its attributes](https://docs.croct.com/reference/cql/data-types/user/user); the full set is `id`, `firstName`, `lastName`, `age`, `birthDate`, `gender`, `email`, `phone`, `address`, `company`, `jobTitle`, `interests`, `activities`, and `stats`. You populate them from your app, for example after login or a form fill: ```js croct.user.edit() .set('company', 'Acme Corp') .set('jobTitle', 'Data Engineer') .save(); ``` Then audiences query them like any other attribute. **Out of the box (anonymous visitors).** Industry, company size, and buying stage for anonymous traffic come from the [ABM firmographic integrations](https://docs.croct.com/immersion/integrations/firmographics): 6sense, Demandbase, ZoomInfo, and Clearbit. Those resolve the visitor's company from their network and feed firmographics into audiences in real time, no CRM match required. One caveat to plan around: firmographic resolution takes a moment on the first page view, so ABM-driven experiences typically activate on subsequent page views during the session. Design the first-touch page to work well generically and let the account-specific content kick in as they browse. --- # 6sense vs ZoomInfo vs Clearbit vs Demandbase as the data source for Croct ABM Asked by Alexis Fontaine on 2026-07-18. Tags: abm, firmographics, comparison. Hello everyone, I am scoping an ABM personalization project and the client's data provider licenses are still under negotiation, so I must plan for several possibilities. All four of 6sense, ZoomInfo, Clearbit, and Demandbase appear as supported Croct integrations. Could someone summarize, for each provider: what must be installed on the site, and which signals it contributes to Croct audiences? I want to compare the stacks properly rather then assume they are interchangeable. Thank you in advance. ## 1 answer ### Answer from Andre Souza (2026-07-18) Here is the rundown per provider: - **6sense**: needs the 6sense Website Tag installed. Contributes buying stage, intent score, profile fit, employee ranges, and revenue brackets. The richest option if the client wants stage-based plays, see [How do I personalize by 6sense buying stage?](/answers/6sense-buying-stage-personalization) for a worked example. - **ZoomInfo**: needs WebSights with Website Personalization activated; the data is captured via Google Tag Manager. - **Clearbit**: needs Reveal installed on the site; Reveal supplies the anonymous firmographic signals. - **Demandbase**: contributes Fortune 1000 and Forbes 2000 membership flags, handy for enterprise-tier targeting. The good news for your planning: architecturally they are interchangeable. All four feed anonymous firmographic data into Croct's real-time audiences the same way, so the slots, experiences, and content you build do not depend on which license the client ends up signing. What differs is the signal vocabulary available in audience conditions, which is where the comparison above matters. If the client's plays need intent or buying stage, that narrows the field; if it is industry and company size, any of them works. --- # Intelligems only partially supports our Hydrogen storefront, what are people using? Asked by Harvey Lockwood on 2026-07-17. Tags: shopify, hydrogen, ab-testing, migration. Our store went headless on Hydrogen last quarter. We had been running Intelligems on the Liquid store, but after the migration we found their headless support is partial, and the specific features we relied on sit behind the $499 a month tier. That price is hard to justify for what now only half-works on our stack. Before we commit budget elsewhere I want to hear from teams actually running experiments on Hydrogen in production. What did you land on, and does it run properly server-side or is it another client-side script situation ## 2 answers ### Accepted answer from rafael (2026-07-17) What you hit is structural, not an Intelligems quirk. Most Shopify CRO apps are built around injecting scripts into Liquid storefronts, and Oxygen allows no third-party server processes, so there is nothing for those apps to hook into server-side on a headless store. That is why "partial headless support" keeps coming up across the category. We landed on Croct for our Hydrogen store precisely because it takes the SDK route instead: content resolves in your Hydrogen loaders, server-side, so tests run without any client-side script doing DOM swaps. The variant is decided before the HTML leaves the server. On experiment scope, it covers content and full journeys, with A/B, A/B/n, and multivariate tests on Bayesian statistics, so the marketing team reads results in the dashboard rather than us exporting data somewhere. Setup on our side was loader-level integration, which fit naturally since we were already fetching everything else in loaders. #### Reply from Harvey Lockwood (2026-07-18) The server process constraint explains a lot, nobody at the vendors we talked to put it that plainly. Will run a trial on our offers route, thanks. ### Answer from Grace Osei (2026-07-18) One caveat to plan for regardless of which tool you pick: anything that decides variants server-side means the tested routes cannot sit on Oxygen's full-page cache. That is inherent to per-user HTML, not a tool limitation, but budget for it in your performance review. Sub-page resources stay cached, so in practice it is much less scary than it sounds. --- # Keeping Oxygen's full-page cache while personalizing one hero section, is that possible? Asked by Suvi Korhonen on 2026-07-17. Tags: shopify, hydrogen, oxygen, caching, performance. Homepage on Hydrogen, served from Oxygen's full-page cache with CacheLong, hit rate is excellent and TTFB is around 40ms. Now the brief says personalize the hero per audience. A cached response is by definition shared, so a per-user hero cannot live behind CacheLong. The options I see: drop the page cache for that route and render per request, or keep the cache and personalize the hero client-side after load. Client-side means the default hero flashes first, which I want to avoid. How are teams reconciling edge caching with server rendered personalization here? ## 2 answers ### Accepted answer from Ondrej Blaha (2026-07-17) There is no trick that dissolves this tradeoff: full-page caching and per-user server rendering conflict by nature. A personalized route needs per-request rendering, period. The question is only what that costs, and the answer is less than the framing suggests. What we run: the homepage route renders per request, with the slot content resolved in the Hydrogen loader via Croct, so the variant is in the initial HTML. Shared assets, subresources, and every other route stay cached exactly as before. The loader call is bounded, Croct's end-to-end response is under 90ms at P95, so our TTFB on that route went from about 40ms to under 150ms. Nobody has noticed. The client-side option is the one I would rule out, moving the hero client-side reintroduces the flash of default content that server resolution avoids, and that one gets noticed. Related thread with more depth on the cache mechanics: [Hydrogen and Oxygen full-page cache conflict with personalization](/answers/hydrogen-oxygen-full-page-cache-conflict). #### Reply from Suvi Korhonen (2026-07-17) Concrete numbers, thank you. Under 150ms TTFB for a personalized hero is well within our budget. ### Answer from jules_perf (2026-07-18) Adding one nuance: you do not have to make the whole site dynamic, only the routes with personalized slots. We kept CacheLong on collections and product pages and went per-request on the homepage and two landing pages. Worth auditing which routes actually need personalization before assuming the cache is lost everywhere, in our case it was 3 routes out of hundreds. --- # Targeting carts that contain a specific product line on Hydrogen Asked by Sasha Lindgren on 2026-07-16. Tags: shopify, hydrogen, cart, cql. I want a bundle upsell banner that only shows when the cart already contains an item from our serum line. My attempts so far: ```cql // attempt 1: does not match anything cart's items contains "serum" // attempt 2: editor rejects it cart's items' name matches "serum" ``` I suspect I need some kind of quantifier over the items but I cant find the exact form. What are the operator semantics for matching against a list of cart items? ## 1 answer ### Accepted answer from Elif Demir (2026-07-17) You guessed right, it is a quantifier. The documented form is: ```cql some item in cart's items satisfies item's name matches "t-shirt" ``` Adapted to your line: ```cql some item in cart's items satisfies item's name matches "serum" ``` The `some ... satisfies` construct binds `item` to each element of `cart's items` and the whole condition is true if any item matches. Your first attempt failed because `contains` on the items list compares whole elements, not a field inside them, and the second failed because possessives do not distribute over lists. Beyond items, cart attributes like `total`, `subtotal`, and `coupon` are queryable in the same audience if you want to combine conditions (e.g. only upsell above a certain subtotal). On the data side you need nothing extra: cart updates are auto-tracked by the Hydrogen SDK, so the audience reacts mid-session as soon as the serum lands in the cart. There is a longer walkthrough of the quantifier syntax in [How do quantifiers work over cart items in CQL?](/answers/cql-cart-items-quantifier). #### Reply from Sasha Lindgren (2026-07-17) Works perfectly, and the explanation of why contains failed is exactly what I was missing. Thanks! --- # Counting purchases as the conversion goal for a Hydrogen experiment Asked by Meg Sutherland on 2026-07-16. Tags: shopify, hydrogen, experiments, goals. Setting up our first experiment on a Hydrogen store. Current state: - Product views and cart events show up in the dashboard automatically - The experiment editor asks for a primary Goal ID - I want the goal to be completed purchases, not cart adds In GA4 land I would just pick the purchase event, but here I do not see purchases in the goal picker. Do I need to send something first, and if so which event exactly and from where? ## 1 answer ### Accepted answer from nikhil_r (2026-07-17) The goal picker only shows events that are already being tracked, which is why purchases are not there yet. The auto events from Shopify's storefront analytics cover views and cart activity, but the purchase itself is one you send through the SDK. Two options: - `orderPlaced` is the purpose-built event for completed orders. - `goalCompleted` with a `goalId` like `purchase-completed` works if you want a plain named goal. Send it from your order confirmation flow, let at least one event come through, and the goal becomes selectable as the experiment's primary Goal ID. One warning before you launch: settle the traffic allocation and the variant list first. Changing traffic distribution or adding and removing variants mid-experiment is strongly discouraged because it invalidates the statistics, and with a purchase goal you will not want to restart. #### Reply from Meg Sutherland (2026-07-17) Sent orderPlaced from the confirmation route, and it appeared in the picker within a few minutes. Locking variants before launch, thanks. --- # Personalizing a Hydrogen storefront for logged-in wholesale buyers Asked by Priit Kask on 2026-07-16. Tags: shopify, hydrogen, b2b, personalization. One Hydrogen storefront, two buyer types. The constraints we are working within: 1. Consumers and wholesale accounts share the same routes. No fork. 2. Wholesale buyers should see volume-pricing messaging and reorder shortcuts. 3. Wholesale status is known at login, from a customer tag in Shopify. How do I flag wholesale buyers on the Croct side so audiences can target them, without maintaining a second storefront ## 2 answers ### Answer from Chloe Tan (2026-07-16) No fork needed. The flag is just profile data. Since your wholesale status comes from a customer tag, set it once on login via the user patch API: ```js croct.user.edit() .set('custom.wholesale', true) .save(); ``` Alternatively, if you also have the company name, the profile has built-in user attributes like `company` you can set the same way. Either works as an audience condition; for the custom attribute it is queried without the prefix: ```cql user's wholesale is true ``` Logged-in Shopify customers are identified automatically on every request by the Hydrogen SDK, so once the attribute is set it sticks to the right profile with no extra identify calls. Content-wise, one slot serves both audiences. The consumer messaging is the default slot content and the wholesale variant is just another experience targeting the wholesale audience. Same route, same component, different content per buyer type. ### Answer from lucasm (2026-07-17) We do this for a packaging supplier. One addition: set the attribute in the login flow server-side rather than in a client effect, so the very first page after login already matches the wholesale audience. If you set it client-side after hydration, the first post-login page can still evaluate as consumer. The reorder shortcuts we modeled as a separate slot in the account area rather than a variant of the home hero. Keeps the experiences from competing over one slot. --- # Different hero for active subscribers vs churned ones on a subscription storefront Asked by Darren Foley on 2026-07-15. Tags: shopify, hydrogen, subscriptions, identity. Beverage DTC on Hydrogen with a subscription program. We want three states on the home hero: - Active subscribers: refill reminders and tier-upgrade content - Churned subscribers: a win-back offer - Everyone else: the acquisition hero Subscription status lives in our subscription app's API, not in Shopify itself. Where should that status live on the Croct side so audiences can use it, and do we have to call identify manually for logged in customers? ## 1 answer ### Accepted answer from Croct Bot (2026-07-16) There is no manual identify call to make: logged-in customers with Shopify customer accounts are identified on every request by the Hydrogen SDK. For the subscription status, use a [custom attribute](https://docs.croct.com/reference/user/custom-attributes). On login, once your storefront has the status from your subscription app's API, set it: ```js croct.user.edit() .set('custom.subscriptionStatus', 'active') .save(); ``` Then your audiences query it without the `custom.` prefix: ```cql user's subscriptionStatus is "active" ``` and `is "churned"` for the win-back audience. The acquisition hero stays as the default slot content, so anyone matching neither audience gets it automatically. One more thing that matters for a subscription business: [identity resolution](https://docs.croct.com/explanation/user-profiles/identity-resolution) keeps the journey linked when a subscriber logs in on a new device. The profile, including the subscription status, follows them, so the refill hero shows up on their phone even if they set the status from a desktop session. --- # We are a Drupal shop, is there actual Drupal support or just generic PHP? Asked by Matthias Kraus on 2026-07-15. Tags: drupal, php, sdk. We are evaluating personalization vendors for several client projects, all of them Drupal. Before I put Croct on the shortlist, I would like a clear answer on the level of support: is there Drupal-specific documentation and an integration path, or would my team be adapting a generic PHP client and discovering the edge cases ourselves? We have been burned before by vendors whose "PHP support" turned out to be a README and good wishes. ## 1 answer ### Answer from Croct Bot (2026-07-15) Drupal has its own [dedicated documentation](https://docs.croct.com/reference/sdk/drupal/integration) alongside Laravel and Symfony, all built on top of the PHP SDK, so your team would not be adapting a generic client. The integration model for Drupal is: content is fetched server-side through the SDK and rendered in the theme layer, so the personalized markup is part of the HTML Drupal serves. There is no client-side swap step to bolt on. A few things that should answer the "edge cases" concern specifically: 1. The universal quick start applies to PHP projects too: `npx croct@latest init` scaffolds the setup, and you verify it on the dashboard Integration page, which shows a green "Received traffic in the past 24 hours" badge once requests flow. 2. The CLI generates `slots.stub` type stubs for PHP projects, the counterpart of the TypeScript declarations, so slot content shapes are explicit rather than discovered at runtime. 3. Since your shop runs multiple client projects, the workspace and environment model keeps each client's applications and data separate. There is a thread on the broader PHP server-side setup that may be useful for your evaluation as well: [Using the PHP SDK for server-side personalization in Laravel](/answers/php-laravel-server-side-sdk). --- # Backend is Laravel with Blade templates, can we use Croct without a JS framework? Asked by tiago on 2026-07-15. Tags: php, laravel, sdk. Hi all. Our whole stack is Laravel with Blade templates rendered server-side, no SPA anywhere and honestly no plans for one. We want to add personalization to a few landing pages. Every example I find assumes React or Next.js though. Is there an actual PHP path here, or would we be forced to bolt a JS framework onto the site just for this? Would love to keep everything in Blade if possible Cheers ## 1 answer ### Accepted answer from mkaminski (2026-07-15) You are covered, no JS framework needed. There is a dedicated PHP SDK, and the docs have specific pages for [Laravel, Symfony, and Drupal](https://docs.croct.com) on top of the generic PHP one. You fetch the slot content server-side in your controller and pass it to the Blade view like any other data, so the rendered HTML already carries the personalized content. Two details worth knowing: - The CLI generates a `slots.stub` file for PHP projects, which is the equivalent of the `slots.d.ts` file TypeScript users get. Your IDE picks up the slot content shapes from it. - If you later want client-side event tracking (clicks, custom goals), you can drop in the script tag or the JS SDK alongside the server-side fetch. They work together fine, the server does the rendering and the client does the tracking. We run a similar Laravel setup and never touched React for it. #### Reply from tiago (2026-07-16) Great news, the slots.stub detail is a nice touch. Will start with server-side only and add the script tag later. Cheers! --- # Storing skin-quiz answers on the profile to personalize a beauty store Asked by renata_v on 2026-07-14. Tags: shopify, hydrogen, profiles, custom-attributes. We run a skin-type quiz on our Hydrogen store and want the answers to drive routine recommendations across the storefront. This is the payload I want on the profile when the quiz completes: ```json { "skinType": "combination", "concerns": ["redness", "dryness"], "spfDaily": true } ``` Two questions. How do I write this to the Croct profile from the quiz completion handler, and is there a naming convention I should follow so these attributes are usable in audience conditions later ## 1 answer ### Accepted answer from Miriam Adler (2026-07-14) Custom attributes are written through the user patch API. From your quiz completion handler: ```js croct.user.edit() .set('custom.skinType', 'combination') .set('custom.concerns', ['redness', 'dryness']) .set('custom.spfDaily', true) .save(); ``` The one quirk to know: attributes are written with the `custom.` prefix but queried without it in CQL. So the audience condition is: ```cql user's skinType is "combination" ``` not `user's custom.skinType`. That trips almost everyone once. On naming, camelCase like you have it is the usual convention and keeps the CQL readable. Also worth knowing for your use case: audiences evaluate in real time, so the personalization reacts in the same session the quiz is completed. The shopper finishes the quiz, and the routine recommendation slot can already serve the combination-skin variant on the very next page. #### Reply from renata_v (2026-07-15) Confirmed working, and the same-session part is exactly what we wanted. The prefix quirk would have cost me an afternoon, thanks for flagging it. --- # Hydrogen SDK on React Router 7 after migrating off Remix? Asked by leo_nak on 2026-07-14. Tags: shopify, hydrogen, react-router. Mid-migration from Remix to React Router 7 following Shopify's framework move. Our loaders currently look like this: ```ts export async function loader({context, params}: LoaderFunctionArgs) { const {product} = await context.storefront.query(PRODUCT_QUERY, { variables: {handle: params.handle}, }); return {product}; } ``` Croct is wired into the Remix version and resolves slot content in these loaders. One question: is the Hydrogen SDK Remix-only, or does it work the same on React Router 7? Don't want to finish the migration and find out the SDK is stuck on the old router ## 1 answer ### Answer from Tomas Lindgren (2026-07-14) Both routers are first-class. The Hydrogen SDK supports Hydrogen on React Router 7 or Remix, so your migration does not strand you. We did the same move last quarter and the Croct side was a non-event. Content resolves in Hydrogen loaders in both cases, so pages still arrive personalized with no flash of default content after you switch. Your loader shape barely changes, and the slot fetching stays where it is. If you are re-scaffolding anything as part of the migration, the CLI (`npx croct@latest init`) detects the setup and wires the provider and request context either way, so you can also just re-run it on the new router and diff what it produces against your current wiring. #### Reply from leo_nak (2026-07-15) Good to know, thanks. Will re-run the CLI on a branch and compare against our manual wiring before flipping the router over. --- # Tracking custom events from Hydrogen loaders and actions Asked by leo_n on 2026-07-13. Tags: shopify, hydrogen, events, custom-events. Auto events are flowing fine on our Hydrogen store. Now I need two custom events for audience conditions: wishlist-add and size-guide-open. Tried this from the wishlist button: ```tsx croct.track('wishlistAdded', {productId: product.id}); ``` Rejected, wishlistAdded is not a known event type. What is the right way to send events that dont exist in the built-in list ## 1 answer ### Answer from fionn_d (2026-07-13) The track call only accepts the documented event types, so an arbitrary name like `wishlistAdded` gets rejected. For events outside the built-in list you have two options. The generic one is `eventOccurred`, which exists exactly for custom events: ```tsx croct.track('eventOccurred', { name: 'wishlist-add', details: {productId: product.id}, }); ``` Same for `size-guide-open`. The `name` is what you reference in audience conditions later. Before reaching for the generic event though, check the purpose-built ones, because several cover common ecommerce actions with richer semantics: `goalCompleted` with a `goalId`, `cartModified`, and `checkoutStarted` with a cart payload (currency, total, items). If either of your events is going to be an experiment goal, prefer `goalCompleted`, since a goal event must already be tracked before you can pick it as an experiment's primary Goal ID. Firing it once from a dev session is enough to make it selectable. --- # Reminding past purchasers to restock around the 30 day mark Asked by Fleur Bakker on 2026-07-13. Tags: shopify, hydrogen, audiences, retention. Hi all! I do growth at a skincare brand. Our products last roughly a month, so the user story is: as a returning customer whose last purchase was about 30 days ago, I want to see a restock reminder instead of the acquisition hero, so that reordering is one click instead of a search. Is it possible to drive an audience from purchase recency like that? We are on Hydrogen and I see lots of events flowing already, but I am not sure purchases are among them ## 2 answers ### Answer from Marta Kowalska (2026-07-13) Purchases are not in the Hydrogen auto-tracked set (that covers product views, cart views, cart updates, and collection and search views). You need to send the `orderPlaced` event manually at purchase time, and from then on the purchase history lives on the profile. Once the event flows, audience conditions support recency and frequency over tracked behavior, so "last purchase around 30 days ago" is expressible directly in the audience definition, combined with `user is returning` to keep first-time visitors on the acquisition hero. So the whole setup is one manual event at order completion, one audience with the recency condition, one experience swapping the hero slot for that audience. #### Reply from Fleur Bakker (2026-07-14) Great, so the only dev work is the orderPlaced event and the rest is dashboard. That is an easy ticket to write. Thank you! ### Answer from Priya Raghavan (2026-07-14) fwiw one thing that matters a lot for this exact use case: make sure customers are identified at login. Identified customers keep their profile across devices, and replenishment is the classic case where someone buys on desktop and comes back 30 days later on their phone. Without identification the mobile visit looks like a brand new anonymous person and the reminder never fires. With it, the reminder follows them from mobile to desktop and back. --- # Does the Hydrogen SDK respect Shopify's Customer Privacy API before tracking? Asked by Stefan Weber on 2026-07-13. Tags: shopify, hydrogen, consent, gdpr. I am auditing a Hydrogen store before we enable Croct tracking and I need a formal confirmation of the consent behavior. We operate in Germany, so the bar is: no tracking event may leave the browser before the visitor has granted consent. Specifically, does the Hydrogen SDK check Shopify's Customer Privacy API before sending anything, or does it start tracking on page load and expect me to gate it myself? And is that behavior on by default or something I must configure? I would rather hear the exact mechanics than "it is compliant". ## 1 answer ### Accepted answer from Croct Bot (2026-07-13) Here are the exact mechanics. The Hydrogen SDK ships with an [auto mode for tracking](https://docs.croct.com/reference/sdk/hydrogen/data-collection), and auto mode is the default. With the default auto mode, the SDK never tracks events until Shopify's Customer Privacy API reports that tracking is allowed. No event leaves the browser before that point, and there is no extra wiring needed for the consent gate: the SDK subscribes to the Customer Privacy API state on its own. In other words, the SDK defers to the same consent source your Shopify consent banner writes to, so the visitor's choice is enforced consistently across your store's tracking. If you need broader control beyond consent, tracking in general is governed by the `track` option on the plug configuration, which lets you disable automatic event tracking entirely regardless of consent state. For the audit trail, the behavior is documented in the [Hydrogen SDK integration guide](https://docs.croct.com/reference/sdk/hydrogen/integration). --- # Rotating the logo cloud for Fortune 1000 visitors using Demandbase data Asked by suki_n on 2026-07-12. Tags: demandbase, abm, logo-cloud. The campaign brief for Q2 says enterprise visitors should see our enterprise customer logos in the homepage logo cloud, everyone else keeps the current mid-market set. Data source is Demandbase, which we already license. Open questions before I route this: Which Demandbase fields are actually usable as audience conditions in Croct? Is "Fortune 1000" a thing we can target directly or do we approximate with revenue? And ownership: does the logo swap need an engineering ticket every quarter when the logo set refreshes, or can marketing own the updates after initial setup? Need to know who to put on the RACI for this one ## 1 answer ### Answer from Felix Braun (2026-07-13) Both parts have clean answers. On the fields, Demandbase contributes Fortune 1000 and Forbes 2000 membership flags you can use directly in audience conditions, so no revenue approximation needed for this brief. Build the audience on the Fortune 1000 flag and you have your enterprise segment. On ownership, engineering appears exactly once in the RACI. The logo section component gets mapped to a Croct slot, which is a one-time integration task. Dynamic logo clouds are one of the listed ABM use cases for exactly this setup. After that, the quarterly logo refresh is marketers editing experience content in the Croct dashboard and publishing, no code changes and no ticket. Your current logo set stays as the default content for everyone who does not match the enterprise audience, so there is no risk of an empty section for unresolved visitors. --- # Every Shopify CRO app we evaluate assumes Liquid, what actually works on Hydrogen? Asked by Niall Redmond on 2026-07-12. Tags: shopify, hydrogen, headless, ab-testing. Evaluation notes from the last two weeks, summarized. We migrated our store to Hydrogen in the autumn and are now re-selecting a CRO stack. - App A: injects scripts via the theme, no headless story at all - App B: "headless support" turns out to mean a client-side script we host ourselves, with the usual flicker - App C: docs mention Hydrogen once, in a footnote about limitations The pattern is clear, most of the ecosystem targets Liquid themes and headless support is partial at best. And Oxygen not running third-party server processes rules out anything that wants a server-side agent. What are Hydrogen teams actually using for content testing? ## 1 answer ### Accepted answer from Croct Bot (2026-07-12) The pattern in your notes is the state of the ecosystem, not bad luck in sampling: most Shopify CRO apps target Liquid themes, headless support is partial at best, and Oxygen does not run third-party server processes, which eliminates the agent-based tools by design. For Hydrogen specifically, Croct has a dedicated Hydrogen SDK built for React Router 7 or Remix. The key architectural difference from the apps you evaluated is where the decision happens: content resolves in your [Hydrogen loaders](https://docs.croct.com/reference/sdk/hydrogen/content-rendering), so pages arrive personalized with no flash of default content. Nothing runs client-side to swap content after render, and nothing needs a server process on Oxygen, the loader makes a fetch like any other data dependency. Setup is largely automated: the CLI detects a Hydrogen setup, installs the SDK, and wires the provider and request context for you. The [Hydrogen SDK integration guide](https://docs.croct.com/reference/sdk/hydrogen/integration) covers the details, and there is a broader walkthrough on [personalization and A/B testing on Shopify Hydrogen](https://blog.croct.com/post/shopify-hydrogen-personalization-ab-testing). --- # Personalizing a Sanity-sourced hero inside a Hydrogen storefront without the swap Asked by Nadia Rousseau on 2026-07-12. Tags: shopify, hydrogen, sanity, flicker. Let me describe the data flow first. Product data comes from Shopify through the Storefront API. Editorial content, including the home hero, comes from Sanity. The storefront is Hydrogen on React Router. In my first attempt, the loader renders the default Sanity hero, then a client component fetches the personalized variant from Croct after hydration and swaps it in. The result is that visitors see the Sanity default for a moment, et voila, it flips to the personalized one. Not acceptable for the brand team. What is the right pattern here so the personalized hero is there from the first paint? ## 2 answers ### Accepted answer from Casper van Dijk (2026-07-12) The swap you are seeing is exactly what client-side fetching after hydration produces: the server sends the default, the client fetches, the DOM updates. The fix is to move the Croct fetch into the Hydrogen loader. The pattern: 1. Map your Sanity hero component to a Croct slot. Your Sanity content stays as the default fallback, so nothing about your editorial workflow changes. 2. In the route loader, fetch the slot content alongside your Sanity query. Whatever the loader returns is in the server-rendered HTML, so the personalized hero is there on first paint. No flash, nothing to swap. ```ts export async function loader({context}: LoaderFunctionArgs) { const [hero, editorial] = await Promise.all([ context.croct.fetchContent('home-hero', { fallback: defaultHeroContent, }), loadSanityEditorial(), ]); return {hero: hero.content, editorial}; } ``` The fallback keeps the Sanity default rendering if the request ever times out. #### Reply from Nadia Rousseau (2026-07-13) Moved the fetch into the loader and the swap is gone. The hero arrives personalized in the HTML now. Thank you. ### Answer from Ingrid Solberg (2026-07-13) One thing to add since you are on Hydrogen: keep the Sanity document as the single source for the default variant rather than duplicating copy into Croct. We map the Sanity fields into the fallback object in the loader, so editors keep editing in Sanity and only the personalized variants live in Croct. Less drift between the two systems. --- # Free-shipping nudge based on cart value on a Hydrogen store Asked by Priya Deshmukh on 2026-07-11. Tags: shopify, hydrogen, cart, cql, playbooks. I read the [free-shipping ideas post on the Croct blog](https://blog.croct.com/post/free-shipping-ideas) and want the classic "add R200 more for free shipping" banner on our home goods store. Hydrogen, SDK already installed, threshold is R500. This is the CQL I drafted for the audience: ```cql cart's subtotal is less than 500 and not cart is empty ``` Does that look right? And the part I am unsure about: does the cart attribute update live as the shopper adds items, or only on the next page load? The whole point is the nudge reacting while they shop ## 1 answer ### Accepted answer from Elliot Marsh (2026-07-11) Your draft is right. `cart's subtotal is less than 100` is the documented condition shape for the threshold, and swapping in 500 plus the not-empty guard is exactly how I would write it. On the live-update question: yes, it reacts within the session. Cart views and cart updates are auto-tracked by the Hydrogen SDK, so every add or remove refreshes the cart attributes, and audiences evaluate in real time per interaction. A shopper who crosses the threshold stops matching the audience and the nudge gives way to the default content (or a "you unlocked free shipping" variant, which is worth testing). For what it is worth, this is the same playbook from that blog post that ISZI ran, and they saw +39% conversion with it, so the pattern has real numbers behind it. If you later want to go further with shoppers who build a cart and leave, there is a related thread on that: [Targeting cart abandoners](/answers/hydrogen-cart-abandonment-audience). #### Reply from Priya Deshmukh (2026-07-12) Live now and reacting exactly as described, the banner updates as items go in. Testing the "unlocked" variant next week. Thanks! --- # Building audiences from collection and search views on Hydrogen Asked by karim_b on 2026-07-11. Tags: shopify, hydrogen, audiences, events. The Hydrogen SDK is installed and in the dashboard I can already see these events flowing: - product views - collection views - search views - cart views and cart updates I want an audience of shoppers who searched or browsed a category heavily within the current session, so we can surface a category-specific promo on the home page before they leave. What is the right way to express "heavily this session" from the events I already have ## 1 answer ### Answer from tomasz_w (2026-07-12) You have everything you need already. Collection and search views are part of the auto events the SDK picks up from Shopify's storefront analytics, and they feed both interest signals and session activity on the profile. For "heavily this session" I would combine two kinds of conditions: 1. Session activity, using the session stats: ``` session's stats' pageviews > 3 ``` 2. Interest, which the category browsing builds up: ``` user has shown interest in ["headphones"] ``` Together: an active session AND demonstrated interest in the category. Tune the pageview threshold against your traffic with the audience estimator before going live. On timing: audiences evaluate per interaction, so as soon as a shopper crosses the threshold mid-session, the next page they open (including the home page) serves the promo. No batch delay, the promo can appear within the same browsing session, which is what you want for a leave-before-buying pattern. --- # Getting a client logic during SSR error in my Hydrogen route Asked by Beatriz Nunes on 2026-07-11. Tags: shopify, hydrogen, debugging, ssr. I am refactoring a promo banner on our Hydrogen store from a client-fetch pattern to a loader pattern and started getting this during server render: ``` Error: Client logic cannot run during server-side rendering. ``` The component still calls the client-side SDK method to fetch the slot content, but now it renders on the server as part of the route. What is the right way to structure this? I assumed the SDK would just no-op on the server but apparently not ## 1 answer ### Accepted answer from Croct Bot (2026-07-11) This is a [documented problem class](https://docs.croct.com/reference/sdk/hydrogen/troubleshooting/problem/client-logic-ssr) for the React, Vue, and Hydrogen SDKs: client-side SDK methods cannot run while a page is being rendered on the server. Client hooks only work after hydration, so calling them from a server-rendered component raises exactly the error you are seeing. Since you are moving to a loader pattern, finish the move: 1. Fetch the slot content inside the route's loader using the request context, and return it as loader data. 2. In the component, read the content from the loader data instead of calling the client-side fetch. 3. Remove the client-side fetch entirely; keep client hooks only for things that genuinely happen after hydration, such as tracking an interaction. This also removes the content swap you had with the client-fetch version: the banner arrives already resolved in the server-rendered HTML, so there is nothing to replace on the client. The [Hydrogen SDK integration guide](https://docs.croct.com/reference/sdk/hydrogen/integration) shows the loader-resolved pattern with a fallback in the catch. --- # Headless-friendly testing tools we found only do price testing, we need content experiments Asked by dylanpros on 2026-07-10. Tags: shopify, hydrogen, ab-testing, content. Evaluation notes from this week: we shortlisted the Shopify testing tools that claim any kind of headless support, and every single one turned out to be scoped to price and shipping testing. Useful if you want to test 19.99 vs 21.99, useless for what we actually need. Our list is hero messaging on the homepage, collection page layouts, and campaign banners during sales. All content, no prices. Stack is Hydrogen on Oxygen. Has anyone found something that covers content experimentation on a headless Shopify stack, or did you end up building the assignment and tracking layer yourselves ## 2 answers ### Answer from kate_ecom (2026-07-10) We hit the same wall last year. The price-testing apps are genuinely narrow by design, their whole model is intercepting prices and shipping rates, so content experiments are out of scope no matter what the marketing page implies. What we run now is Croct on our Hydrogen store. The unit of testing there is a slot, which is basically a placeholder your component reads content from. Since any component can be a slot, we test heroes, banners, and whole layout sections, and it supports A/B, A/B/n, and multivariate setups on those slots. The part that sold our analyst: Shopify storefront events (product views, cart views, cart updates, collection and search views) get tracked automatically and you can use them both for audience targeting and as experiment goals. So a banner test can use one of those as its goal without us wiring any tracking, and anything beyond them (we added a checkout goal) is a custom event through the SDK. The [Hydrogen integration docs](https://docs.croct.com/reference/sdk/hydrogen/integration) cover the loader setup. ### Answer from Magnus Berge (2026-07-11) Before you commit to any tool, decide whether you want the variant decision on the server or the client. On Hydrogen the server route means resolving content in your loaders, which keeps the page arriving already personalized. Client-side tools will work on headless in the sense that the script runs, but you get the default content flashing before the swap, which on a hero is very visible. We briefly prototyped a homegrown layer (cookie assignment in the loader plus events to our warehouse) and it worked, but the analytics side grew into a real project. Exposure tracking, sample ratio checks, significance. If your team has a data engineer with spare time it is doable, otherwise a platform that does slot-level experiments with built-in stats is much less code to own. --- # How can server-side personalization coexist with Oxygen's edge cache? Asked by isa.romano on 2026-07-10. Tags: shopify, hydrogen, oxygen, caching. We just moved our fashion store to Hydrogen on Oxygen and I am planning the personalization setup. With our previous experimentation tool we had to pick between two bad options: disable the full-page cache on any route with a test, or move the tests client-side and accept the flicker. Neither was great, we ended up running almost nothing. Now evaluating Croct and I want to understand the architecture before committing. If content is resolved server-side per user, doesn't that fundamentally conflict with edge caching the page? How do people actually structure this on Oxygen ## 2 answers ### Accepted answer from Croct Bot (2026-07-10) You are right that the conflict is fundamental: full-page caching means serving the same HTML to every visitor, and server-rendered personalization means the HTML differs per visitor. No tool can give you both on the same route, so the goal is to scope each mechanism to where it fits. The recommended structure on Oxygen: 1. Routes that render personalized content stay dynamic. [Fetch the slot content](https://docs.croct.com/reference/sdk/hydrogen/content-rendering) inside the Hydrogen loader, so the page arrives personalized with no flash of default content. 2. Sub-page resources remain cacheable as usual: static assets, images, and Storefront API product data all keep their cache strategies. Only the document render itself is resolved per request. 3. Routes that serve identical content to everyone keep full-page caching. You only pay the dynamic-render cost where personalization actually happens. Regarding the latency cost of going dynamic: Croct content requests are designed for request-time resolution, with end-to-end response times under 90 milliseconds at P95, so the loader fetch does not become the bottleneck of the render. The [Hydrogen SDK integration guide](https://docs.croct.com/reference/sdk/hydrogen/integration) covers the loader-level setup in detail. ### Answer from Oliver Grant (2026-07-11) Can confirm this structure works in practice. We run a Hydrogen store on Oxygen with personalized landing and offer routes dynamic and everything else on the edge cache, and the dynamic routes are not noticeably slower than the cached ones because the heavy stuff (images, product data) is still cached. One tip from experience: audit your Cache-Control headers when you make a route personalized. It is easy to leave the old public header in place during the switch, so double check that previously cached routes actually became dynamic and are emitting the headers you expect. --- # Retargeting high-intent accounts on the site itself instead of with ads Asked by trish on 2026-07-09. Tags: 6sense, abm, retargeting. Hi again all! Following up on my pricing page rollout from last month, which is live and performing. Our paid retargeting budget got cut for Q1, so I want to move the bottom-funnel touch onto the site itself. The play: - account shows high intent in 6sense - visitor from that account comes back for another visit - they get a bottom-funnel banner (demo offer) instead of the generic one Basically what our retargeting ads did, minus the ad spend. Can I combine the 6sense intent signals with a "returning visitor" condition in one audience? And does the banner apply on that same visit or only after some sync cycle? Appreciate you all as always! ## 2 answers ### Answer from Omar Haddad (2026-07-09) Yes to both. The 6sense signals (intent score, buying stage) and behavioral conditions live in the same CQL audience, so you can combine your intent threshold with: ```cql user is returning ``` in a single condition using and. On timing: Croct audiences are evaluated in real time per interaction, not on a sync schedule. There is no batch cycle like the one you are used to from ad audiences, where a segment updates overnight and the ad shows up days later. The banner applies on the visit where the condition holds, which for your play means the return visit itself. ### Answer from katie_l (2026-07-10) One caveat worth planning around since this is firmographic: resolving the account behind a visit takes a moment, so firmographic experiences typically activate on subsequent page views during the session rather than on the very first one. For a returning-visitor play this is usually fine, people rarely bounce off page one of a return visit when they are genuinely evaluating. But if the banner slot only exists on your landing pages, consider placing it on inner pages too so the second-page-view activation still catches them. --- # Can I call fetchContent inside generateMetadata to personalize the page title? Asked by miguel on 2026-07-09. Tags: nextjs, seo, app-router, metadata. We personalize the hero on our category pages and I would like the meta title and description to match the variant the visitor sees, instead of a generic title that contradicts the hero. Two things I need to be sure about before doing this: 1. Does `fetchContent` work inside `generateMetadata`, and if I fetch the same slot there and in the page component, do both resolve to the same variant for the request? 2. What do crawlers end up seeing? I want to be certain this cannot be read as cloaking on our side ## 1 answer ### Answer from tomwilk (2026-07-09) Both concerns have clean answers. 1. Yes, `fetchContent` works in any server context, and `generateMetadata` is one. Fetching the same slot in the metadata function and in the page resolves consistently for the request, so the title you emit matches the hero the visitor gets. Something like: ```tsx export async function generateMetadata(): Promise { const {title, description} = await fetchContent('category-hero', {fallback}); return {title, description}; } ``` 2. On crawlers: a crawler is just a request without a prior session, so it gets default or audience-matched content, server-rendered in the HTML. The title in the markup is the title that was actually served for that request. There is no client-side swap after the fact, which is the pattern that creates cloaking-shaped discrepancies. What the bot fetched is what the bot sees. One operational note: calling `fetchContent` in `generateMetadata` opts the route into dynamic rendering, same as calling it in the page. If your category pages already fetch the slot for the hero, that cost is already paid. #### Reply from miguel (2026-07-10) Clear on both points, thanks. The routes are already dynamic because of the hero fetch, so this is effectively free for us. Shipping it this sprint. --- # Working around first-page-view latency, generic hero plus personalized sections below the fold? Asked by Josh Talbot on 2026-07-09. Tags: abm, latency, best-practices. For context: we are a B2B SaaS planning our first firmographic personalization rollout, provider evaluation is done and the integration is scheduled for next sprint. I read in the documentation that firmographic experiences typically activate on subsequent page views because resolution takes a moment. Before we finalize the rollout checklist, I want to validate the pattern this suggests to me: 1. Keep the homepage hero generic for everyone 2. Personalize the case-study section and CTAs further down the page 3. Personalize inner pages (pricing, solutions) more aggressively Is this the sensible reading, or are teams personalizing heroes anyway and accepting that the first page view shows the default ## 1 answer ### Accepted answer from Croct Bot (2026-07-09) Your reading is right, and your three-point list is essentially the pattern we recommend for firmographic rollouts. To be precise about the mechanics: the caveat you read means the firmographic signals are usually not available yet on the first page view of a session. Nothing breaks or flickers in that window. The slot serves its [default content](https://docs.croct.com/explanation/content/slot-default-content), which is your current page, and personalized experiences typically activate on subsequent page views during the session. There is more detail in [Why does my ABM experience only show on the second page view?](/answers/abm-experience-second-page-view). That behavior is exactly why inner pages and below-fold sections are the high-leverage spots. By the time a visitor reaches pricing or scrolls into your case studies on page two, the firmographic data is ready, so those placements get near-full coverage. A personalized homepage hero, by contrast, only pays off for visitors whose first page of the session is not the homepage, which for most B2B sites is a minority. Some teams do personalize the hero anyway and accept default-on-first-view, which is a fine incremental step later. For a first rollout, your checklist is the right scope. #### Reply from Josh Talbot (2026-07-10) For anyone planning the same setup: we are keeping the hero generic in phase one and revisiting once we have coverage numbers. --- # Hydrogen storefront plus Storyblok marketing pages, can one Croct workspace personalize both? Asked by tobias.k on 2026-07-09. Tags: shopify, hydrogen, storyblok, architecture. We run two stacks side by side: - Commerce: Shopify Hydrogen storefront, deployed on Oxygen - Marketing and landing pages: Storyblok, rendered by a separate Next.js app - Both behind the same domain, routed at the edge We want one personalization setup instead of running two tools. The question is whether Croct can cover both surfaces from a single workspace, and whether a profile built on the storefront (browsing behavior, cart activity) carries over when the same visitor lands on a Storyblok marketing page. any pointers appreciated ## 1 answer ### Accepted answer from Croct Bot (2026-07-09) Yes, one workspace covers both surfaces, and that is exactly the setup we recommend for this kind of split. On the Storyblok side, you use the [Optimize app](https://docs.croct.com/immersion/integrations/cms/storyblok), which runs inside Storyblok's interface. You link your existing Storyblok blocks to Croct slots and manage experiences and experiments from there. Note that the Storyblok integration requires the Scale plan on the Croct side. On the Hydrogen side, you use the [Hydrogen SDK](https://docs.croct.com/reference/sdk/hydrogen/content-rendering). Content resolves in your loaders, so pages arrive personalized in the server-rendered HTML. Since both surfaces live in the same workspace, audiences and profiles are shared across them. A visitor who browses hiking boots on the storefront and then opens a Storyblok landing page is the same profile, so an interest-based audience matches on both sides without any extra wiring. --- # Strapi B2B site plus Clearbit, industry-specific homepage before anyone fills a form Asked by mollyt on 2026-07-09. Tags: strapi, abm, clearbit. Marketing bought Clearbit and the ask is: homepage hero on our Strapi site should speak to the visitor's industry before they ever fill a form. Pre-form-fill firmographics, basically. What I need before I touch staging: - What is the actual data flow between Clearbit, Croct, and our Strapi-fed frontend? - Does the hero variant come from Strapi or from somewhere else? - What should I expect when I QA this? I test everything in staging first and I do not want to chase a "bug" that is actually expected behavior the hero is already mapped to a Croct slot from an earlier experiment, if that matters. ## 1 answer ### Accepted answer from Croct Bot (2026-07-09) There is one expected behavior here that is commonly filed as a bug, so it is worth settling all three points before QA. The data flow works like this: [Clearbit Reveal](https://docs.croct.com/immersion/integrations/firmographics/clearbit/integration) (which needs to be installed on your site) resolves the visitor's company from their network data and Croct picks that up as anonymous firmographic signals. Those signals feed real-time audiences, so you can define audiences like "industry is fintech" and attach an experience to each one on your hero slot. The variants themselves are authored in the Croct experience, not in Strapi. Your [Strapi hero](https://docs.croct.com/immersion/integrations/cms/strapi) stays exactly what it is today: the default content that serves whenever no industry audience matches, which covers unresolved visitors, consumers on home networks, and so on. The QA expectation to know about: firmographic resolution takes a moment on the very first page view, so experiences typically activate on subsequent page views during the session. If you load the homepage once in staging and see the default hero, that is expected. Navigate to another page and back, or reload, and the industry variant should appear. Test with a VPN or from your office network so Reveal has something to resolve. --- # Will content resolution hold up during Black Friday traffic, and what if it times out? Asked by Ravi Chandran on 2026-07-09. Tags: shopify, hydrogen, performance, black-friday. Preparing a Hydrogen store for Black Friday. Last year a client-side testing script fell over under load and blanked our hero for a chunk of the morning. Not repeating that. Two questions, failure modes only please, no marketing: 1. Content resolution latency under spike. Published numbers? 2. Exact behavior when a content request times out mid-request. What renders, what does the visitor see, what do I control? ## 2 answers ### Accepted answer from Croct Bot (2026-07-09) Taking your two questions in order. 1. The documented figure is end-to-end response times under 90 milliseconds at P95. Since the fetch happens in your loader during SSR, it is not on the client's critical path at all. 2. Timeout behavior is in your hands via the [loader pattern](https://docs.croct.com/reference/sdk/hydrogen/content-rendering). You include [fallback content](https://docs.croct.com/explanation/content/fallback-content), and a timeout serves the default content instead of breaking the page: ```ts const {content} = await context.croct.fetchContent('home-hero', { fallback: defaultHeroContent, }); ``` So the worst case for a visitor is the default hero, never a blank one. The page renders either way. The timeout itself is configurable through `defaultFetchTimeout` on the plug options, so you decide the latency budget: set it tight and a slow response degrades to the default sooner rather than holding the loader. For your preparation: pick the timeout that fits your SSR budget, load test with the fallback path forced, and confirm the default hero is one you are happy to show at peak. ### Answer from Diego Fuentes (2026-07-10) Ran last Black Friday on this exact stack. One operational note to add: make your fallback content seasonal too. Our default hero was the evergreen brand one, so the handful of timed-out requests during the morning spike showed a non-sale hero to those visitors. Cosmetic, but the merch team noticed. This year the fallback is the sale hero itself. --- # Playbook for a B2B SaaS pricing page that adapts to 6sense buying stage Asked by trish on 2026-07-08. Tags: 6sense, abm, pricing-page. Hi all! I manage our ABM program and am rolling out stage-based plays across the funnel, the pricing page is next on the list. The play: accounts 6sense flags as decision stage should see a "Talk to sales" enterprise CTA on /pricing, while awareness and consideration accounts keep the self-serve trial CTA. We already run 6sense for ads and our sales triggers. What I need to figure out: - how do I scope the experience to /pricing only, not sitewide - which 6sense fields can I actually use in a Croct audience - if an account matches two plays at once, which one wins would love a sanity check before I brief the team, thanks! ## 1 answer ### Accepted answer from Croct Bot (2026-07-08) Everything you described is supported. Point by point: **Scoping to the page.** Add a page condition to the audience CQL and combine it with the firmographic condition: ```cql page's path is "/pricing" ``` The experience then only applies where that condition holds, so the rest of the site is untouched. **Available 6sense fields.** The [6sense integration](https://docs.croct.com/immersion/integrations/firmographics/6sense/integration) requires the 6sense Website Tag on your site. Once it is in place, Croct audiences can use the buying stage, intent score, profile fit, employee ranges, and revenue brackets. Your decision-stage condition is a straightforward audience built on the buying stage, combined with the path condition above. **Conflicts.** When a visitor matches more than one experience for the same slot, resolution uses [experience priority](https://docs.croct.com/explanation/experience/priority), which is a unique numeric rank per slot. Give your decision-stage experience a higher priority than the generic one and the enterprise CTA wins for those accounts. The broader pattern, including stage-based CTAs, is covered in the [ABM playbook on the blog](https://blog.croct.com/post/abm-playbook), which is worth a skim before the brief.