create unique features of a document based on ab tests
npm install sanity-plugin-roboto-ab-test> This is a Sanity Studio v3 plugin.
``sh`
npm install sanity-plugin-roboto-ab-test
Add it as a plugin in sanity.config.ts (or .js):
`ts
// sanity.config.ts
import {defineConfig} from 'sanity'
import {abTest} from 'sanity-plugin-roboto-ab-test'
export default defineConfig({
//...
plugins: [
abTest({
schemaType: "page",
}),
],
})
`
Adding the plugin to your Sanity project will add a new abTest document type to your schema. This type is used to create and manage A/B tests on page level.
you need to add the abTest to the structure list.
`ts
import { abTestStructureList } from 'sanity-plugin-roboto-ab-test';
export const structure = (S: StructureBuilder, context: StructureResolverContext) =>
S.list()
.title('Content')
.items([
// other lists
abTestStructureList(S),
]);
`
to consume the abTest values in your app/website, you can
use the middleware to set a cookie this will help us later on the stage to check which variant to show.
`ts
import { NextRequest, NextResponse } from 'next/server';
import { USER_VARIANT_COOKIE } from './config';
import { getBucket } from './lib/ab-testing';
export const config = {
matcher: ['/((?!api|_next|_vercel|.\\..).*)'],
};
const getPageSlug = (pathname: string) => {
// only rewriting on sanity slug pages
const [pathnameWithoutSlug] = pathname.split('/').filter(Boolean);
if (pathnameWithoutSlug) {
if (['blog'].includes(pathnameWithoutSlug)) return null;
return pathnameWithoutSlug;
}
return null;
};
export function middleware(req: NextRequest) {
let cookie = req.cookies.get(USER_VARIANT_COOKIE)?.value;
if (!cookie) {
const variant = getBucket(['0', '1']);
cookie = variant-${variant};/test/${variantId}/${pageSlug}
}
const [, variantId] = cookie.split('-');
const url = req.nextUrl;
const pageSlug = getPageSlug(url.pathname);
if (!pageSlug) {
const res = NextResponse.next();
if (!req.cookies.has(USER_VARIANT_COOKIE)) {
res.cookies.set(USER_VARIANT_COOKIE, cookie);
}
return res;
}
if (variantId !== '0') {
url.pathname = ;`
}
const res = NextResponse.rewrite(url);
if (!req.cookies.has(USER_VARIANT_COOKIE)) {
res.cookies.set(USER_VARIANT_COOKIE, cookie);
}
return res;
}
`tsx
// apps/web/src/app/test/[variant]/[slug]/page.tsx
const abTestQuery = groq
*[_type == "abTest" && enabled]{
_id,
"variants":variants[]->slug.current
};
const abTestPageQuery = groq
*[_type == "abTest" && enabled && $slug in variants[]->slug.current][0]{
"slugs": variants[]->slug.current
};
export const generateStaticParams = async () => {
const [res, err] = await handleErrors(
sanityServerFetch
query: abTestQuery,
tags: [SANITY_TAGS.abTestIndex],
}),
);
if (!res || err) return [];
const paths: {
slug: string;
variant: string;
}[] = [];
res.forEach((test) => {
const _variants = test.variants?.filter(Boolean) as string[];
_variants.forEach((variant, index) => {
const [slugFragments] = variant.split('/').filter(Boolean);
if (slugFragments)
paths.push({
slug: slugFragments,
variant: ${index},
});
});
});
return paths;
};
const pageToFetch = async (slug: string, variant: string) => {
const { isEnabled } = draftMode();
const filterVariant = Number(variant);
const pageSlug = /${slug};
if (isEnabled) return pageSlug;
if (isNaN(filterVariant)) return pageSlug;
const [res, abError] = await handleErrors(
sanityServerFetch
query: abTestPageQuery,
params: { slug: pageSlug },
tags: [SANITY_TAGS.abTest, SANITY_TAGS.abTestIndex],
}),
);
if (abError) return pageSlug;
const finalSlug = res?.slugs?.at(filterVariant);
if (!finalSlug) return pageSlug;
return finalSlug;
};
export default async function Page({
params,
}: {
params: { slug: string; variant: string };
}) {
const { slug, variant } = params ?? {};
const { isEnabled } = draftMode();
const finalSlug = await pageToFetch(slug, variant);
const [data, err] = await getSlugPageData(finalSlug);
if (err || !data) return notFound();
if (isEnabled) {
return (
initialData={data}
query={getSlugPageDataQuery}
params={{ slug: /${slug} }}``
as={SlugPageClient}
>
);
}
return
};
MIT © Hrithik Prasad
This plugin uses @sanity/plugin-kit
with default configuration for build & watch scripts.
See Testing a plugin in Sanity Studio
on how to run this plugin with hotreload in the studio.