Use Hypertune on static pages

1. Set up Hypertune

To use feature flags and run experiments on static pages without layout shift or flickering, first integrate Hypertune using one of the quickstarts:

These quickstarts instruct you to wrap your page with the generated <HypertuneHydrator> and <HypertuneRootProvider> components, passing dehydratedState and rootArgs from the server. However, while this avoids layout shift, it requires getHypertune, which makes the page dynamic.

Instead, this guide shows you how to:

  • Generate a static variant of your page for each combination of flag values.

  • Use Middleware to route each visitor to the correct variant.

2. Define the flags for your page

Create an array with the flags that you want to use on your page:

lib/constants.ts
import { FlagPath } from '@/generated/hypertune'

export const offerPageFlagPaths: FlagPath[] = [
  'exampleFlag',
  'enableDesignV2',
]

3. Add a Dynamic Segment

Add a Dynamic Segment to your page called [encodedFlagValues]. This will hold the encoded flag values in the URL.

Then, on your page:

  • Use the generated decodeFlagValues function to access flag values.

  • Add the generated <HypertuneClientLogger> component so flag evaluations and experiment exposures are logged on the client.

  • Return an empty array from generateStaticParams to enable ISR. This ensures that once a static variant of your page is generated, it is cached and reused.

app/offer/[encodedFlagValues]/page.tsx
import { decodeFlagValues } from '@/generated/hypertune'
import { HypertuneClientLogger } from '@/generated/hypertune.react'
import { offerPageFlagPaths } from '@/lib/constants'

export async function generateStaticParams() {
  // Enable ISR
  return []
}

export default async function Page({
  params,
}: {
  params: { encodedFlagValues: string }
}) {
  const { exampleFlag, enableDesignV2 } = decodeFlagValues(
    params.encodedFlagValues,
    offerPageFlagPaths
  )

  return (
    <div>
      <div>Example Flag: {String(exampleFlag)}</div>
      <div>Enable Design V2: {String(enableDesignV2)}</div>
      <HypertuneClientLogger flagPaths={offerPageFlagPaths} />
    </div>
  )
}

4. Encode flags in Middleware

In Middleware, match on your static page, encode the flag values for the visitor, and rewrite the URL to include them:

middleware.ts
import { NextRequest, NextResponse } from 'next/server'
import { flagFallbacks } from '@/generated/hypertune'
import { offerPageFlagPaths } from '@/lib/constants'
import getHypertune from '@/lib/getHypertune'

export const config = {
  matcher: '/offer',
}

export async function middleware(request: NextRequest) {
  const hypertune = await getHypertune()

  const encodedFlagValues = hypertune.encodeFlagValues({
    flagFallbacks,
    flagPaths: offerPageFlagPaths,
  })

  // Rewrites the request to include the encoded flag values
  const nextUrl = new URL(
    `/offer/${encodedFlagValues}${request.nextUrl.search}`,
    request.url
  )

  return NextResponse.rewrite(nextUrl, { request })
}

Summary

  1. A visitor requests /offer.

  2. Middleware computes their flag values, encodes them, and rewrites the request to /offer/[encodedFlagValues].

  3. Your page decodes the values and renders the correct static variant.

  4. Once generated, that variant is cached via ISR for future visitors who have the same flag values.

Benefits

  • Fully static pages with feature flags and experiments.

  • No hydration mismatch, layout shift, or flickering.

  • Variants cached at the edge for fast subsequent loads.

Last updated