Skip to content

Commit

Permalink
Duplicates edge-functions into edge-middleware. (#528)
Browse files Browse the repository at this point in the history
### Description

Simply copy / paste of `edge-functions` and changing the folder name to
`edge-middleware`.

This is unconventional, but the safest way to transition a rename, as
far as I can tell.

By duplicating, I can change all the deploy button links while also not
breaking templates as we're [***not*** going to sync these duplicate
examples to the Template
Marketplace](https://github.com/vercel/examples/blob/main/scripts/lib/update-changed-templates.ts#L5).

Future steps include:

1. Duplicate (this PR)
2. Change deploy buttons
3. Update examples, change Template Marketplace sync, add README
redirect notes to moved files
4. Change Template Marketplace urls and add redirects

> Steps 3 and 4 will need to be carefully / closely timed.

### Demo URL

Should allow ***both*** `edge-function` and `edge-middleware` deploy
button urls to work once merged:

*
https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/edge-functions/feature-flag-optimizely&env=OPTIMIZELY_SDK_KEY&project-name=feature-flag-optimizely&repository-name=feature-flag-optimizely
*
https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/edge-middleware/feature-flag-optimizely&env=OPTIMIZELY_SDK_KEY&project-name=feature-flag-optimizely&repository-name=feature-flag-optimizely

### Type of Change

- [x] Other (changes to the codebase, but not to examples)
  • Loading branch information
manovotny authored Jan 24, 2023
1 parent f52a98f commit b9ed483
Show file tree
Hide file tree
Showing 1,251 changed files with 58,751 additions and 0 deletions.
2 changes: 2 additions & 0 deletions edge-middleware/ab-testing-google-optimize/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
NEXT_PUBLIC_GOOGLE_TRACKING_ID =
NEXT_PUBLIC_OPTIMIZE_CONTAINER_ID =
4 changes: 4 additions & 0 deletions edge-middleware/ab-testing-google-optimize/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"root": true,
"extends": "next/core-web-vitals"
}
34 changes: 34 additions & 0 deletions edge-middleware/ab-testing-google-optimize/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 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/

# Production
/build

# 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
2 changes: 2 additions & 0 deletions edge-middleware/ab-testing-google-optimize/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Enabled to avoid deps failing to use next@canary
legacy-peer-deps=true
60 changes: 60 additions & 0 deletions edge-middleware/ab-testing-google-optimize/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
name: A/B Testing with Google Optimize
slug: ab-testing-google-optimize
description: Learn to use Google Optimize as an A/B testing solution for experimentation at the edge.
framework: Next.js
useCase: Edge Functions
css: Tailwind
deployUrl: https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/edge-functions/ab-testing-google-optimize&project-name=ab-testing-google-optimize&repository-name=ab-testing-google-optimize
demoUrl: https://edge-functions-ab-testing-google-optimize.vercel.app
---

# A/B Testing with Google Optimize

[Google Optimize](https://marketingplatform.google.com/about/optimize/) is Google' optimization tool and A/B testing solution, natively integrated with Google Analytics. In this example we'll do A/B testing with Optimize experiments at the edge.

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.

## Demo

https://edge-functions-ab-testing-google-optimize.vercel.app

### One-Click Deploy

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

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/edge-functions/ab-testing-google-optimize&project-name=ab-testing-google-optimize&repository-name=ab-testing-google-optimize)

## Getting Started

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
npx create-next-app --example https://github.com/vercel/examples/tree/main/edge-functions/ab-testing-google-optimize ab-testing-google-optimize
# or
yarn create next-app --example https://github.com/vercel/examples/tree/main/edge-functions/ab-testing-google-optimize ab-testing-google-optimize
```

[`middleware.ts`](middleware.ts) loads the experiments using a pre-defined JSON file ([lib/optimize-experiments.json](lib/optimize-experiments.json)), it currently has to be edited manually in order to add the experiments created in https://optimize.google.com.

Run Next.js in development mode:

```bash
npm install
npm run dev

