Skip to content
Draft
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
2 changes: 1 addition & 1 deletion apps/docs/content/docs/api-reference/frameworks/next.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ const sale = await showSummerSale(code, precomputeFlags);

Example usage in `generateStaticParams`:

```ts title="app/[code]/page.tsx#next"
```ts title="app/precomputed/[code]/page.tsx#next"
import { generatePermutations as generatePermutations } from 'flags/next';

export async function generateStaticParams() {
Expand Down
72 changes: 28 additions & 44 deletions apps/docs/content/docs/frameworks/next/precompute.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export const marketingFlags = [showSummerSale, showBanner] as const;

### 2. Precompute flags in middleware

Import and pass the group of flags to the `precompute` function in middleware. Then, forward the precomputation result (`code`) to the underlying page using an URL rewrite:
Import and pass the group of flags to the `precompute` function in middleware. Then, forward the precomputation result (`code`) to the underlying page using an URL rewrite. We recommend nesting precomputed pages under a dedicated `precomputed` folder so it's clear at a glance which routes participate in precomputation:

```tsx title="proxy.ts#next"
import { type NextRequest, NextResponse } from 'next/server';
Expand All @@ -131,7 +131,7 @@ export async function proxy(request: NextRequest) {

// rewrites the request to include the precomputed code for this flag combination
const nextUrl = new URL(
`/${code}${request.nextUrl.pathname}${request.nextUrl.search}`,
`/precomputed/${code}${request.nextUrl.pathname}${request.nextUrl.search}`,
request.url,
);

Expand All @@ -143,10 +143,12 @@ export async function proxy(request: NextRequest) {

Next, import the feature flags you created earlier, such as `showBanner`, while providing the code from the URL and the `marketingFlags` list of flags used in the precomputation.

Place the page under `app/precomputed/[code]/page.tsx`. Keeping precomputed pages in a `precomputed` folder makes it obvious which routes are served via the precompute pattern and avoids accidentally turning every top-level route into a dynamic segment.

When the `showBanner` flag is called within this component it reads the result from the precomputation, and it does not invoke the flag's `decide` function again:

```tsx title="app/[code]/page.tsx#next"
import { marketingFlags, showSummerSale, showBanner } from '../../flags';
```tsx title="app/precomputed/[code]/page.tsx#next"
import { marketingFlags, showSummerSale, showBanner } from '../../../flags';
type Params = Promise<{ code: string }>;

export default async function Page({ params }: { params: Params }) {
Expand Down Expand Up @@ -176,14 +178,14 @@ works with API Routes.

<IframeBrowser
src="snippets:/concepts/precompute/automatic"
codeSrc="https://github.com/vercel/flags/tree/main/examples/snippets/app/concepts/precompute/automatic/%5Bcode%5D"
codeSrc="https://github.com/vercel/flags/tree/main/examples/snippets/app/concepts/precompute/automatic/precomputed/%5Bcode%5D"
/>

## Enabling ISR (optional)

You can enable Incremental Static Regeneration (ISR) to cache generated pages after their initial render:

```tsx title="app/[code]/layout.tsx#next"
```tsx title="app/precomputed/[code]/layout.tsx#next"
import type { ReactNode } from 'react';

export async function generateStaticParams() {
Expand All @@ -202,7 +204,7 @@ In the example above, we used [`generateStaticParams`](https://nextjs.org/docs/a

The `flags/next` submodule exposes the [`generatePermutations`](/docs/api-reference/frameworks/next#generatepermutations) helper function for generating pages for different combinations of flags at build time. This function is called and takes a list of flags and returns an array of strings representing each combination of flags:

```tsx title="app/[code]/page.tsx#next"
```tsx title="app/precomputed/[code]/page.tsx#next"
import type { ReactNode } from 'react';
import { generatePermutations } from 'flags/next';

Expand All @@ -224,9 +226,9 @@ If you're using the Pages Router, you need to pass a flag to `generatePermutatio

You also need to specify a `getStaticPaths` function which can return the permutations to generate at build time or an empty array to use ISR.

```tsx title="pages/[code]/index.tsx#next"
```tsx title="pages/precomputed/[code]/index.tsx#next"
import { generatePermutations } from 'flags/next';
import { marketingFlags, exampleFlag } from '../flags';
import { marketingFlags, exampleFlag } from '../../flags';

export const getStaticPaths = (async () => {
const codes = await generatePermutations(marketingFlags);
Expand All @@ -247,7 +249,7 @@ export const getStaticProps = (async (context) => {

<IframeBrowser
src="snippets:/examples/pages-router-precomputed"
codeSrc="https://github.com/vercel/flags/blob/main/examples/snippets/pages/examples/pages-router-precomputed/%5Bcode%5D/index.tsx"
codeSrc="https://github.com/vercel/flags/blob/main/examples/snippets/pages/examples/pages-router-precomputed/precomputed/%5Bcode%5D/index.tsx"
/>

## Declaring available options (optional)
Expand Down Expand Up @@ -313,19 +315,19 @@ This section shows how to adapt the precompute pattern to different scenarios.

### Precomputing a single page only

The examples above use a single top-level group of flags, which will opt all pages nested under `app/[code]` into precomputation. Instead of opting the whole application into precomputation, you can also precompute a single page only.
The examples above use a single top-level `precomputed` segment, which opts every page nested under `app/precomputed/[code]` into precomputation. Instead of opting a whole subtree into precomputation, you can also precompute a single page only by scoping the `precomputed` segment to that route.

For example, if you want to precompute the `/pricing` page only:
- Move your pricing page from `app/pricing/page.tsx` to `app/pricing/[pricingCode]/page.tsx`
- Move your pricing page from `app/pricing/page.tsx` to `app/pricing/precomputed/[pricingCode]/page.tsx`
- Export a `pricingFlags` array of flags from your `flags.ts` file, containing all flags used by the pricing page
- Run Proxy for requests to `/pricing`, and pass `pricingFlags` to the `precompute` function
- Adjust the rewrite in Proxy to rewrite requests from `/pricing` to `/pricing/[pricingCode]`
- Use the `pricingFlags` array to access the precomputed result in `app/pricing/[pricingCode]/page.tsx` when using Flags
- Adjust the rewrite in Proxy to rewrite requests from `/pricing` to `/pricing/precomputed/[pricingCode]`
- Use the `pricingFlags` array to access the precomputed result in `app/pricing/precomputed/[pricingCode]/page.tsx` when using Flags

```tsx title="app/pricing/[pricingCode]/page.tsx#next"
```tsx title="app/pricing/precomputed/[pricingCode]/page.tsx#next"
import type { ReactNode } from 'react';
import { generatePermutations } from 'flags/next';
import { pricingFlags, discountFlag } from '../../../flags';
import { pricingFlags, discountFlag } from '../../../../flags';

export async function generateStaticParams() {
const codes = await generatePermutations(pricingFlags);
Expand All @@ -340,33 +342,13 @@ export default async function Page(props: { params: Promise<{ pricingCode: strin
}
```

### Precomputing a subset of pages

This section describes an alternative folder structure where only a part of the page tree makes use of precomputation.

So far the examples have used a single top-level group of flags under `app/[code]`.

Instead, you can nest precomputed flags under a folder like `app/precomputed/[code]`. This makes it clear that only those pages will have access to the precomputed flags.


```md
app
├─page.tsx
└─precomputed
└─[rootCode]
└─page.tsx
```

Adjust the rewrite in Proxy to include the `precomputed` segment.

Note that you will need to manually maintain the paths in Proxy for which the rewrite should run.

Keeping the `precomputed` folder convention — even when the dynamic segment is scoped to a single route — makes it obvious which routes are served via the precompute pattern.

### Multiple groups

Define multiple groups of flags to avoid unnecessarily generating permutations for flags which are not used by all pages. This control allows you to only generate permutations of the precise flags used by eacha subset of your pages.
Define multiple groups of flags to avoid unnecessarily generating permutations for flags which are not used by all pages. This control allows you to only generate permutations of the precise flags used by each subset of your pages.

For example, you can have a root group of flags which apply to all pages, and a nested group of flags which only apply to a single page or subset of pages. Create a root `[rootCode]` at the root of your application for the common flags, and a `[pricingCode]` for the flags used by the pricing page.
For example, you can have a root group of flags which apply to all pages, and a nested group of flags which only apply to a single page or subset of pages. Create a root `precomputed/[rootCode]` for the common flags, and a nested `precomputed/[pricingCode]` for the flags used by the pricing page.

```tsx title="flags.ts#next"
// all available flags
Expand All @@ -383,11 +365,13 @@ The file tree would look like this:

```md
app
└─[rootCode]
├─ page.tsx
└─ pricing
└─ [pricingCode]
└─ page.tsx
└─precomputed
└─[rootCode]
├─ page.tsx
└─ pricing
└─ precomputed
└─ [pricingCode]
└─ page.tsx
```

To use this pattern, you need to adjust Proxy:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AddToCart } from '@/app/[code]/add-to-cart';
import { AddToCart } from '@/app/precomputed/[code]/add-to-cart';
import { SummerSale } from '@/app/summer-sale';
import { ImageGallery } from '@/components/image-gallery';
import { Main } from '@/components/main';
Expand Down
5 changes: 3 additions & 2 deletions examples/shirt-shop/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ export async function proxy(request: NextRequest) {
const cartId = await getCartId();
const code = await precompute(productFlags);

// rewrites the request to the variant for this flag combination
// rewrites the request to the variant for this flag combination. The
// precomputed pages live under a `precomputed` folder by convention.
const nextUrl = new URL(
`/${code}${request.nextUrl.pathname}${request.nextUrl.search}`,
`/precomputed/${code}${request.nextUrl.pathname}${request.nextUrl.search}`,
request.url,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ export async function automaticPrecomputeProxy(request: NextRequest) {
// precompute the flags
const code = await precompute(marketingFlags);

// rewrite the page with the code
// rewrite the page with the code, nested under a `precomputed` folder so
// it's clear which routes are served via the precompute pattern
return NextResponse.rewrite(
new URL(`/concepts/precompute/automatic/${code}`, request.url),
new URL(`/concepts/precompute/automatic/precomputed/${code}`, request.url),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
marketingAbTest,
marketingFlags,
secondMarketingAbTest,
} from '../flags';
import { RegenerateIdButton } from '../regenerate-id-button';
} from '../../flags';
import { RegenerateIdButton } from '../../regenerate-id-button';

// Generate all permutations (all combinations of flag 1 and flag 2).
export async function generateStaticParams() {
Expand Down
5 changes: 3 additions & 2 deletions examples/snippets/app/examples/marketing-pages/proxy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ export async function marketingProxy(request: NextRequest) {
// precompute the flags
const code = await precompute(marketingFlags);

// rewrite the page with the code and set the cookie
// rewrite the page with the code and set the cookie. Precomputed pages
// are nested under a `precomputed` folder by convention.
return NextResponse.rewrite(
new URL(`/examples/marketing-pages/${code}`, request.url),
new URL(`/examples/marketing-pages/precomputed/${code}`, request.url),
{
headers: {
// Set the cookie on the response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { generatePermutations } from 'flags/next';
import { cookies, headers } from 'next/headers';
import Image from 'next/image';
import { Suspense } from 'react';
import { coreFlags, hasAuthCookieFlag } from '../flags';
import { coreFlags, hasAuthCookieFlag } from '../../flags';

// opt into on parital prerendering for this page, which is necessary while
// it's experimental, see https://nextjs.org/learn/dashboard-app/partial-prerendering
Expand Down
5 changes: 3 additions & 2 deletions examples/snippets/app/examples/suspense-fallbacks/proxy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ export async function pprShellsProxy(request: NextRequest) {
// precompute the flags
const code = await precompute(coreFlags);

// rewrite the page with the code
// rewrite the page with the code. Precomputed pages are nested under a
// `precomputed` folder by convention.
return NextResponse.rewrite(
new URL(`/examples/suspense-fallbacks/${code}`, request.url),
new URL(`/examples/suspense-fallbacks/precomputed/${code}`, request.url),
);
}
6 changes: 5 additions & 1 deletion examples/snippets/lib/pages-router-precomputed/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ export async function pagesRouterProxy(request: NextRequest) {
// precompute the flags
const code = await precompute(exampleFlags);

// Precomputed pages are nested under a `precomputed` folder by convention.
return NextResponse.rewrite(
new URL(`/examples/pages-router-precomputed/${code}`, request.url),
new URL(
`/examples/pages-router-precomputed/precomputed/${code}`,
request.url,
),
);
}
2 changes: 1 addition & 1 deletion examples/snippets/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { automaticPrecomputeProxy } from './app/concepts/precompute/automatic/[code]/proxy';
import { automaticPrecomputeProxy } from './app/concepts/precompute/automatic/precomputed/[code]/proxy';
import { manualPrecomputeProxy } from './app/concepts/precompute/manual/proxy';
import { featureFlagsInProxy } from './app/examples/feature-flags-in-proxy/proxy';
import { marketingProxy } from './app/examples/marketing-pages/proxy';
Expand Down
4 changes: 2 additions & 2 deletions skills/flags-sdk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,8 @@ Use precompute to keep pages static while using feature flags. Middleware evalua
High-level flow:
1. Declare flags and group them in an array
2. Call `precompute(flagGroup)` in middleware, get a `code` string
3. Rewrite request to `/${code}/original-path`
4. Page reads flag values from `code`: `await myFlag(code, flagGroup)`
3. Rewrite request to `/precomputed/${code}/original-path` — always nest precomputed routes under a `precomputed` folder so it's clear which pages participate in the pattern
4. Page (at `app/precomputed/[code]/page.tsx`) reads flag values from `code`: `await myFlag(code, flagGroup)`

For full implementation details, see framework-specific references:
- **Next.js**: See [references/nextjs.md](references/nextjs.md) — covers proxy middleware, precompute setup, ISR, generatePermutations, multiple groups
Expand Down
18 changes: 11 additions & 7 deletions skills/flags-sdk/references/nextjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ Not available in Pages Router.

Keep pages static while using feature flags. Proxy evaluates flags and encodes results into the URL.

Always nest precomputed pages under a `precomputed` folder (for example `app/precomputed/[code]/page.tsx`). This keeps it clear at a glance which routes participate in the precompute pattern and avoids turning every top-level route into a dynamic segment.

### Prerequisites

Set `FLAGS_SECRET` env var (32 random bytes, base64-encoded). Use a separate value for each environment (Development, Preview, Production), and mark the Preview and Production values as Sensitive. Run the generator once per environment to produce distinct values:
Expand Down Expand Up @@ -212,6 +214,8 @@ export const marketingFlags = [showSummerSale, showBanner] as const;

### Step 2: Precompute in proxy

Rewrite into the `precomputed/` folder so the page tree clearly marks which routes are served via precompute:

```ts
// proxy.ts
import { type NextRequest, NextResponse } from 'next/server';
Expand All @@ -223,7 +227,7 @@ export const config = { matcher: ['/'] };
export async function proxy(request: NextRequest) {
const code = await precompute(marketingFlags);
const nextUrl = new URL(
`/${code}${request.nextUrl.pathname}${request.nextUrl.search}`,
`/precomputed/${code}${request.nextUrl.pathname}${request.nextUrl.search}`,
request.url,
);
return NextResponse.rewrite(nextUrl, { request });
Expand All @@ -233,8 +237,8 @@ export async function proxy(request: NextRequest) {
### Step 3: Read precomputed values in page

```tsx
// app/[code]/page.tsx
import { marketingFlags, showSummerSale, showBanner } from '../../flags';
// app/precomputed/[code]/page.tsx
import { marketingFlags, showSummerSale, showBanner } from '../../../flags';

type Params = Promise<{ code: string }>;

Expand All @@ -255,7 +259,7 @@ export default async function Page({ params }: { params: Params }) {
### Step 4: Enable ISR & build time prerendering

```tsx
// app/[code]/layout.tsx
// app/precomputed/[code]/layout.tsx
import { generatePermutations } from 'flags/next';

export async function generateStaticParams() {
Expand Down Expand Up @@ -302,12 +306,12 @@ export const rootFlags = [navigationFlag, bannerFlag];
export const pricingFlags = [discountFlag];
```

File tree:
Each group still lives under its own `precomputed/` folder:

```
app/[rootCode]/
app/precomputed/[rootCode]/
page.tsx
pricing/[pricingCode]/
pricing/precomputed/[pricingCode]/
page.tsx
```

Expand Down
Loading