1.0.10 • Published 1 year ago

sanity-plugin-roboto-ab-test v1.0.10

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

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.

1.0.9

1 year ago

1.0.8

1 year ago

1.0.10

1 year ago

1.0.7

1 year ago

1.0.6

1 year ago

1.0.5

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago