diff --git a/contentful/README.md b/contentful/README.md
index 995fc1b..05e4f5c 100644
--- a/contentful/README.md
+++ b/contentful/README.md
@@ -1,12 +1,117 @@
-## Getting Started
+## Description
-First, run the development server:
+This is an example front-end app that uses Contentful as its Content Management System. In Contentful, there is a Growthbook app plugin that, once installed, will let your content creators create experiments on their own. This app has an example of how you can take a `Growthbook Experiment` content model and show the right variation to the user.
-```bash
-npm run dev
+## Configuration
+
+1. **Setting up Growthbook**
+
+ a. First, create a free [GrowthBook Account](https://www.growthbook.io) and add a new SDK Connection, selecting JavaScript (or React) as the language.
+
+ b. Create a new [datasource](https://docs.growthbook.io/warehouses) and connect it to your data warehouse.
+
+ c. Go to **Settings -> API keys** and create a key with an `admin` role. (This is needed for Contentful to connect to Growthbook and add experiments and feature flags.)
+
+2. **Setting up Contentful**
+
+ a. Next, create a free [Contentful Account](https://www.contentful.com).
+
+ b. Click the gear icon in the upper right and go to **API keys** and create a new API key. (This is used by your front-end app/this example app to connect to Contentful to get the content.)
+
+ c. Also go to the gear icon and click on **CMA tokens** and create a new token. (This is used by a script within this example app to create example Content Models and Content within Contentful without you having to set it up manually.)
+
+ d. Add the Growthbook App to your Contentful Space by going to **Apps -> Marketplace -> A/B tests -> Growthbook** and clicking Install. On the configuration page, enter the URL of your Growthbook instance, the API Key from step 1c, and the Datasource from step 1b. This will create a new content model called `Growthbook Experiment` that you can use to create experiments in Contentful.
+
+3. **Setting up this example app**
+
+ a. Next, in this example root, copy `.env.local.example` to `.env.local` and fill in the values from your GrowthBook SDK Connection (step 1a), the Contentful API key info (step 2b), and the Contentful CMA token (step 2c).
+
+ b. In the terminal run: `npm run create-contentful-content`. This will create the `Growthbook Example Page` and the `Growthbook Example Product` Content Models in Contentful. It will also create a `Growthbook Example Product Page` and four products as Contentful Content.
+
+ c. Run `npm run dev` and go to the [product page](http://localhost:3000) to see the Contentful content.
+
+4. **Pretend to be a Content Editor creating a GB A/B test**
+
+ a. In the Contentful app, go to the **Growthbook Example Product Page**, click the three dots next to the **Control** t-shirt in the Products collection, and click **Remove**.
+
+ b. Click **Add content** and select **Growthbook Experiment** as the Content Type.
+
+ c. Enter `Contentful Example` for the Experiment name.
+
+ d. Click "Add Variation" and select the Link an existing variant option. Select the **Control t-shirt**.
+
+ e. Click "Add Variation" and select the Link an existing variant option. Select the **Variant t-shirt**.
+
+ f. The **Create New Experiment** should now be clickable. Click it.
+
+ g. Click start experiment.
+
+ h. Click **publish**.
+
+ i. Click `<-` to go back to the Product Page.
+
+ j. Click **Publish** on the Product Page.
+
+ k. Go to the [product page](http://localhost:3000) to see which content Growthbook has chosen to show. As this example initialized Growthbook with a random targeting attribute id, it pretends each page load is a new user. You can refresh a few times to see Growthbook serve the control t-shirt sometimes and the variant t-shirt other times.
+
+## How it Works
+
+This example uses server-side Growthbook. For client-side Growthbook, see the next-js example.
+
+The code in `src/app/lib/growthbookExperiment.ts` shows the GraphQL to get the **Growthbook Experiment** content model which contains the `featureFlagId` and the `variationsCollection`. In order to get the Variation you can either load the variation content by it's id and \_\_typename in a separate call to Contentful API, or add the fields for each Content Type you want to be able to experiment on like in the example below:
+
+```
+export const GROWTHBOOK_EXPERIMENT_GRAPHQL_FIELDS = `
+ sys {
+ id
+ }
+ featureFlagId
+ variationsCollection {
+ items {
+ sys {
+ id
+ }
+ __typename
+ ... on GbExampleProduct {
+ ${PRODUCT_GRAPHQL_FIELDS}
+ }
+ }
+ }
+`;
+```
+
+The `getVariation` function shows how to get the variation from it. Basically, calling `gb.getFeatureValue` with the `featureFlagId` will return the index of the variation to show in the `variationsCollection`.
+
+```
+export function getVariation(
+ gb: GrowthBook,
+ growthbookExperiment: GrowthbookExperimentInterface
+) {
+ const featureFlagId = growthbookExperiment.featureFlagId;
+ const variationsCollection = growthbookExperiment.variationsCollection;
+ const index = gb.getFeatureValue(featureFlagId ?? "", 0);
+
+ // This can only happen if the experiment is out of sync with the published version.
+ if (index > variationsCollection.items.length + 1) {
+ return variationsCollection.items[0];
+ }
+ const variation = variationsCollection.items[index];
+
+ return variation;
+}
+```
+
+The `src/app/page.tsx` file contains the Growthbook object and initialization. It also shows how you can convert GrowthbookExperiment to the variation the user should see.
+
+```
+ const items = page.productsCollection.items.map((item) => {
+ if (item.__typename === "GrowthbookExperiment") {
+ return getVariation(gb, item);
+ }
+ return item;
+ });
```
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+Server-fetched feature flags and experiment definitions are persisted in the Next.js data cache for 60 seconds (configurable in `src/app/lib/growthbookServer.ts`). For faster updates, there is a `POST /revalidate` route handler that can be triggered from an SDK Webhook in GrowthBook.
-Follow the instructions on the homepage to create an example for how Content creators can create
-Experiments within Contentful all on their own.
+Data about which variation the user saw is sent to the client where an analytics event is triggered (or console.log in these examples). This happens via the `GrowthBookTracking` client component defined in `src/app/lib/GrowthBookTracking`.
diff --git a/contentful/src/app/[slug]/page.tsx b/contentful/src/app/[slug]/page.tsx
deleted file mode 100644
index ae33323..0000000
--- a/contentful/src/app/[slug]/page.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { getPage } from "@/app/lib/page";
-import { ProductInterface } from "@/app/lib/product";
-import { getVariation } from "@/app/lib/growthbookExperiment";
-import { GrowthBook } from "@growthbook/growthbook";
-import { randomUUID } from "crypto";
-import { GrowthBookTracking } from "@/app/lib/growthbookTracking";
-
-interface PageProps {
- params: {
- slug: string;
- };
-}
-
-const Product = ({ product }: { product: ProductInterface }) => (
-
-
-
-
{product.title}
-
{product.description}
-
From ${product.price}
-
-
-
-
-);
-
-const Page = async ({ params }: PageProps) => {
- const gb = new GrowthBook({
- apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST,
- clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY,
- });
-
- await gb.init({ timeout: 1000 });
-
- // Set targeting attributes for the user
- // Normally this is set based on a cookie on logged in user data, but here
- // we use a random ID so we can see different variations
- await gb.setAttributes({
- id: randomUUID(),
- });
-
- const { slug } = await params;
-
- const page = await getPage(slug);
-
- if (!page) {
- return Page not found
;
- }
-
- const items = page.productsCollection.items.map((item) => {
- if (item.__typename === "GrowthbookExperiment") {
- return getVariation(gb, item);
- }
- return item;
- });
-
- // If the code above ran any experiments, get the tracking call data
- // This is passed into the client component below
- const trackingData = gb.getDeferredTrackingCalls();
-
- return (
-
-
{page.title}
-
{page.description}
-
Products
-
- {items.map((item) => (
-
- ))}
-
-
-
- );
-};
-
-export default Page;
diff --git a/contentful/src/app/page.tsx b/contentful/src/app/page.tsx
index e441395..6e7f8ea 100644
--- a/contentful/src/app/page.tsx
+++ b/contentful/src/app/page.tsx
@@ -1,185 +1,78 @@
-import Link from "next/link";
+import { getPage } from "@/app/lib/page";
+import { ProductInterface } from "@/app/lib/product";
+import { getVariation } from "@/app/lib/growthbookExperiment";
+import { GrowthBook } from "@growthbook/growthbook";
+import { randomUUID } from "crypto";
+import { GrowthBookTracking } from "@/app/lib/growthbookTracking";
+
+const Product = ({ product }: { product: ProductInterface }) => (
+
+
+
+
{product.title}
+
{product.description}
+
From ${product.price}
+
+
+
+
+);
+
+export default async function Home() {
+ const slug = "products";
+
+ const page = await getPage(slug);
+
+ if (!page) {
+ return (
+
+ Product page not found. Run `npm run create-contentful-content` to add
+ the exaple Growthbook Page to your Contentful space.
+
+ );
+ }
+
+ const gb = new GrowthBook({
+ apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST,
+ clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY,
+ });
+
+ await gb.init({ timeout: 1000 });
+
+ // Set targeting attributes for the user
+ // Normally this is set based on a cookie on logged in user data, but here
+ // we use a random ID so we can see different variations
+ await gb.setAttributes({
+ id: randomUUID(),
+ });
+
+ const items = page.productsCollection.items.map((item) => {
+ if (item.__typename === "GrowthbookExperiment") {
+ return getVariation(gb, item);
+ }
+ return item;
+ });
+
+ // If the code above ran any experiments, get the tracking call data
+ // This is passed into the client component below
+ const trackingData = gb.getDeferredTrackingCalls();
-export default function Home() {
return (
-
-
-
Description
-
- This is an example front-end app that uses Contentful as it's Content
- Management System. In Contentful there is a Growthbook app plugin that
- once installed, will let your content creators create experiments on
- their own. This app has an example of how you can take a `Growthbook
- Experiment` content model and show the right variation to the user.
-
-
Configuration
-
-
- - Setting up Growthbook
-
- -
- First, create a free{" "}
-
- GrowthBook Account
- {" "}
- and add a new SDK Connection, selecting JavaScript (or React) as
- the language.
-
- -
- {" "}
- Create a new{" "}
-
- datasource
- {" "}
- and connect it to your data warehouse
-
- -
- {" "}
- Go to Settings->API keys and create a key with an
- `admin` role. (This is needed for Contentful to connect to
- Growthbook and add experiments and feature flags.)
-
-
- - Setting up Contentful
-
- -
- Next, create a free{" "}
-
- Contentful Account
- {" "}
-
- -
- Click the gear icon in the upper right and go to API keys{" "}
- and create a new API key. (This is used by your front-end
- app/this example app to connect to Contentful to get the
- content.)
-
- -
- Also go to the gear icon and click on CMA tokens and
- create a new token. (This is used by a script within this
- example app to create example Content Models and Content within
- Contentful without you having to set it up manually.)
-
- -
- Add the Growthbook App to your Contentful Space by going to{" "}
-
Apps->Marketplace->A/B tests->Growthbook{" "}
- and clicking Install.
-
On the configuration page, enter the URL of your
- Growthbook instance, the Api Key from step 1c. and the
- Datasource from step 1b. This will create a new content model
- called Growthbook Experiment
that you can use to
- create experiments in Contentful.
-
-
- - Setting up this example app
-
- -
- Next, in this example root, copy
.env.local.example
{" "}
- to .env.local
and fill in the values from your
- GrowthBook SDK Connection (step 1a), the Contentful API key info
- (step 2b), and the Contentful CMA token (step 2c).
-
- -
- In the terminal run:{" "}
-
npm run create-contentful-content
. This will create
- the Growthbook Example Page
and the{" "}
- Growthbook Example Product
Content Models in
- Contentful. It will also create a{" "}
- Growthbook Example Product Page
and four products
- as Contentful Content.
-
- -
- Run
npm run dev
and go to the{" "}
-
- product page
- {" "}
- to see the contentful content.
-
-
- - Pretend to be a Content Editor creating a GB A/B test.
-
- -
- In the Contentful app go to the{" "}
- Growthbook Example Product Page click the three dots next
- to the Control t-shirt in the Products collection and
- click Remove.
-
- -
- Click Add content and select Growthbook Experiment{" "}
- as the Content Type.{" "}
-
- -
- Enter
Contentful Example
for the Experiment name.
-
- -
- Click "Add Variation" and select the Link an existing variant
- option. Select the Control t-shirt
-
- -
- Click "Add Variation" and select the Link an existing variant
- option. Select the Variant t-shirt
-
- -
- The Create New Experiment should now be clickable. Click
- it.
-
- - Click start experiment.
- -
- Click publish.
-
- - Click <- to go back to the Product Page
- -
- Click Publish on the Product Page
-
- -
- Go to the{" "}
-
- product page
- {" "}
- to see which content Growthbook has chosen to show. As this
- example initialized growthbook with a random targeting attribute
- id, it pretends each page load is a new user. You can refresh a
- few times to see Growthbook serve the control t-shirt sometimes
- and the variant t-shirt the other times.
-
-
-
-
-
- How it Works
-
- This example uses server-side Growthbook. For client-side growthbook
- see the next-js example.
-
-
- The code in src/app/lib/growthbookExperiment.ts
shows
- the GraphQL to get the Growthbook Experiment content model
- which contains the featureFlagId
and the{" "}
- variationsCollection
. The getVariation
{" "}
- function show how to get the variation from it. Basically calling
- gb.getFeatureValue with the featureFlagId
will return
- the index of the variation to show in the{" "}
- variationsCollection
.
-
-
- The src/app/pages/[slug]/page.tsx
file contains the
- growthbook object and initialization.
-
-
- Server-fetched feature flags and experiment definitions are
- persisted in the Next.js data cache for 60 seconds (configurable in{" "}
- src/app/lib/growthbookServer.ts
). For faster updates,
- there is a POST /revalidate
route handler that can be
- triggered from an SDK Webhook in GrowthBook.
-
-
- Data about which variation the user saw is sent to the client where
- an analytics event is triggered (or console.log in these examples).
- This happens via the GrowthBookTracking
client
- component defined in src/app/lib/GrowthBookTracking
.
-
-
+
+
{page.title}
+
{page.description}
+
Products
+
+ {items.map((item) => (
+
+ ))}
-
+
+
);
}