Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GrowthBook example #915

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions edge-middleware/ab-testing-growthbook/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NEXT_PUBLIC_GROWTHBOOK_API_HOST=
NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY=
NEXT_PUBLIC_GROWTHBOOK_DECRYPTION_KEY=
4 changes: 4 additions & 0 deletions edge-middleware/ab-testing-growthbook/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"root": true,
"extends": "next/core-web-vitals"
}
42 changes: 42 additions & 0 deletions edge-middleware/ab-testing-growthbook/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# Dependencies
/node_modules
/.pnp
.pnp.js

# Testing
/coverage

# Next.js
/.next/
/out/
next-env.d.ts

# Production
build
dist

# Misc
.DS_Store
*.pem

# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Local ENV files
.env.local
.env.development.local
.env.test.local
.env.production.local

# Vercel
.vercel

# Turborepo
.turbo

# typescript
*.tsbuildinfo
74 changes: 74 additions & 0 deletions edge-middleware/ab-testing-growthbook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
name: Run A/B Tests with GrowthBook
slug: ab-testing-growthbook
publisher: GrowthBook
description: An example app showcasing the many ways to integrate GrowthBook with your Next.js app.
framework:
- Next.js
- React
type: Edge Middleware
css: Tailwind
githubUrl: https://github.com/vercel/examples/tree/main/edge-middleware/ab-testing-growthbook
demoUrl: https://ab-testing-example-growthbook.vercel.app/
relatedTemplates:
- ab-testing-google-optimize
- ab-testing-simple
deployUrl: https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fgrowthbook%2Fexamples%2Ftree%2Fmain%2Fnext-js&env=NEXT_PUBLIC_GROWTHBOOK_API_HOST,NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY&envDescription=Find%20your%20API%20Host%20and%20Client%20Key%20under%20your%20SDK%20Connections%20in%20GrowthBook&envLink=https%3A%2F%2Fapp.growthbook.io%2Fsdks
---

# ab-testing-growthbook example

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

# ab-testing-growthbook example

This example illustrates the different ways you can integrate GrowthBook with your Next.js app.

## How to Use

You can choose from one of the following two methods to use this repository:

### One-Click Deploy

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fgrowthbook%2Fexamples%2Ftree%2Fmain%2Fnext-js&env=NEXT_PUBLIC_GROWTHBOOK_API_HOST,NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY&envDescription=Find%20your%20API%20Host%20and%20Client%20Key%20under%20your%20SDK%20Connections%20in%20GrowthBook&envLink=https%3A%2F%2Fapp.growthbook.io%2Fsdks)

### Clone and Deploy

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:

```bash
pnpm create next-app --example https://github.com/vercel/examples/tree/main/edge-middleware/ab-testing-growthbook
```

#### Set up environment variables

Log in to [GrowthBook](https://app.growthbook.io/) and navigate to **SDK Configuration -> SDK Connections**. Create an SDK connection for your app.
Then, copy [.env.example](./env.example) to `.env.local` and fill it in with your API Host and Client Key:

```bash
cp .env.example .env.local
```

(`NEXT_PUBLIC_GROWTHBOOK_DECRYPTION_KEY` is optional.)

Next, run Next.js in development mode:

```bash
pnpm dev
```

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)).
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use client'
import { onExperimentView } from '../../lib/GrowthBookTracking'
import ClientComponent from './ClientComponent'
import { GrowthBook, GrowthBookPayload } from '@growthbook/growthbook'
import { GrowthBookProvider } from '@growthbook/growthbook-react'
import { useMemo } from 'react'
import { GB_UUID_COOKIE } from '../../middleware'
import Cookies from 'js-cookie'