# or

yarn
yarn dev
```

Once the page loads (http://localhost:3000) the layout will load the `optimize.js` script using your google tracking id, and the pages will register events for the current experiment and variant.

To create your own experiments you'll need an account with [Google Optimize](https://optimize.google.com/optimize/home). Once that's done, copy the `.env.example` file in this directory to `.env.local` (which will be ignored by Git):

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

Then open `.env.local` and set the environment variables to match the ones for your Google Optimize account.
45 changes: 45 additions & 0 deletions edge-middleware/ab-testing-google-optimize/components/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { FC } from 'react'
import Script from 'next/script'
import { Layout, Page } from '@vercel/examples-ui'
import type { LayoutProps } from '@vercel/examples-ui/layout'
import { GaProvider } from '@lib/useGa'

function throwIfSSR() {
throw new Error('Using GA during SSR is not allowed')
}

function gaHandler() {
const dataLayer = ((window as any).dataLayer =
(window as any).dataLayer || [])

dataLayer.push(arguments)
}

const OptimizeLayout: FC<LayoutProps> = ({ children, ...props }) => {
const ga = typeof window === 'undefined' ? throwIfSSR : gaHandler

return (
<Layout {...props}>
<Page>
{/* <Script
src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GOOGLE_TRACKING_ID}`}
onLoad={() => {
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
gtag('js', new Date())
gtag('config', process.env.NEXT_PUBLIC_GOOGLE_TRACKING_ID)
}}
/> */}
<Script
src={`https://www.googleoptimize.com/optimize.js?id=${process.env.NEXT_PUBLIC_OPTIMIZE_CONTAINER_ID}`}
/>
<GaProvider value={ga}>{children}</GaProvider>
</Page>
</Layout>
)
}

export default OptimizeLayout
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const COOKIE_NAME = 'ab-optimize'
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[
{
"name": "Green button sells more 2",
"id": "MFY6FYIySra93KqA3vs9UQ",
"variants": [
{
"name": "original",
"id": 0,
"weight": 33.3333
},
{
"name": "Variant 1",
"id": 1,
"weight": 33.3333
},
{
"name": "Variant 2",
"id": 2,
"weight": 33.3333
}
]
}
]
11 changes: 11 additions & 0 deletions edge-middleware/ab-testing-google-optimize/lib/optimize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import experiments from './optimize-experiments.json'

/**
* Returns the experiment to use, the overall flow is:
* - You created an experiment in Google Optimize
* - Then created the pages that will match that experiment, in this case pages/[variant]
* - Start experimenting and then make decisions without having changed the original pages
*/
export function getCurrentExperiment() {
return experiments.find((exp) => exp.name === 'Green button sells more 2')
}
7 changes: 7 additions & 0 deletions edge-middleware/ab-testing-google-optimize/lib/useGa.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createContext, useContext } from 'react'

const gaContext = createContext<any>(null)

export const GaProvider = gaContext.Provider

export const useGa = () => useContext(gaContext)
39 changes: 39 additions & 0 deletions edge-middleware/ab-testing-google-optimize/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { NextRequest, NextResponse } from 'next/server'
import { COOKIE_NAME } from '@lib/constants'
import { getCurrentExperiment } from '@lib/optimize'

export const config = {
matcher: ['/marketing', '/about'],
}

