Skip to content

Commit a37ca9f

Browse files
committed
init repo
0 parents  commit a37ca9f

21 files changed

+3051
-0
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
NEXT_PUBLIC_ABBY_PROJECT_ID=<YOUR ABBY PROJECT ID>

.eslintrc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"root": true,
3+
"extends": "next/core-web-vitals"
4+
}

.gitignore

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# Dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# Testing
9+
/coverage
10+
11+
# Next.js
12+
/.next/
13+
/out/
14+
15+
# Production
16+
/build
17+
18+
# Misc
19+
.DS_Store
20+
*.pem
21+
22+
# Debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# Local ENV files
28+
.env.local
29+
.env.development.local
30+
.env.test.local
31+
.env.production.local
32+
33+
# Vercel
34+
.vercel
35+
36+
# Turborepo
37+
.turbo
38+
39+
# typescript
40+
*.tsbuildinfo

.npmrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Enabled to avoid deps failing to use next@canary
2+
legacy-peer-deps=true

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
name: A/B Testing with A/BBY
3+
slug: ab-testing-abby
4+
description: A/BBY is a service for developer focused Feature Flags & A/B Testing. In this template you'll be able to use feature flags and A/B tests at the edge.
5+
framework: Next.js
6+
useCase: Edge Middleware
7+
css: Tailwind
8+
deployUrl: https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/edge-middleware/ab-testing-abby&env=NEXT_PUBLIC_ABBY_PROJECT_ID&project-name=abby-demo&repository-name=ab-testing-abby
9+
demoUrl: https://abby-nextjs-example.vercel.app/
10+
relatedTemplates:
11+
- ab-testing-simple
12+
---
13+
14+
# A/B Testing with ConfigCat
15+
16+
[A/BBY](https://tryabby.dev) is a service for developer focused Feature Flags & A/B Testing. In this Demo you'll be able to use feature flags and A/B tests at the edge.
17+
18+
By A/B testing directly on the server-side, you'll reduce layout shift from client-loaded experiments and improving your site's performance with smaller JavaScript bundles.
19+
20+
## Demo
21+
22+
https://abby-nextjs-example.vercel.app/
23+
24+
## How to Use
25+
26+
You can choose from one of the following two methods to use this repository:
27+
28+
### One-Click Deploy
29+
30+
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme):
31+
32+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/edge-middleware/ab-testing-abby&env=NEXT_PUBLIC_ABBY_PROJECT_ID&project-name=abby-demo&repository-name=ab-testing-abby)
33+
34+
### Clone and Deploy
35+
36+
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [pnpm](https://pnpm.io/installation) to bootstrap the example:
37+
38+
```bash
39+
pnpm create next-app --example https://github.com/vercel/examples/tree/main/edge-middleware/ab-testing-abby ab-testing-abby
40+
```
41+
42+
You'll need to have a free [A/BBY](https://tryabby.dev/login) account. Once that's done, copy the `.env.example` file in this directory to `.env.local` (which will be ignored by Git):
43+
44+
```bash
45+
cp .env.example .env.local
46+
```
47+
48+
Then open `.env.local` and set the `NEXT_PUBLIC_ABBY_PROJECT_ID` environment variable to your project's id. Your can copy your project's ID by clicking the Code button on the top right of your dashboard and selecting _Copy Project ID_.
49+
50+
The demo uses needs the following to be set in your A/BBY project:
51+
52+
- 1 Environment called _default_
53+
- 2 Feature Flags (_serverFlag_ and _clientFlag_)
54+
- 2 A/B Tests (_Home_ and _Marketing_)
55+
56+
Next, run Next.js in development mode:
57+
58+
```bash
59+
pnpm dev
60+
```
61+
62+
The `middleware.ts` file is used for the `/marketing` and `home/` routes. The user will see the page for his variant.
63+
64+
The index page ([pages/index.tsx](pages/index.tsx)) also shows how to do AB testing under the same path, in SSR and client-side.
65+
66+
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=edge-middleware-eap) ([Documentation](https://nextjs.org/docs/deployment)).

lib/ab-testing.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { createAbby } from "@tryabby/next";
2+
3+
if (typeof process.env.NEXT_PUBLIC_ABBY_PROJECT_ID != "string") {
4+
throw new Error("NEXT_PUBLIC_ABBY_PROJECT_ID is not defined");
5+
}
6+
7+
export const abby = createAbby({
8+
projectId: process.env.NEXT_PUBLIC_ABBY_PROJECT_ID,
9+
currentEnvironment: "default",
10+
tests: {
11+
Home: {
12+
variants: ["A", "B", "C"],
13+
},
14+
Marketing: {
15+
variants: ["b", "c", "original"],
16+
},
17+
},
18+
flags: ["clientFlag", "serverFlag"],
19+
});

middleware.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { NextResponse } from "next/server";
2+
import { abby } from "@lib/ab-testing";
3+
4+
export const config = {
5+
matcher: ["/home", "/marketing"],
6+
};
7+
8+
export default abby.withAbbyEdge((req) => {
9+
const { pathname } = req.nextUrl;
10+
if (pathname === "/home") {
11+
const [bucket, setCookie] = abby.getABTestValue("Home", req);
12+
const url = req.nextUrl.clone();
13+
url.pathname = `/home/${bucket}`;
14+
const res = NextResponse.rewrite(url);
15+
setCookie(res);
16+
return res;
17+
}
18+
19+
if (pathname === "/marketing") {
20+
const [bucket, setCookie] = abby.getABTestValue("Marketing", req);
21+
const url = req.nextUrl.clone();
22+
url.pathname = `/marketing/${bucket}`;
23+
const res = NextResponse.rewrite(url);
24+
setCookie(res);
25+
return res;
26+
}
27+
});

next-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/image-types/global" />
3+
4+
// NOTE: This file should not be edited
5+
// see https://nextjs.org/docs/basic-features/typescript for more information.

package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "ab-testing-abby",
3+
"repository": "https://github.com/vercel/examples.git",
4+
"license": "MIT",
5+
"private": true,
6+
"scripts": {
7+
"dev": "next dev",
8+
"build": "next build",
9+
"start": "next start",
10+
"lint": "next lint"
11+
},
12+
"dependencies": {
13+
"@tryabby/next": "^3.0.1",
14+
"@vercel/examples-ui": "^1.0.5",
15+
"next": "latest",
16+
"react": "latest",
17+
"react-dom": "latest"
18+
},
19+
"devDependencies": {
20+
"@types/node": "^17.0.45",
21+
"@types/react": "^18.0.28",
22+
"autoprefixer": "^10.4.14",
23+
"eslint": "^8.36.0",
24+
"eslint-config-next": "latest",
25+
"postcss": "^8.4.21",
26+
"tailwindcss": "^3.2.7",
27+
"turbo": "^1.8.5",
28+
"typescript": "^4.9.5"
29+
}
30+
}

pages/_app.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { AppProps } from 'next/app'
2+
import type { LayoutProps } from '@vercel/examples-ui/layout'
3+
import { getLayout } from '@vercel/examples-ui'
4+
import { abby } from '@lib/ab-testing'
5+
import '@vercel/examples-ui/globals.css'
6+
import { ABBY_DATA_KEY } from '@tryabby/next'
7+
8+
function MyApp({ Component, pageProps: { [ABBY_DATA_KEY]: abbyData, ...pageProps } }: AppProps) {
9+
const Layout = getLayout<LayoutProps>(Component)
10+
11+
return (
12+
<abby.AbbyProvider initialData={abbyData}>
13+
<Layout
14+
title="AB Testing with A/BBY"
15+
path="edge-middleware/abby-ab-testing"
16+
>
17+
<Component {...pageProps} />
18+
</Layout>
19+
</abby.AbbyProvider>
20+
)
21+
}
22+
23+
24+
export default abby.withAbby(MyApp)

pages/home/[variant].tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { abby } from "@lib/ab-testing";
2+
import { Button, Layout, Page, Text } from "@vercel/examples-ui";
3+
import { useRouter } from "next/router";
4+
5+
export default function Home() {
6+
const router = useRouter();
7+
8+
const resetStoredVariant = () => {
9+
const resetVariant = abby.getABResetFunction("Home");
10+
resetVariant();
11+
router.reload();
12+
};
13+
const variant = (router.query.variant ?? "") as string;
14+
15+
return (
16+
<Page>
17+
<Text variant="h2" className="mb-6">
18+
Home page variant
19+
</Text>
20+
<Text className="text-lg mb-4">
21+
You&apos;re currently on <b>variant {variant.toUpperCase()}</b>
22+
</Text>
23+
<Button variant="black" onClick={resetStoredVariant}>
24+
Remove bucket
25+
</Button>
26+
</Page>
27+
);
28+
}
29+
30+
export async function getStaticPaths() {
31+
return {
32+
paths: abby.getVariants("Home").map(variant => {
33+
return {
34+
params: {
35+
variant,
36+
},
37+
};
38+
}),
39+
fallback: false,
40+
}
41+
}
42+
43+
export async function getStaticProps() {
44+
// Here you would return data about the bucket
45+
return { props: {} }
46+
}
47+
48+
Home.Layout = Layout;

pages/index.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { abby } from "@lib/ab-testing";
2+
import { Layout, Page, Text, Link, List } from "@vercel/examples-ui";
3+
import { GetStaticPropsResult, InferGetStaticPropsType } from "next";
4+
5+
type Props = InferGetStaticPropsType<typeof getStaticProps>;
6+
export default function Index({ myServerFlag }: Props) {
7+
const clientFlag = abby.useFeatureFlag("clientFlag");
8+
return (
9+
<Page>
10+
<Text variant="h1" className="mb-6">
11+
AB testing with buckets
12+
</Text>
13+
<Text className="mb-4">
14+
In this demo we use cookies to assign a bucket with the variant to show.
15+
Visit one of the pages below and a bucket will be assigned to you.
16+
</Text>
17+
<List>
18+
<li>
19+
<Link href="/home">/home</Link>
20+
</li>
21+
<li>
22+
<Link href="/marketing">/marketing</Link>
23+
</li>
24+
</List>
25+
26+
{clientFlag && (
27+
<Text className="mt-4">
28+
If you see this text the <b>client</b> flag is enabled
29+
</Text>
30+
)}
31+
{myServerFlag && (
32+
<Text className="mt-4">
33+
If you see this text the <b>server side</b> flag is enabled
34+
</Text>
35+
)}
36+
</Page>
37+
);
38+
}
39+
40+
export const getStaticProps = () => {
41+
const myServerFlag = abby.getFeatureFlagValue("serverFlag");
42+
return {
43+
props: { myServerFlag },
44+
} satisfies GetStaticPropsResult<{}>;
45+
};
46+
47+
Index.Layout = Layout;

pages/marketing/[variant].tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { abby } from "@lib/ab-testing";
2+
import { Button, Layout, Page, Text } from "@vercel/examples-ui";
3+
import { useRouter } from "next/router";
4+
5+
export default function Marketing() {
6+
const router = useRouter();
7+
8+
const resetStoredVariant = () => {
9+
const resetVariant = abby.getABResetFunction("Marketing");
10+
resetVariant();
11+
router.reload();
12+
};
13+
const variant = (router.query.variant ?? "") as string;
14+
15+
return (
16+
<Page>
17+
<Text variant="h2" className="mb-6">
18+
Marketing page variant
19+
</Text>
20+
<Text className="text-lg mb-4">
21+
You&apos;re currently on <b>variant {variant.toUpperCase()}</b>
22+
</Text>
23+
24+
<Button type="button" variant="black" onClick={resetStoredVariant}>
25+
Reset Variant
26+
</Button>
27+
</Page>
28+
);
29+
}
30+
31+
export async function getStaticPaths() {
32+
return {
33+
paths: abby.getVariants("Marketing").filter(v => v !== "original").map(variant => {
34+
return {
35+
params: {
36+
variant,
37+
},
38+
};
39+
}),
40+
fallback: false,
41+
}
42+
}
43+
44+
45+
export async function getStaticProps() {
46+
// Here you would return data about the bucket
47+
return { props: {} }
48+
}
49+
50+
Marketing.Layout = Layout;

0 commit comments

Comments
 (0)