---
title: "How to personalize Hygraph content based on visitor behavior"
slug: "personalization-hygraph-visitor-behavior"
locale: "en-us"
description: "Hygraph's Variants feature models personalized content well and stops at the content boundary by design. Here's how to use it, what it leaves you, and what that costs at scale."
published_at: "2026-09-16"
updated_at: "2026-09-16"
image: "https://storage.googleapis.com/croct-assets-b931d070/blog/How_to_personalize_Hygraph_content_based_on_visitor_behavior_6f670b4314/How_to_personalize_Hygraph_content_based_on_visitor_behavior_6f670b4314.png"
authors: ["Juliana Amorim"]
category: "Personalization"
tags: ["Segmentation","Comparison","How To","Conversion Rate Optimization (CRO)"]
canonical: "https://blog.croct.com/post/personalization-hygraph-visitor-behavior"
---

# How to personalize Hygraph content based on visitor behavior

Hygraph does offer a personalization feature, and it's a good one.

[Variants](https://hygraph.com/blog/introducing-variants) let you attach personalized versions of a content entry to named Segments, without duplicating entries or restructuring your content graph. For a CMS built around structured, relational content, it's a clean design.

It's also explicitly scoped, and Hygraph is admirably direct about where the scope ends. From their own [documentation](https://hygraph.com/docs/developer-guides/schema/variants):

> Hygraph doesn't decide which content Variant is shown to your end-users, and we don't collect or process any end-user data to drive Segment creation. While it is possible to build personalized experiences without an additional tool, we recommend using a personalization engine for more complex setups.

That's the honest version of the line every headless CMS draws. Modeling personalized content is the part Hygraph makes easy. Knowing who the visitor is, deciding which Segment they belong to, and proving the personalized version worked is the part you build.

This guide walks through how Variants actually works, what it costs once you scale it past a handful of segments, and the two ways teams close the gap.

## What Variants gives you

Being precise here matters, because Variants is better designed than the field-level variant systems on several competing CMSs.

- **Variant support is opt-in per field**

  You [enable it field by field](https://hygraph.com/docs/developer-guides/schema/variants) in the Schema Editor, so a model with twenty fields and four variable ones stays mostly untouched. It isn't available for custom fields or unique fields.

- **Segments are first-class entries**

  A Segment is a named reference with a slug, managed in the Content Editor. Variants link to one or more Segments. You aren't encoding audience names into field values or inventing a convention.

- **Variants are linked, not duplicated**

  A Variant isn't a separate entry. It's attached to a main entry, inherits its structure, and overrides only the fields you enabled. Values from the main entry are copied in when you create it, so a Variant that differs slightly is quick to produce.

- **The overlay is one query parameter**

  You filter the entry's Variants by Segment slug and merge the result over the base. One parameter for the whole entry, not one per personalized field.

So the content modeling side is genuinely solved inside Hygraph. What isn't solved is everything on either side of it.

## How to personalize a Hygraph entry for a Segment

The pattern has four steps. Hygraph publishes a [working example](https://github.com/hygraph/variant-coffee-example) built on Next.js App Router, and the code below follows it.

### Step 1: Enable Variant support on the fields that should vary

In the Schema Editor, open your content model, edit a field, and check **Enable variants** under Settings. Repeat for each field you want to personalize.

Keep this list short. Every field you enable is a field that has to be filled in for every Variant you create, and Variants don't inherit later edits from the main entry.

### Step 2: Create your Segments

In the Content Editor, under **Segments**, add an entry with a name and an optional description. The slug is what your frontend will pass at query time.

Segments are references, not audiences. The actual user lists live in an external system, and Hygraph stores the label you target against.

### Step 3: Add a Variant and link it to a Segment

Open the main entry, and under **Variants** in the right sidebar, select **Add**. The main entry's values are copied in. Edit the Variant-enabled fields, click **Select segment**, choose one or more Segments, and save.

Two publishing rules that catch people out: the main entry must be published before its Variants can be, and publishing the main entry doesn't publish its Variants. Each one publishes separately.

### Step 4: Resolve the Segment and overlay the Variant

This is the part Hygraph leaves to you. The example derives the Segment from a URL parameter or a cookie:

```ts
// lib/utils.ts
export async function getSegment(searchParams) {
  const {segment} = await searchParams;
  const cookieStore = await cookies();

  return segment || cookieStore.get('segment')?.value;
}
```

The query asks for the base entry plus its matching Variants:

```graphql
query HomePage($segment: String, $variantId: ID) {
  page(where: {slug: "home"}) {
    title
    subtitle
    ctaLabel
    variants(
      where: {
        OR: [
          {segments_some: {slug: $segment}},
          {id: $variantId}
        ]
      }
    ) {
      title
      subtitle
      ctaLabel
    }
  }
}
```

And the overlay merges the Variant over the base:

```ts
export function applyVariant(base) {
  const variant = base?.variants?.[0];

  if (!variant) {
    return base;
  }

  return {...base, ...variant};
}
```

Wired together in a server component:

```jsx
const segment = await getSegment(searchParams);
const variantId = await getVariantId(searchParams);
const home = await getHomePageData(segment, variantId);
const personalized = applyVariant(home);
```

That works. If you're personalizing a marketing site for a handful of known segments driven by campaign links, this is a perfectly reasonable place to stop.

### What this doesn't cover

Three things sit outside the box, and they decide whether personalization is a feature or a program.

#### The Segment comes from a URL parameter or a cookie

That's fine for a campaign link carrying `?segment=shops`, and it's not a segmentation engine. Anything beyond an explicit label, such as behavior in the current session, firmographics, purchase history, returning versus new, is logic you write, plus somewhere to keep the data.

Note also that a Segment in a URL is user-controllable: shareable, editable, and indexable.

#### The overlay takes the first match

Hygraph's own README is upfront about this. If a visitor matches one Segment, correct. If they match two, like an enterprise account *and* a returning customer, they get whichever Variant the API happened to return first.

There's no priority order, and nothing in the query expresses one. You need an explicit precedence rule the moment your Segments overlap, and it lives in your code because the CMS has no concept of it.

#### Nothing measures anything

Variants have no experiment object, no traffic allocation, no random assignment, no exposure tracking, and no statistics. You can ship a Variant for coffee shop owners and never learn whether it converted better than the base entry.

Hygraph lists AB testing as a use case Variants supports, and that's true in the sense that it can hold the variations. It can't split traffic or tell you which one won.

## The cost that shows up at scale

The four steps above are cheap for three Segments. Here's what compounds.

### Variants don't sync with the main entry

Each Variant maintains its own values independently. Updating a Variant-enabled field on the main entry does not update that field in its Variants, and vice versa.

Change your headline for legal reasons, and you change it once on the main entry and once on every Variant that overrides it. Six Segments means seven edits, and any one you miss is a Variant that quietly drifts out of alignment with your brand.

This is the same editorial-surface problem that duplicating entries creates, moved inside a nicer interface. It's less bad than duplication, and it's not free.

### Publishing is per Variant

Publishing the main entry doesn't publish its Variants. Each one publishes separately, and only after the main entry is live. A six-Segment page is seven publish actions, in order, every time.

### The limits are real

Variants is an enterprise feature, gated behind a sales conversation. There's a maximum of 30 Variants per entry. Segments and Variants both count toward the content entry limits of your billing plan, so personalization consumes the same quota as your content. Segment fields can't be localized, and custom fields aren't supported.

None of these is unreasonable. They do mean personalization on Hygraph has a floor price and a ceiling, and both are worth knowing before you design around them.

### Everything downstream is still yours

Stack up what remains after Variants has done its job: identity resolution, Segment membership logic, wherever the behavioral data lives, precedence rules, exposure tracking, conversion attribution, and a statistical engine. Six responsibilities, at least four of them outside your CMS entirely, none of them your core product.

## Option 1: build the delivery stack

The in-house path is legitimate, and Hygraph's docs describe its shape accurately: identify user Segments in real time, then dynamically deliver content based on the Variants you set up.

"Identify user Segments in real time" is doing enormous work in that sentence. In practice, it means:

1. **Identity:** A stable visitor ID in a first-party, server-set cookie. Safari's ITP caps client-set cookies at seven days, so a visitor returning after that gets re-segmented mid-campaign.
2. **Behavioral data:** Somewhere to record what visitors did, so Segments can be more than a label in a URL. Usually a CDP, which is a second system to buy, integrate, and keep in sync.
3. **Segment evaluation:** Rules that map profile data to Segment slugs, re-evaluated as behavior changes.
4. **Precedence:** The explicit priority order Hygraph's example tells you to write yourself.
5. **Exposure and conversion tracking:** Sent server-side, or ad blockers remove a non-random slice of your data and bias the denominator rather than just shrinking it.
6. **Statistics:** Something that decides whether the difference you're seeing is real.

Writing this once is a few weeks. Owning it is permanent, and every new Segment touches several layers at once: a CDP change, a rule update, a Segment entry in Hygraph, Variants on every affected page, and a deploy.

## Option 2: attach a decision layer

The alternative inverts the question. Rather than supplying a Segment to Hygraph, you let a layer evaluate the audience, resolve the content, and leave your schema and queries alone.

Croct sits alongside Hygraph. Your component queries Hygraph exactly as it does now, and passes the result as the fallback:

```jsx
export default async function Home() {
  // Unchanged: no $segment, no variants block, no overlay
  const {page} = await hygraph.request(HOME_QUERY);

  const {content} = await fetchContent('home-hero', {
    fallback: page,
  });

  return <Hero {...content} />;
}
```

### Audiences are evaluated, not supplied

This is the difference that matters. Instead of your code deciding the Segment and passing it in, audience conditions are evaluated server-side against a live visitor profile. Visitor profiles are built in, so behavioral targeting works without a CDP, and firmographic data from 6sense, Demandbase, or ZoomInfo lands on the same profile for B2B targeting.

### Precedence is configuration

When a visitor matches several audiences, the interface sets priority. You don't extend a helper function to get deterministic behavior.

### Your schema and content model don't change

Croct reads nothing from Hygraph and writes nothing back. No Variant-enabled fields, no Segment entries consuming your content quota, no 30-per-entry ceiling. Rolling back means deleting one call.

Worth saying plainly: this doesn't mean abandoning Variants. If you already use them for locale or campaign segments, keep them. The two coexist.

### Experiments come with the same mechanism

The slot that personalizes also A/B tests, with deterministic assignment, cross-device stickiness, exposure tracking and Bayesian analysis on 100% of your data. That's the half Variants explicitly doesn't cover.

### Everything resolves server-side

Content is decided before the response is sent, so there's no flicker, crawlers see what visitors see, and Core Web Vitals are unaffected. Because resolution happens per component, the rest of the page stays prerendered and cacheable.

## Side-by-side

|                              | Variants alone                  | Variants + CDP + analytics   | Croct               |
| ---------------------------- | ------------------------------- | ---------------------------- | ------------------- |
| **Content modeling**         | Native, well designed           | Native                       | In the Croct UI     |
| **Who decides the Segment**  | Your frontend                   | Your rules plus a CDP        | Croct               |
| **Behavioral targeting**     | ❌                               | ✅                            | ✅                   |
| **Multi-segment precedence** | ❌                               | ✅                            | ✅                   |
| **A/B testing**              | ❌                               | ✅                            | ✅                   |
| **Exposure tracking**        | ❌                               | ✅                            | ✅                   |
| **Statistics**               | ❌                               | ✅                            | ✅                   |
| **No Schema impact**         | ❌                               | ✅                            | ✅                   |
| **No quota impact**          | ❌                               | ❌                            | ✅                   |
| **Editorial sync**           | Manual, per Variant             | Manual, per Variant          | Single source       |
| **Publishing**               | Each Variant separately         | Each Variant separately      | One publish         |
| **Plan requirement**         | Enterprise                      | Enterprise plus vendors      | Free plan available |
| **Adding a Segment**         | Segment, Variants, code, deploy | CDP, rules, Variants, deploy | Defined in the UI   |

## My recommendation

I should say plainly that I'm Croct's founder, so read the last column with that in mind. But the comparison above is built from Hygraph's own documentation, and I'd rather be useful than flattering.

If Variants covers you, use Variants. A fixed set of campaign or locale Segments, no need to measure lift, an enterprise plan you already have, and content that doesn't change often: the native feature is sufficient and adding a vendor solves a problem you don't have. Hygraph built something good here and it deserves to be used.

The line is where Segments stop being labels. The moment a Segment means "visitors who viewed pricing twice this session" rather than "people who clicked this campaign link," you've crossed from content modeling into visitor data, and Hygraph has told you plainly that's not what they do. At that point you're either buying a CDP and building the evaluation layer, or attaching something that includes both.

And if you can't measure it, you're not personalizing, you're guessing. This is the part I'd weigh most heavily. Six Variants shipped with no way to know whether any of them beat the base entry isn't a personalization program. It's six more things to maintain.

A reasonable first move either way: [create a forever-free account](http://app.croct.com/signup), pick the single highest-traffic entry with the clearest conversion role, personalize it for one Segment, and measure it against the base for two weeks. That tells you more about whether you need a decision layer than any comparison table, including this one.
