-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
### 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
Showing
1,251 changed files
with
58,751 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
NEXT_PUBLIC_GOOGLE_TRACKING_ID = | ||
NEXT_PUBLIC_OPTIMIZE_CONTAINER_ID = |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"root": true, | ||
"extends": "next/core-web-vitals" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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): | ||
|
||
[](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
45
edge-middleware/ab-testing-google-optimize/components/layout.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const COOKIE_NAME = 'ab-optimize' |
23 changes: 23 additions & 0 deletions
23
edge-middleware/ab-testing-google-optimize/lib/optimize-experiments.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
11
edge-middleware/ab-testing-google-optimize/lib/optimize.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
edge-middleware/ab-testing-google-optimize/pages/[variant]/about.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'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), | ||
}, | ||
} | ||
} |
Oops, something went wrong.
b9ed483
There was a problem hiding this comment.
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
b9ed483
There was a problem hiding this comment.
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:
ai-chatgpt – ./solutions/ai-chatgpt
ai-chatgpt.vercel.app
ai-chatgpt-git-main-vercel-solutions-vtest314.vercel.app
ai-chat-example.vercel.app
ai-chatgpt-vercel-solutions-vtest314.vercel.app
nextgpt.vercel.app
chat-ai.vercel.app
b9ed483
There was a problem hiding this comment.
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
b9ed483
There was a problem hiding this comment.
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