👋 Next.js Conf 2024: Come build, party, run, and connect with us! See all events

Discussion on implementing A/B experiments in Sanity.io using GROQ queries and object references

15 replies
Last updated: Jan 11, 2023
user T
hey great demo today. I might have missed it but is there any info on how you're doing A/B experiments anywhere?
Dec 9, 2022, 9:00 AM
In that demo I have an object called “experiment”, which contains an array for two references.
In my GROQ query I only resolve one of them, based on an
$audience
variable which is set to either 0 or 1.
In the live preview I also had a way to change the audience variable so you could at least preview both.

It’s a simple example in that it doesn’t then do the sort of tracking you would want to analyze the “score” of which one did better. But that’s where you’d likely need to integrate your own solution.
Dec 9, 2022, 9:09 AM
Ah okay, what I was hoping to see was some way to manage who sees which experiment
Dec 9, 2022, 9:24 AM
Maybe that could be some randomized number
Dec 9, 2022, 9:24 AM
Math.round(Math.random())
Will give you a
1
or
0
for the first load
But you’d probably also want to write that to a cookie/localStorage so that you’re consistently serving the same one each time.
Dec 9, 2022, 9:34 AM
Might be worth tying to some vercel edge function so we have location based testing too
Dec 9, 2022, 9:35 AM
Yep for sure! I’ve got a personalisation demo that feels the Vercel Geo headers into a variable as well.
Dec 9, 2022, 9:35 AM
Oh sweet is it shared?
Dec 9, 2022, 9:36 AM
The API route looks like this:
From memory though,
x-vercel-ip-country
is only present for Enterprise Vercel accounts

import {getClient} from '../../lib/sanity.server'

export default async function getBanner(req, res) {
  const country = req?.query?.country || req?.headers?.['x-vercel-ip-country']

  if (!country) {
    return res
      .status(400)
      .json({message: `No Country in Params or Request Headers`, headers: req.headers})
  }

  const data = await getClient().fetch(
    `*[_type == "banner" && upper(country) == upper($country)][0]`,
    {country}
  )

  if (!data) {
    return res.status(404).json({message: `No Banner found for Country`, country})
  }

  return res.status(200).json(data)
}
Dec 9, 2022, 9:37 AM
Thanks a million I'll look into this
Dec 9, 2022, 9:45 AM
In that demo I have an object called “experiment”, which contains an array for two references.
user T
is the "experiment" object inside the "Home" document? Can you share how you did this?
Jan 11, 2023, 12:30 AM
The “Home” document is a just a singleton “Page”.
The experiment object schema looks like this:


import {Wand2} from 'lucide-react'
import {defineField} from 'sanity'

export default defineField({
  name: 'pageBuilderExperimentCell',
  title: 'Experiment',
  type: 'object',
  icon: Wand2,
  fields: [
    defineField({
      name: 'experiments',
      description: 'Choose 1-2 blocks to be split between A/B audiences',
      type: 'array',
      of: [
        defineField({
          name: 'experiment',
          type: 'reference',
          to: [{type: 'article'}],
        }),
      ],
      validation: (rule) => rule.max(2).required(),
    }),
  ],
  preview: {
    select: {
      title0: 'experiments.0.title',
      title1: 'experiments.1.title',
    },
    prepare: ({title0, title1}) => ({
      title:
        title0 || title1
          ? [title0, title1]
              .filter(Boolean)
              .map((title) => `"${title}"`)
              .join(` vs `)
          : `No "Articles" selected`,
      subtitle: 'Experiment',
      media: Wand2,
    }),
  },
})
and in my GROQ query I only resolve one of these, based on a variable passed into the query:

experiments[$audience]
$audience is either
1
or
0
Jan 11, 2023, 9:11 AM
Cool, I like this idea, thank you so much for sharing the code!
Where do you add this object? Do you create a document like "Experiments" and add this object to it, or do you add it to the "Articles" document?
Jan 11, 2023, 1:49 PM
In this particular demo, it’s in the Page Builder array of a Page document.
Jan 11, 2023, 2:55 PM
Oh, I got it! This is good to test parts of the page. In my case I need to run an experiment with two pages, so I think the solution is to create a document "A/B Tests" where I can add the "experiments" object which contains references for "Pages" document. And I can use the query you shared to get the right experiment based on
audience
Jan 11, 2023, 6:00 PM
Good idea!
Jan 11, 2023, 6:02 PM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?