@koryla/react
v0.1.4
Published
React Server Component SDK for Koryla A/B testing. Zero client-side JavaScript — variants are resolved on the server before the page renders.
Downloads
43
Readme
@koryla/react
React Server Component SDK for Koryla A/B testing. Zero client-side JavaScript — variants are resolved on the server before the page renders.
Installation
npm install @koryla/reactSetup
1. Add environment variables
# .env.local
KORYLA_API_KEY=sk_live_...
KORYLA_API_URL=https://koryla.com2. Create a Koryla client
Instantiate once — it holds the 60s config cache:
// lib/koryla.ts
import { createKoryla } from '@koryla/react'
export const koryla = createKoryla({
apiKey: process.env.KORYLA_API_KEY!,
apiUrl: process.env.KORYLA_API_URL!,
})3. Use in your page or component
// app/page.tsx (Next.js App Router)
import { headers } from 'next/headers'
import { koryla } from '@/lib/koryla'
import { Experiment, Variant } from '@koryla/react'
export default async function Page() {
const result = await koryla.getVariant(
'your-experiment-id', // from Koryla dashboard
headers().get('cookie') ?? '', // sticky sessions via cookie
)
return (
<main>
<Experiment variantId={result?.variantId ?? ''}>
<Variant id="control">
<h1>Original headline</h1>
</Variant>
<Variant id="variant-b">
<h1>New headline that converts better</h1>
</Variant>
</Experiment>
</main>
)
}4. Persist the variant cookie
On new assignments the visitor's variant isn't sticky yet. Set the cookie in a Next.js middleware:
// middleware.ts
import { koryla } from '@/lib/koryla'
import { NextResponse } from 'next/server'
export async function middleware(request) {
const result = await koryla.getVariant(
'your-experiment-id',
request.headers.get('cookie') ?? '',
)
const response = NextResponse.next()
if (result?.isNewAssignment) {
response.cookies.set(result.cookieName, result.variantId, {
maxAge: 60 * 60 * 24 * 30,
sameSite: 'lax',
path: '/',
})
}
return response
}How it works
User visits /
│
▼
Next.js renders Page server component
│
├── koryla.getVariant() reads config (cached 60s)
├── reads koryla_sid cookie
│ ├── cookie present → use existing variant (sticky)
│ └── no cookie → assign variant by traffic weight
│
└── <Experiment> renders only the matching <Variant>
│
▼
Browser receives HTML with the correct variant already rendered
No JS, no swap, no flickerAPI
createKoryla(options)
| Option | Type | Description |
|--------|------|-------------|
| apiKey | string | Your sk_live_... key from Settings → API Keys |
| apiUrl | string | https://koryla.com |
| cacheTtl | number | Config cache TTL in ms. Default: 60000 |
Returns { getVariant }.
koryla.getVariant(experimentId, cookieHeader)
Returns Promise<VariantResult | null>. null means the experiment wasn't found or is inactive.
interface VariantResult {
experiment: Experiment
variant: Variant // the assigned variant object
variantId: string // e.g. "abc-123"
isNewAssignment: boolean
cookieName: string // e.g. "ky_exp-id"
}<Experiment variantId={...}>
Renders only the <Variant> child whose id matches variantId.
Falls back to the first child if no match.
<Variant id="...">
Marker component. Wrap content inside <Experiment>.
Why this is better than VWO / Optimizely
| | VWO / Optimizely | @koryla/react | |--|--|--| | When variant is decided | In the browser, after JS loads | On the server, before any HTML | | Flicker | Yes (page hides while swapping) | No | | Extra JS on page | ~80–150 KB | 0 KB | | Blockable by ad blockers | Yes | No | | Works without JS | No | Yes |
