sanity-plugin-roboto-ab-test
v1.0.10
Published
create unique features of a document based on ab tests
Downloads
5
Readme
sanity-plugin-roboto-ab-test
This is a Sanity Studio v3 plugin.
Installation
npm install sanity-plugin-roboto-ab-test
Requirements
- A Sanity project
Usage
Add it as a plugin in sanity.config.ts
(or .js):
// 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.
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.
How to setup the middleware:
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}`;
}
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 = `/test/${variantId}/${pageSlug}`;
}
const res = NextResponse.rewrite(url);
if (!req.cookies.has(USER_VARIANT_COOKIE)) {
res.cookies.set(USER_VARIANT_COOKIE, cookie);
}
return res;
}
Setting up /test/[variantId]/[pageSlug]
page
// 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<AbTestQueryResult>({
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<AbTestPageQueryResult>({
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 (
<LiveQuery
enabled
initialData={data}
query={getSlugPageDataQuery}
params={{ slug: `/${slug}` }}
as={SlugPageClient}
>
<SlugPage data={data} />
</LiveQuery>
);
}
return <SlugPage data={data} />;
};
License
MIT © Hrithik Prasad
Develop & test
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.