Hypertune Toolbar

Overview

The Hypertune Toolbar lets you view and override feature flags directly in your frontend.

Setup

1. Ensure Tailwind CSS is set up

Make sure your project has Tailwind CSS set up and that you are importing its base styles.

2. Create a flag to control visibility

In the Hypertune UI, create a flag called showHypertuneToolbar. This flag will control whether the Hypertune Toolbar is visible.

3. Set the environment variable

Set the HYPERTUNE_INCLUDE_TOOLBAR environment variable to true:

HYPERTUNE_INCLUDE_TOOLBAR=true

4. Regenerate the client

Regenerate the client:

npx hypertune

5. Add the <HypertuneToolbar> component

Add the generated <HypertuneToolbar> component to your app, passing it the path to the flag you created earlier via the showFlagPath prop:

app/layout.tsx
import { HypertuneProvider } from '@/generated/hypertune.react'
import { HypertuneToolbar } from '@/generated/HypertuneToolbar'
import getHypertune from '@/lib/getHypertune'
import './globals.css'

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  const hypertune = await getHypertune()

  const dehydratedState = hypertune.dehydrate()
  const rootArgs = hypertune.getRootArgs()

  return (
    <HypertuneProvider
      createSourceOptions={{
        token: process.env.NEXT_PUBLIC_HYPERTUNE_TOKEN!,
      }}
      dehydratedState={dehydratedState}
      rootArgs={rootArgs}
    >
      <html lang="en">
        <body>
          {children}
          <HypertuneToolbar showFlagPath="showHypertuneToolbar" />
        </body>
      </html>
    </HypertuneProvider>
  )
}

6. Handle overrides on the server

Overrides are stored in a cookie called hypertuneOverride. If you're using Next.js or hydrating the SDK from the server, update your getHypertune function to read this cookie and pass it to hypertuneSource.setOverride:

lib/getHypertune.ts
import 'server-only'
import { DeepPartial } from 'hypertune'
import { unstable_noStore as noStore } from 'next/cache'
import { cookies } from 'next/headers'
import {
  createSource,
  overrideCookieName,
  Source,
} from '@/generated/hypertune'

const hypertuneSource = createSource({
  token: process.env.NEXT_PUBLIC_HYPERTUNE_TOKEN!,
})

export default async function getHypertune({
  isRouteHandler = false,
}: {
  isRouteHandler?: boolean
} = {}) {
  noStore()

  await hypertuneSource.initIfNeeded()

  hypertuneSource.setRemoteLoggingMode(
    isRouteHandler ? 'normal' : 'off'
  )

  hypertuneSource.setOverride(await getOverrideFromCookie())

  return hypertuneSource.root({
    args: {
      context: {
        environment: process.env.NODE_ENV,
        user: {
          id: 'e23cc9a8-0287-40aa-8500-6802df91e56a',
          name: 'Example User',
          email: '[email protected]',
        },
      },
    },
  })
}

async function getOverrideFromCookie(): Promise<DeepPartial<Source> | null> {
  try {
    const cookieStore = await cookies()
    const overrideCookie = cookieStore.get(overrideCookieName)

    if (!overrideCookie?.value) {
      return null
    }

    return JSON.parse(overrideCookie.value)
  } catch (error) {
    console.warn(
      `Failed to parse ${overrideCookieName} cookie:`,
      error
    )
    return null
  }
}

Last updated