-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add examples for flags with Toolbar (#878)
### Description Adds examples for LauchDarkly, Optimizely, Split, and Statsig to be used with the Toolbar. ### Demo URL See all attached Toolbar Flags deployments. ### Type of Change - [x] New Example - [ ] Example updates (Bug fixes, new features, etc.) - [ ] Other (changes to the codebase, but not to examples) --------- Co-authored-by: Dominik Ferber <[email protected]>
- Loading branch information
Showing
65 changed files
with
14,377 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,3 @@ | ||
{ | ||
"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,36 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
.yarn/install-state.gz | ||
|
||
# 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 | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.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,54 @@ | ||
--- | ||
name: Vercel Toolbar x LaunchDarkly example | ||
slug: toolbar-launchdarkly | ||
description: Learn how to set up the LaunchDarkly with the Vercel Toolbar | ||
framework: Next.js | ||
useCase: Vercel Toolbar | ||
css: Tailwind | ||
deployUrl: https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/toolbar/toolbar-launchdarkly&project-name=toolbar-launchdarkly&repository-name=toolbar-launchdarkly&env=LAUNCHDARKLY_API_KEY&env=LAUNCHDARKLY_ENV&env=LAUNCHDARKLY_PROJECT_KEY&env=FLAGS_SECRET | ||
demoUrl: https://toolbar-launchdarkly.vercel.app | ||
relatedTemplates: | ||
- toolbar-optimizely | ||
- toolbar-split | ||
- toolbar-statsig | ||
--- | ||
|
||
# toolbar-launchdarkly example | ||
|
||
This example shows how to combine the Vercel Toolbar with LaunchDarkly in Next.js. | ||
|
||
You will need to provide the `LAUNCHDARKLY_API_KEY`, `LAUNCHDARKLY_ENV` and `LAUNCHDARKLY_PROJECT_KEY` environment variables. | ||
|
||
You will also need to provide a `FLAGS_SECRET` environment variable. You can generate one with `node -e "console.log(crypto.randomBytes(32).toString('base64url'))"`. | ||
|
||
When running locally, you will need to run `vercel link` to link one of your Vercel projects. The environment variables mentioned above need to be defined on the linked project in the Vercel dashboard. Having them in local `.env` files is not enough. | ||
|
||
## Demo | ||
|
||
https://toolbar-launchdarkly.vercel.app | ||
|
||
## How to Use | ||
|
||
You can choose from one of the following two methods to use this repository: | ||
|
||
### One-Click Deploy | ||
|
||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples): | ||
|
||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/toolbar/toolbar-launchdarkly&project-name=toolbar-launchdarkly&repository-name=toolbar-launchdarkly&env=LAUNCHDARKLY_API_KEY&env=LAUNCHDARKLY_ENV&env=LAUNCHDARKLY_PROJECT_KEY&env=FLAGS_SECRET) | ||
|
||
### Clone and Deploy | ||
|
||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [pnpm](https://pnpm.io/installation) to bootstrap the example: | ||
|
||
```bash | ||
pnpm create next-app --example https://github.com/vercel/examples/tree/main/toolbar/toolbar-launchdarkly | ||
``` | ||
|
||
Next, run Next.js in development mode: | ||
|
||
```bash | ||
pnpm dev | ||
``` | ||
|
||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=toolbar-launchdarkly) ([Documentation](https://nextjs.org/docs/deployment)). |
16 changes: 16 additions & 0 deletions
16
toolbar/toolbar-launchdarkly/app/.well-known/vercel/flags/route.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,16 @@ | ||
import { getLaunchDarklyData } from '@vercel/flags/providers/launchdarkly' | ||
import { type NextRequest, NextResponse } from 'next/server' | ||
import { type ApiData, verifyAccess } from '@vercel/flags' | ||
|
||
export async function GET(request: NextRequest) { | ||
const access = await verifyAccess(request.headers.get('Authorization')) | ||
if (!access) return NextResponse.json(null, { status: 401 }) | ||
|
||
const data = await getLaunchDarklyData({ | ||
apiKey: process.env.LAUNCHDARKLY_API_KEY!, | ||
environment: process.env.LAUNCHDARKLY_ENV!, | ||
projectKey: process.env.LAUNCHDARKLY_PROJECT_KEY!, | ||
}) | ||
|
||
return NextResponse.json<ApiData>(data) | ||
} |
Binary file not shown.
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,33 @@ | ||
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
|
||
:root { | ||
--foreground-rgb: 0, 0, 0; | ||
--background-start-rgb: 214, 219, 220; | ||
--background-end-rgb: 255, 255, 255; | ||
} | ||
|
||
@media (prefers-color-scheme: dark) { | ||
:root { | ||
--foreground-rgb: 255, 255, 255; | ||
--background-start-rgb: 0, 0, 0; | ||
--background-end-rgb: 0, 0, 0; | ||
} | ||
} | ||
|
||
body { | ||
color: rgb(var(--foreground-rgb)); | ||
background: linear-gradient( | ||
to bottom, | ||
transparent, | ||
rgb(var(--background-end-rgb)) | ||
) | ||
rgb(var(--background-start-rgb)); | ||
} | ||
|
||
@layer utilities { | ||
.text-balance { | ||
text-wrap: balance; | ||
} | ||
} |
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,16 @@ | ||
import * as LaunchDarkly from '@launchdarkly/node-server-sdk' | ||
let launchDarklyClient: LaunchDarkly.LDClient | ||
|
||
async function initialize() { | ||
launchDarklyClient = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY!) | ||
return launchDarklyClient.waitForInitialization() | ||
} | ||
|
||
export async function getClient(): Promise<LaunchDarkly.LDClient> { | ||
if (launchDarklyClient) { | ||
await launchDarklyClient.waitForInitialization() | ||
return launchDarklyClient | ||
} | ||
await initialize() | ||
return launchDarklyClient | ||
} |
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,49 @@ | ||
import type { Metadata } from 'next' | ||
import { Inter } from 'next/font/google' | ||
import './globals.css' | ||
import { VercelToolbar } from '@vercel/toolbar/next' | ||
import { FlagValues } from '@vercel/flags/react' | ||
import { Suspense } from 'react' | ||
import { FlagValuesType, encrypt } from '@vercel/flags' | ||
|
||
const inter = Inter({ subsets: ['latin'] }) | ||
|
||
export const metadata: Metadata = { | ||
title: 'Toolbar Flags', | ||
} | ||
|
||
async function ConfidentialFlagValues({ | ||
flagValues, | ||
}: { | ||
flagValues: FlagValuesType | ||
}) { | ||
const encryptedFlagValues = await encrypt(flagValues) | ||
return <FlagValues values={encryptedFlagValues} /> | ||
} | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: Readonly<{ | ||
children: React.ReactNode | ||
}>) { | ||
const flags = { instructions: true } | ||
|
||
return ( | ||
<html lang="en"> | ||
<body className={inter.className}> | ||
{children} | ||
{/* | ||
⚠️ This shows the toolbar to all visitors | ||
See detailed instructions to limit who can see the toolbar for real applications: | ||
https://vercel.com/docs/workflow-collaboration/vercel-toolbar/in-production-and-localhost/add-to-production | ||
*/} | ||
<VercelToolbar /> | ||
{/* Render this tag to tell the application about your flags */} | ||
<Suspense fallback={null}> | ||
<ConfidentialFlagValues flagValues={flags} /> | ||
</Suspense> | ||
</body> | ||
</html> | ||
) | ||
} |
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,181 @@ | ||
import Image from 'next/image' | ||
import { cookies } from 'next/headers' | ||
import { Suspense } from 'react' | ||
import { | ||
encrypt, | ||
decrypt, | ||
type FlagValuesType, | ||
type FlagOverridesType, | ||
} from '@vercel/flags' | ||
import { FlagValues } from '@vercel/flags/react' | ||
import { LDContext } from '@launchdarkly/node-server-sdk' | ||
import { getClient } from './launchdarkly' | ||
|
||
/** | ||
* A function which respects overrides set by the Toolbar, and returns feature flags. | ||
*/ | ||
async function getFlags() { | ||
const overridesCookieValue = cookies().get('vercel-flag-overrides')?.value | ||
const overrides = overridesCookieValue | ||
? await decrypt<FlagOverridesType>(overridesCookieValue) | ||
: null | ||
|
||
const client = await getClient() | ||
const context: LDContext = { key: 'context-key-123abc' } | ||
// const allFlags = client.allFlagsState(context); | ||
const flags = { | ||
// We fall back to false here, but you could fall back to values of your | ||
// flag provider instead | ||
docs: overrides?.docs ?? (await client.variation('docs', context, false)), | ||
learn: | ||
overrides?.learn ?? (await client.variation('learn', context, false)), | ||
templates: | ||
overrides?.templates ?? | ||
(await client.variation('templates', context, false)), | ||
deploy: | ||
overrides?.deploy ?? (await client.variation('deploy', context, false)), | ||
} | ||
return flags | ||
} | ||
|
||
async function ConfidentialFlagValues({ | ||
flagValues, | ||
}: { | ||
flagValues: FlagValuesType | ||
}) { | ||
const encryptedFlagValues = await encrypt(flagValues) | ||
return <FlagValues values={encryptedFlagValues} /> | ||
} | ||
|
||
export default async function Home() { | ||
// Note that this top-level await is blocking rendering. | ||
// In a real application you could extract the Docs, Learn, Templates, Deploy | ||
// components into their own suspense boundaries to allow flushing the main | ||
// tag without blocking on the flags. | ||
const flags = await getFlags() | ||
|
||
return ( | ||
<main className="flex min-h-screen flex-col items-center justify-between p-24"> | ||
<Suspense fallback={null}> | ||
<ConfidentialFlagValues flagValues={flags} /> | ||
</Suspense> | ||
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex"> | ||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30"> | ||
Get started by editing | ||
<code className="font-mono font-bold">app/page.tsx</code> | ||
</p> | ||
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none"> | ||
<a | ||
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0" | ||
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
By{' '} | ||
<Image | ||
src="/vercel.svg" | ||
alt="Vercel Logo" | ||
className="dark:invert" | ||
width={100} | ||
height={24} | ||
priority | ||
/> | ||
</a> | ||
</div> | ||
</div> | ||
|
||
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]"> | ||
<Image | ||
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert" | ||
src="/next.svg" | ||
alt="Next.js Logo" | ||
width={180} | ||
height={37} | ||
priority | ||
/> | ||
</div> | ||
|
||
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left"> | ||
{/* Using a feature flag here to conditionally show or hide the pill */} | ||
{flags.docs && ( | ||
<a | ||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" | ||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<h2 className={`mb-3 text-2xl font-semibold`}> | ||
Docs{' '} | ||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none"> | ||
-> | ||
</span> | ||
</h2> | ||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}> | ||
Find in-depth information about Next.js features and API. | ||
</p> | ||
</a> | ||
)} | ||
|
||
{/* Using a feature flag here to conditionally show or hide the pill */} | ||
{flags.learn && ( | ||
<a | ||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<h2 className={`mb-3 text-2xl font-semibold`}> | ||
Learn{' '} | ||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none"> | ||
-> | ||
</span> | ||
</h2> | ||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}> | ||
Learn about Next.js in an interactive course with quizzes! | ||
</p> | ||
</a> | ||
)} | ||
|
||
{/* Using a feature flag here to conditionally show or hide the pill */} | ||
{flags.templates && ( | ||
<a | ||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" | ||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<h2 className={`mb-3 text-2xl font-semibold`}> | ||
Templates{' '} | ||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none"> | ||
-> | ||
</span> | ||
</h2> | ||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}> | ||
Explore starter templates for Next.js. | ||
</p> | ||
</a> | ||
)} | ||
|
||
{/* Using a feature flag here to conditionally show or hide the pill */} | ||
{flags.deploy && ( | ||
<a | ||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" | ||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<h2 className={`mb-3 text-2xl font-semibold`}> | ||
Deploy{' '} | ||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none"> | ||
-> | ||
</span> | ||
</h2> | ||
<p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}> | ||
Instantly deploy your Next.js site to a shareable URL with Vercel. | ||
</p> | ||
</a> | ||
)} | ||
</div> | ||
</main> | ||
) | ||
} |
Oops, something went wrong.