@shoal/next wraps the core SDK with helpers for the App Router. Flags are evaluated on the server, so pages render the right variation on the first byte — no flicker.
Terminal
npm install @shoal/nextTerminal
pnpm add @shoal/nextTerminal
yarn add @shoal/nextTerminal
bun add @shoal/nextCreate a client
Create one client per process and reuse it. The helper caches the ruleset between requests.
lib/shoal.ts
import { createNextClient } from "@shoal/next";
export const shoal = createNextClient({
key: process.env.SHOAL_KEY!,
context: async ({ cookies }) => ({
user: { id: cookies.get("uid")?.value ?? "anonymous" },
}),
});Server components
app/checkout/page.tsx
import { shoal } from "@/lib/shoal";
export default async function CheckoutPage() {
const variant = await shoal.variation("checkout-theme", "classic");
return variant === "compact" ? <CompactCheckout /> : <ClassicCheckout />;
}Middleware
Rewrite to a different page at the edge — useful for full-page experiments.
middleware.ts
import { NextResponse, type NextRequest } from "next/server";
import { shoal } from "@/lib/shoal";
export async function middleware(req: NextRequest) {
if (await shoal.isOn("new-pricing", { request: req })) {
return NextResponse.rewrite(new URL("/pricing-v2", req.url));
}
}
export const config = { matcher: "/pricing" };Client components
Pass server-evaluated values down instead of evaluating in the browser:
app/layout.tsx
const flags = await shoal.bootstrap(["summer-banner", "chat-widget"]);
return <FlagsProvider value={flags}>{children}</FlagsProvider>;components/banner.tsx
"use client";
import { useFlag } from "@shoal/react";
export function Banner() {
return useFlag("summer-banner") ? <SummerBanner /> : null;
}