Skip to content

Commit

Permalink
Update Hypertune example to use the new Feature Flags pattern (#924)
Browse files Browse the repository at this point in the history
Update Hypertune example to use the new Feature Flags pattern

---------

Co-authored-by: Michal Bock <[email protected]>
  • Loading branch information
miraan and SpeedyCoder authored Jun 11, 2024
1 parent fdb4f5f commit c1f0e04
Show file tree
Hide file tree
Showing 26 changed files with 2,883 additions and 1,995 deletions.
4 changes: 4 additions & 0 deletions edge-middleware/feature-flag-hypertune/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
HYPERTUNE_FRAMEWORK=nextApp
HYPERTUNE_PLATFORM=vercel
HYPERTUNE_OUTPUT_DIRECTORY_PATH=generated
HYPERTUNE_GET_HYPERTUNE_IMPORT_PATH=../lib/getHypertune
18 changes: 9 additions & 9 deletions edge-middleware/feature-flag-hypertune/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: Hypertune Integration example
slug: feature-flag-hypertune
description: Learn to use Hypertune, a powerful feature flag, A/B testing and app configuration platform.
description: Learn to use Hypertune, a powerful feature flag, A/B testing, analytics and app configuration platform.
framework: Next.js
useCase: Edge Middleware
css: Tailwind
Expand All @@ -12,19 +12,19 @@ relatedTemplates:
- feature-flag-apple-store
---

# Feature flags, A/B testing and app configuration with Hypertune and Vercel
# Feature flags, A/B testing, analytics and app configuration with Hypertune and Vercel

[Hypertune](https://www.hypertune.com/) is a powerful feature flag, A/B testing, analytics and app configuration platform. Optimized for TypeScript, React and Next.js. Built with full end-to-end type-safety and Git version control.
[Hypertune](https://www.hypertune.com/) is a powerful feature flag, A/B testing, analytics and app configuration platform. Optimized for TypeScript, React and Next.js. Built with full end-to-end type-safety and Git-style version control.

No need to juggle different SDKs for the server and the client. Install one SDK that works across the server and the client and is compatible with App Router and Server Components.
No need to juggle different SDKs for the server and the client. Install one SDK that works across the server and the client and is compatible with the App Router and Server Components.

Avoid cumulative layout shift, UI flickers, hydration errors and page load delay. Instantly initialize the SDK on the server from Vercel Edge Config. And instantly initialize the SDK on the client from server props on the first render.
Avoid layout shift, UI flickers, hydration errors and page load delay. Instantly initialize the SDK on the server from Vercel Edge Config. And instantly initialize the SDK on the client from server props on the first render.

Static typing and code generation gives you full end-to-end type-safety across your flag inputs, outputs and logic so you can be confident in your code and upgrade your developer experience.

This example shows how to use the [Hypertune integration](https://vercel.com/integrations/hypertune) with Vercel Edge Config to initialize the Hypertune SDK with near-zero latency on the server so you can access your feature flags and run A/B tests with no performance impact to your app.

It also shows how to integrate with the Vercel Toolbar to let you easily set local flag overrides while developing.
It also shows how to integrate Hypertune with Vercel's Flags SDK to use the Vercel Toolbar, to view and override your feature flags without leaving your frontend, and Vercel's Flags pattern.

## Deploy with Vercel

Expand All @@ -45,9 +45,9 @@ Once you've deployed your project, open the [Hypertune UI](https://app.hypertune
1. Clone your project's repository and `cd` into it
2. Run `vercel link` to link to the Vercel project
3. Run `vercel env pull .env.development.local` to pull your environment variables
4. Run `npm i`
5. Run `npm run dev`
4. Run `pnpm i`
5. Run `pnpm run dev`

### Add new feature flags

To add a new feature flag, create it in the [Hypertune UI](https://app.hypertune.com/), then regenerate the client with `npx hypertune` so you can access it with full end-to-end type-safety.
To add a new feature flag, create it in the [Hypertune UI](https://app.hypertune.com/), then regenerate the client with `pnpm hypertune` so you can access it with full end-to-end type-safety.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { getHypertuneData } from '@vercel/flags/providers/hypertune'

export async function GET(request: NextRequest) {
const access = await verifyAccess(request.headers.get('Authorization'))
if (!access) return NextResponse.json(null, { status: 401 })
if (!access) {
return NextResponse.json(null, { status: 401 })
}

const data = await getHypertuneData({
token: process.env.HYPERTUNE_ADMIN_TOKEN!,
})

return NextResponse.json<ApiData>(data)
}
5 changes: 2 additions & 3 deletions edge-middleware/feature-flag-hypertune/app/api/flags/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ export const runtime = 'edge'
export const dynamic = 'force-dynamic'

export async function GET() {
const rootNode = await getHypertune()
const hypertune = await getHypertune()

const exampleFlag = rootNode.exampleFlag().get(/* fallback */ false)
console.log('Edge Function flag:', exampleFlag)
const exampleFlag = hypertune.exampleFlag({ fallback: false })

return NextResponse.json({ exampleFlag })
}
37 changes: 21 additions & 16 deletions edge-middleware/feature-flag-hypertune/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
import type { ReactNode } from 'react'
import { Layout, getMetadata } from '@vercel/examples-ui'
import { VercelToolbar } from '@vercel/toolbar/next'
import { HypertuneSourceProvider } from '../generated/hypertune.react'
import '@vercel/examples-ui/globals.css'

export const metadata = getMetadata({
title: 'feature-flag-hypertune',
description: 'An example showing how to use Vercel with Hypertune',
description: 'An example showing how to use Hypertune with Vercel.',
})

export const runtime = 'edge'

export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{ children: ReactNode }>) {
return (
<html lang="en">
<body>
<Layout
path="edge-middleware/feature-flag-hypertune"
deployButton={{
customDeployUrl:
'https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fhypertunehq%2Fvercel-examples-fork%2Ftree%2Fmain%2Fedge-middleware%2Ffeature-flag-hypertune&env=NEXT_PUBLIC_HYPERTUNE_TOKEN,HYPERTUNE_ADMIN_TOKEN,EDGE_CONFIG,EDGE_CONFIG_HYPERTUNE_ITEM_KEY,FLAGS_SECRET&envDescription=Environment%20variables%20needed%20to%20use%20Hypertune%20with%20Vercel%20Edge%20Config%20and%20the%20Vercel%20Toolbar&envLink=https%3A%2F%2Fdocs.hypertune.com%2Fgetting-started%2Fvercel-quickstart&project-name=feature-flag-hypertune&repository-name=feature-flag-hypertune&demo-title=Hypertune%20with%20Vercel&demo-description=Use%20Hypertune%20with%20Vercel%20Edge%20Config%20and%20the%20Vercel%20Toolbar&demo-url=https%3A%2F%2Ffeature-flag-hypertune.vercel.app%2F&demo-image=https%3A%2F%2Ffeature-flag-hypertune.vercel.app%2Fdemo.png&integration-ids=oac_naLXREDG2o9KihTGYBVz9fVl',
}}
>
{children}
<VercelToolbar />
</Layout>
</body>
</html>
<HypertuneSourceProvider
createSourceOptions={{ token: process.env.NEXT_PUBLIC_HYPERTUNE_TOKEN! }}
>
<html lang="en">
<body>
<Layout
path="edge-middleware/feature-flag-hypertune"
deployButton={{
customDeployUrl:
'https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fhypertunehq%2Fvercel-examples-fork%2Ftree%2Fmain%2Fedge-middleware%2Ffeature-flag-hypertune&env=NEXT_PUBLIC_HYPERTUNE_TOKEN,HYPERTUNE_ADMIN_TOKEN,EDGE_CONFIG,EDGE_CONFIG_HYPERTUNE_ITEM_KEY,FLAGS_SECRET&envDescription=Environment%20variables%20needed%20to%20use%20Hypertune%20with%20Vercel%20Edge%20Config%20and%20the%20Vercel%20Toolbar&envLink=https%3A%2F%2Fdocs.hypertune.com%2Fgetting-started%2Fvercel-quickstart&project-name=feature-flag-hypertune&repository-name=feature-flag-hypertune&demo-title=Hypertune%20with%20Vercel&demo-description=Use%20Hypertune%20with%20Vercel%20Edge%20Config%20and%20the%20Vercel%20Toolbar&demo-url=https%3A%2F%2Ffeature-flag-hypertune.vercel.app%2F&demo-image=https%3A%2F%2Ffeature-flag-hypertune.vercel.app%2Fdemo.png&integration-ids=oac_naLXREDG2o9KihTGYBVz9fVl',
}}
>
{children}
<VercelToolbar />
</Layout>
</body>
</html>
</HypertuneSourceProvider>
)
}
23 changes: 14 additions & 9 deletions edge-middleware/feature-flag-hypertune/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react'
import { Text, Page, Link, List } from '@vercel/examples-ui'
import ServerExample from '../lib/ServerExample'
import ServerComponent from '../components/ServerComponent'
import ClientComponentWrapper from '../components/ClientComponentWrapper'

export const metadata = {
title: 'Vercel x Hypertune example',
description:
'An example showing how to use Hypertune and Vercel. This example builds on top of the Hypertune integration which syncs Hypertune flags into Edge Config, so you can read them from your application near-instantly.',
'An example showing how to use Hypertune with Vercel. This example builds on top of the Hypertune integration which syncs Hypertune flags into Edge Config so you can read them from your application near-instantly. It also shows how to integrate with the Vercel Toolbar so you can easily view and override your feature flags without leaving your frontend. Finally, it shows how to use the Vercel Feature Flags pattern to use flags on a static page.',
}

export const runtime = 'edge'
Expand All @@ -25,14 +26,18 @@ export default async function Home() {
</Link>{' '}
with Vercel Edge Config to initialize the Hypertune SDK with near-zero
latency on the server so you can access your feature flags and run A/B
tests with no performance impact to your app. It also shows how to
integrate with the Vercel Toolbar to let you easily set local flag
overrides while developing.
tests with no performance impact to your app.
</Text>
<Text>
It also shows how to integrate Hypertune with Vercel&apos;s Flags SDK
to use the Vercel Toolbar, to view and override your feature flags
without leaving your frontend, and Vercel&apos;s Flags pattern.
</Text>
</section>

<section className="flex flex-col gap-4">
<ServerExample />
<ServerComponent />
<ClientComponentWrapper />
<Text>
Once you&apos;ve deployed this project, open the{' '}
<Link href="https://app.hypertune.com/" target="_blank">
Expand All @@ -53,18 +58,18 @@ export default async function Home() {
your environment variables
</li>
<li>
Run <strong>npm i</strong>
Run <strong>pnpm i</strong>
</li>
<li>
Run <strong>npm run dev</strong>
Run <strong>pnpm run dev</strong>
</li>
</List>
<Text>
To add a new feature flag, create it in the{' '}
<Link href="https://app.hypertune.com/" target="_blank">
Hypertune UI
</Link>
, then regenerate the client with <strong>npx hypertune</strong> so
, then regenerate the client with <strong>pnpm hypertune</strong> so
you can access it with full end-to-end type-safety.
</Text>
</section>
Expand Down
15 changes: 15 additions & 0 deletions edge-middleware/feature-flag-hypertune/app/static/[code]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { unstable_generatePermutations as generatePermutations } from '@vercel/flags/next'
import { exampleFlagFlag } from '../../../generated/hypertune.vercel'
import precomputeFlags from '../../../lib/precomputeFlags'

export async function generateStaticParams() {
const codes = await generatePermutations(precomputeFlags)
return codes.map((code) => ({ code }))
}

export default async function Page({ params }: { params: { code: string } }) {
// access the precomputed result by passing params.code and precomputeFlags
const exampleFlag = await exampleFlagFlag(params.code, precomputeFlags)

return <div>Example Flag: {String(exampleFlag)}</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client'

import { Text } from '@vercel/examples-ui'
import { useHypertune } from '../generated/hypertune.react'

export default function ClientComponent(): React.ReactElement {
const hypertune = useHypertune()

const exampleFlag = hypertune.exampleFlag({ fallback: false })

return (
<Text>
(Client Component) Example Flag: <strong>{String(exampleFlag)}</strong>
</Text>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { merge } from 'hypertune'
import {
HypertuneHydrator,
HypertuneRootProvider,
} from '../generated/hypertune.react'
import getHypertune from '../lib/getHypertune'
import ClientComponent from './ClientComponent'

export default async function ClientComponentWrapper() {
const hypertune = await getHypertune()

const serverRootArgs = hypertune.getRootArgs()
const serverDehydratedState = hypertune.dehydrate()

return (
<HypertuneRootProvider
rootArgs={merge(
serverRootArgs
// Set real values for browser-only args, e.g.
// { context: { browserOnlyId: "1" } },
)}
>
<HypertuneHydrator dehydratedState={serverDehydratedState}>
<ClientComponent />
</HypertuneHydrator>
</HypertuneRootProvider>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Suspense } from 'react'
import { Text } from '@vercel/examples-ui'
import { VercelFlagValues } from '../generated/hypertune.vercel'
import getHypertune from '../lib/getHypertune'

export default async function ServerComponent() {
const hypertune = await getHypertune()

const exampleFlag = hypertune.exampleFlag({ fallback: false })

return (
<>
<Text>
(Server Component) Example Flag: <strong>{String(exampleFlag)}</strong>
</Text>

<Suspense fallback={null}>
<VercelFlagValues flagValues={hypertune.get()} />
</Suspense>
</>
)
}
Loading

0 comments on commit c1f0e04

Please sign in to comment.