export function middleware(req: NextRequest) {
let cookie = req.cookies.get(COOKIE_NAME)?.value

if (!cookie) {
let n = Math.random() * 100
const experiment = getCurrentExperiment()
const variant = experiment.variants.find((v, i) => {
if (v.weight >= n) return true
n -= v.weight
})

cookie = `${experiment.id}.${variant.id}`
}

const [, variantId] = cookie.split('.')
const url = req.nextUrl

// `0` is the original version
if (variantId !== '0') {
url.pathname = url.pathname.replace('/', `/${cookie}/`)
}

const res = NextResponse.rewrite(url)

// Add the cookie if it's not there
if (!req.cookies.has(COOKIE_NAME)) {
res.cookies.set(COOKIE_NAME, cookie)
}

return res
}
6 changes: 6 additions & 0 deletions edge-middleware/ab-testing-google-optimize/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
29 changes: 29 additions & 0 deletions edge-middleware/ab-testing-google-optimize/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"repository": "https://github.com/vercel/examples.git",
"license": "MIT",
"private": true,
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@vercel/examples-ui": "^1.0.4",
"js-cookie": "^3.0.1",
"next": "canary",
"react": "latest",
"react-dom": "latest"
},
"devDependencies": {
"@types/js-cookie": "^3.0.1",
"@types/node": "^17.0.14",
"@types/react": "^17.0.38",
"autoprefixer": "^10.4.2",
"eslint": "^8.9.0",
"eslint-config-next": "canary",
"postcss": "^8.4.6",
"tailwindcss": "^3.0.18",
"typescript": "^4.5.5"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useEffect } from 'react'
import Cookies from 'js-cookie'
import { Text, Button } from '@vercel/examples-ui'
import { getCurrentExperiment } from '@lib/optimize'
import { COOKIE_NAME } from '@lib/constants'
import { useGa } from '@lib/useGa'
import OptimizeLayout from '@components/layout'

export default function About({ experiment, variant }) {
const ga = useGa()
const sendEvent = () => {
const event = {
hitType: 'event',
eventCategory: 'AB Testing',
eventAction: 'Clicked button',
eventLabel: 'AB Testing About button',
}
ga('send', event)
console.log('sent event:', event)
}

useEffect(() => {
const cookie = Cookies.get(COOKIE_NAME)
if (ga && cookie) {
ga('set', 'exp', cookie)
}
ga('send', 'pageview')
}, [ga])

return (
<>
<Text variant="h2" className="mb-6">
About Page
</Text>
<Text className="text-lg mb-4">
You&apos;re currently looking at the variant <b>{variant.name}</b> in
the experiment <b>{experiment.name}</b>
</Text>
<Text className="mb-4">
Click the button below to register an event with GA for this variant:
</Text>
<Button variant="secondary" onClick={sendEvent}>
Send event
</Button>
</>
)
}

About.Layout = OptimizeLayout

export async function getStaticPaths() {
const experiment = getCurrentExperiment()

return {
paths: experiment.variants.map((v) => ({
params: { variant: `${experiment.id}.${v.id}` },
})),
fallback: false,
}
}

export async function getStaticProps({ params }) {
const experiment = getCurrentExperiment()
const [, variantId] = params.variant.split('.')

// Here you could fetch any data related only to the variant
return {
props: {
// Only send the experiment data required by the page
experiment: { name: experiment.name },
variant: experiment.variants.find((v) => String(v.id) === variantId),
},
}
}
Loading

4 comments on commit b9ed483

@vercel
Copy link

@vercel vercel bot commented on b9ed483 Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

app-dir-share-state – ./app-directory/share-state

app-dir-share-state-git-main-now-examples.vercel.app
app-dir-share-state-now-examples.vercel.app
app-dir-share-state.vercel.app

@vercel
Copy link

@vercel vercel bot commented on b9ed483 Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on b9ed483 Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

app-dir-css-in-js – ./app-directory/css-in-js

app-dir-css-in-js.vercel.app
app-dir-css-in-js-git-main-now-examples.vercel.app
app-dir-css-in-js-now-examples.vercel.app

@vercel
Copy link

@vercel vercel bot commented on b9ed483 Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

static-tweets-tailwind – ./solutions/static-tweets-tailwind

static-tweets-tailwind-git-main-vercel-labs.vercel.app
static-tweets-tailwind-vercel-labs.vercel.app
static-tweets-tailwind.vercel.app

Please sign in to comment.