Skip to content

Vercel edge middleware version #9

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

Open
wants to merge 2 commits 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
1 change: 1 addition & 0 deletions packages/ssr-demo/contexts/experimentContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const ExperimentContext = createContext(null);
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const ExperimentProvider = (props) => {
const { value, children } = props;
console.log('experiment provider', value);
return (
<ExperimentContext.Provider value={value}>
{children}
Expand Down
41 changes: 41 additions & 0 deletions packages/ssr-demo/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { NextRequest, NextResponse } from 'next/server';

type HttpExperimentResult = {
[flagKey: string]:
| {
key: string;
payload?: Record<string, unknown>;
}
| undefined;
};

export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname === '/') {
const res = await fetch(
`https://api.lab.amplitude.com/v1/vardata?${new URLSearchParams({
user_id: 'userId',
}).toString()}`,
{
headers: new Headers({
Authorization: `Api-Key client-IAxMYws9vVQESrrK88aTcToyqMxiiJoR`,
}),
},
);
const features = (await res.json()) as HttpExperimentResult;

if (features?.['js-ssr-demo']?.key === 'true') {
return NextResponse.rewrite(
new URL('/experiment-outcomes/js-ssr-demo/variant', request.url),
);
}

return NextResponse.rewrite(
new URL('/experiment-outcomes/js-ssr-demo/control', request.url),
);
}
return NextResponse.next();
}

export const config = {
matcher: ['/'],
};
5 changes: 4 additions & 1 deletion packages/ssr-demo/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
/// <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.
15 changes: 15 additions & 0 deletions packages/ssr-demo/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
async redirects() {
return [
// this redirect prevents people from navigating straight to the experiment outcomes
{
source: '/experiment-outcomes/:path*',
destination: '/',
permanent: true,
},
];
},
};

module.exports = nextConfig;
12 changes: 6 additions & 6 deletions packages/ssr-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
"dependencies": {
"@amplitude/experiment-js-client": "^0.2.0",
"@amplitude/experiment-node-server": "^1.2.0",
"next": "9.5.5",
"next-transpile-modules": "^4.1.0",
"react": "16.13.1",
"react-dom": "16.13.1"
"next": "^12.2.0",
"next-transpile-modules": "^9.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/node": "^14.11.8",
"@types/react": "^16.9.52",
"typescript": "^4.0.3"
"@types/react": "^18.0.0",
"typescript": "^4.5.5"
}
}
12 changes: 9 additions & 3 deletions packages/ssr-demo/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { AppProps } from 'next/app';
import { ReactNode } from 'react';

import { ExperimentProvider } from '../contexts/experimentContext';
import { ExperimentServer } from '../lib/experiment';
import '../styles/globals.css';

let experiment: ExperimentClient;
Expand All @@ -18,15 +17,15 @@ function MyApp(appProps: AppProps): ReactNode {
experiment = new ExperimentClient(
'client-IAxMYws9vVQESrrK88aTcToyqMxiiJoR',
{
initialVariants: appProps['features'],
initialVariants: pageProps['features'],
},
);
} else if (!experiment) {
// in the client, we only want to create the ExperimentClient once
experiment = Experiment.initialize(
'client-IAxMYws9vVQESrrK88aTcToyqMxiiJoR',
{
initialVariants: appProps['features'],
initialVariants: pageProps['features'],
},
);
}
Expand All @@ -39,6 +38,10 @@ function MyApp(appProps: AppProps): ReactNode {
);
}

/**
* Since the fetch is happening in the middleware we don't need this anymore
*
*
MyApp.getInitialProps = async ({ ctx }) => {
// Fetch data from external APIs
if (ctx.req) {
Expand All @@ -53,5 +56,8 @@ MyApp.getInitialProps = async ({ ctx }) => {
return {};
}
};
*
*
*/

export default MyApp;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Variants } from '@amplitude/experiment-js-client';
import { GetStaticProps, NextPage } from 'next';
import Head from 'next/head';
import Link from 'next/link';
import { useContext } from 'react';

import { ExperimentContext } from '../../../contexts/experimentContext';
import styles from '../../../styles/Home.module.css';

export const Control: NextPage = () => {
const experiment = useContext(ExperimentContext);
const feature = experiment.variant('js-ssr-demo');
return (
<div className={styles.container}>
<Head>
<title>SSR Demo</title>
<link rel="icon" href="/favicon.ico" />
</Head>

<main className={styles.main}>
<h1 className={styles.title}>SSR demo for Experiment</h1>

<Link href="/client-link">Client side navigation demo</Link>
<p className={styles.description}>
If you see an image below, the feature flag is enabled
</p>
<p>{`js-ssr-demo: ${feature?.value}`}</p>
<p>{`payload: ${JSON.stringify(feature?.payload)}`}</p>
</main>
<footer className={styles.footer}>
<img
src="/amplitude-logo.svg"
alt="Flag enabled!"
className={styles.logo}
/>
</footer>
</div>
);
};

export const getStaticProps: GetStaticProps<{
features: Variants;
}> = async () => ({
props: {
features: {
'js-ssr-demo': {
value: 'false',
payload: { testKey: 'abc' },
},
},
},
});

export default Control;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Variants } from '@amplitude/experiment-js-client';
import { GetStaticProps } from 'next';
import Head from 'next/head';
import Link from 'next/link';
import { useContext } from 'react';

import { ExperimentContext } from '../../../contexts/experimentContext';
import styles from '../../../styles/Home.module.css';

export const Variant = () => {
const experiment = useContext(ExperimentContext);
const feature = experiment.variant('js-ssr-demo');

return (
<div className={styles.container}>
<Head>
<title>SSR Demo</title>
<link rel="icon" href="/favicon.ico" />
</Head>

<main className={styles.main}>
<h1 className={styles.title}>SSR demo for Experiment</h1>

<Link href="/client-link">Client side navigation demo</Link>
<p className={styles.description}>
If you see an image below, the feature flag is enabled
</p>
<p>{`js-ssr-demo: ${feature?.value}`}</p>
<p>{`payload: ${JSON.stringify(feature?.payload)}`}</p>
</main>
<footer className={styles.footer}>
<img
src="/amplitude-logo.svg"
alt="Flag enabled!"
className={styles.logo}
/>
</footer>
</div>
);
};

export const getStaticProps: GetStaticProps<{
features: Variants;
}> = async () => ({
props: {
features: {
'js-ssr-demo': {
value: 'true',
payload: { testKey: 'abc' },
},
},
},
});

export default Variant;
3 changes: 2 additions & 1 deletion packages/ssr-demo/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
"jsx": "preserve",
"incremental": true
},
"include": [
"next-env.d.ts",
Expand Down
Loading