export default function ClientApp({ payload }: { payload: GrowthBookPayload }) {
// Create a singleton GrowthBook instance for this page
const gb = useMemo(
() =>
new GrowthBook({
apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST,
clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY,
decryptionKey: process.env.NEXT_PUBLIC_GROWTHBOOK_DECRYPTION_KEY,
trackingCallback: onExperimentView,
attributes: {
id: Cookies.get(GB_UUID_COOKIE),
},
}).initSync({
payload,
// Optional, enable streaming updates
streaming: true,
}),
[payload]
)

return (
<GrowthBookProvider growthbook={gb}>
<ClientComponent />
</GrowthBookProvider>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client'
import RevalidateMessage from '../revalidate/RevalidateMessage'
import { useFeatureIsOn, useFeatureValue } from '@growthbook/growthbook-react'
import { Text, List } from '@vercel/examples-ui'

export default function ClientComponent() {
const feature1Enabled = useFeatureIsOn('feature1')
const feature2Value = useFeatureValue('feature2', 'fallback')
return (
<section className="flex flex-col gap-3">
<Text variant="h2">Optimized Client Rendering</Text>
<Text>
This page fetches feature flags and experiments at build time, but waits
to evaluate them until client-side rendering. This gives you flexibility
while avoiding flicker.
</Text>
<List variant="ul">
<li>
feature1: <strong>{feature1Enabled ? 'ON' : 'OFF'}</strong>
</li>
<li>
feature2: <strong>{feature2Value}</strong>
</li>
</List>

<RevalidateMessage />
</section>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { configureServerSideGrowthBook } from '../../lib/growthbookServer'
import ClientApp from './ClientApp'
import { GrowthBook } from '@growthbook/growthbook'
import { Page } from '@vercel/examples-ui'

export default async function PrerenderedClientPage() {
// Helper to configure cache for next.js
configureServerSideGrowthBook()

// Create and initialize a GrowthBook instance
const gb = new GrowthBook({
apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST,
clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY,
decryptionKey: process.env.NEXT_PUBLIC_GROWTHBOOK_DECRYPTION_KEY,
})
await gb.init({ timeout: 1000 })

// Get the payload to hydrate the client-side GrowthBook instance
// We need the decrypted payload so the initial client-render can be synchronous
const payload = gb.getDecryptedPayload()

// Cleanup your GrowthBook instance
gb.destroy()

return (
<Page className="flex flex-col gap-12">
<ClientApp payload={payload} />
</Page>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client'
import { List, Text, Link } from '@vercel/examples-ui'
import { useFeatureIsOn, useFeatureValue } from '@growthbook/growthbook-react'

export default function ClientComponent() {
const feature1Enabled = useFeatureIsOn('feature1')
const feature2Value = useFeatureValue('feature2', 'fallback')
return (
<section className="flex flex-col gap-3">
<Text variant="h2">Client Rendering (Unoptimized)</Text>
<Text>
This component renders entirely client-side. The page initially
delivered to the client will not have feature definitions loaded, and a
network request will be required. This can result in a
&apos;flicker&apos; where fallback values are rendered first and then
swapped in with their real values later.
</Text>
<Text>
To avoid this flicker, check out the{' '}
<Link href="/client-optimized">Optimized Client</Link> example.
</Text>
<List variant="ul">
<li>
feature1: <strong>{feature1Enabled ? 'ON' : 'OFF'}</strong>
</li>
<li>
feature2: <strong>{feature2Value}</strong>
</li>
</List>
</section>
)
}
46 changes: 46 additions & 0 deletions edge-middleware/ab-testing-growthbook/app/client/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client'
import Cookies from 'js-cookie'
import { GrowthBook, GrowthBookProvider } from '@growthbook/growthbook-react'
import { useEffect, useMemo } from 'react'
import ClientComponent from './ClientComponent'
import { GB_UUID_COOKIE } from '../../middleware'
import { onExperimentView } from '../../lib/GrowthBookTracking'
import { Page } from '@vercel/examples-ui'

export default function ClientPage() {
// Create a single memoized GrowthBook instance for the client
const gb = useMemo(() => {
return new GrowthBook({
apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST,
clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY,
decryptionKey: process.env.NEXT_PUBLIC_GROWTHBOOK_DECRYPTION_KEY,
trackingCallback: onExperimentView,
})
}, [])

useEffect(() => {
// Fetch feature payload from GrowthBook
gb.init({
// Optional, enable streaming updates
streaming: true,
})

// Set targeting attributes for the user
let uuid = Cookies.get(GB_UUID_COOKIE)
if (!uuid) {
uuid = Math.random().toString(36).substring(2)
Cookies.set(GB_UUID_COOKIE, uuid)
}
gb.setAttributes({
id: uuid,
})
}, [gb])

return (
<GrowthBookProvider growthbook={gb}>
<Page className="flex flex-col gap-12">
<ClientComponent />
</Page>
</GrowthBookProvider>
)
}
33 changes: 33 additions & 0 deletions edge-middleware/ab-testing-growthbook/app/hybrid/ClientApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use client'
import { onExperimentView } from '../../lib/GrowthBookTracking'
import { GrowthBookPayload } from '@growthbook/growthbook'
import { GrowthBook, GrowthBookProvider } from '@growthbook/growthbook-react'
import { PropsWithChildren, useMemo } from 'react'
import { GB_UUID_COOKIE } from '../../middleware'
import Cookies from 'js-cookie'

export default function ClientApp({
payload,
children,
}: PropsWithChildren<{ payload: GrowthBookPayload }>) {
// Create a singleton GrowthBook instance for this page
const gb = useMemo(
() =>
new GrowthBook({
apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST,
clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY,
decryptionKey: process.env.NEXT_PUBLIC_GROWTHBOOK_DECRYPTION_KEY,
trackingCallback: onExperimentView,
attributes: {
id: Cookies.get(GB_UUID_COOKIE),
},
}).initSync({
payload,
// Optional, enable streaming updates
streaming: true,
}),
[payload]
)

return <GrowthBookProvider growthbook={gb}>{children}</GrowthBookProvider>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use client'
import { useFeatureIsOn, useFeatureValue } from '@growthbook/growthbook-react'
import { List, Text } from '@vercel/examples-ui'

export default function ClientComponent() {
const feature1Enabled = useFeatureIsOn('feature1')
const feature2Value = useFeatureValue('feature2', 'fallback')
return (
<section className="flex flex-col gap-3">
<Text>And these features are rendered client-side:</Text>
<List variant="ul">
<li>
feature1: <strong>{feature1Enabled ? 'ON' : 'OFF'}</strong>
</li>
<li>
feature2: <strong>{feature2Value}</strong>
</li>
</List>
</section>
)
}
Loading