EmpireUI
Get Pro
← Blog8 min read#vercel#edge config#feature flags

Vercel Edge Config: Feature Flags, A/B Tests Without Redeploy

Ship feature flags and A/B tests that update instantly — no redeploy, no cold starts. Here's how to wire up Vercel Edge Config in Next.js the right way.

Abstract gradient background representing data flow and configuration layers

What Edge Config Actually Is (And Why You'd Care)

Edge Config is Vercel's globally-distributed key-value store that your edge functions and middleware can read in sub-millisecond time — without a network round-trip to your database or any external API. It was introduced properly in 2022, and by 2026 it's become the go-to primitive for runtime configuration on Vercel's platform.

The core idea: you write config values once through Vercel's dashboard or REST API, and your edge middleware reads them at the CDN layer — before a single line of your app code runs. That's what makes it different from a .env file or a database read. It's not just fast; it's *pre-render* fast.

Honestly, the most underrated use case isn't feature flags at all — it's emergency kill switches. Something goes wrong in production at 2am, you flip a value in Edge Config, and the broken feature is gone from every user in under a second. No git commit. No deploy pipeline. Nothing.

Worth noting: Edge Config isn't a general-purpose database. The storage limit per store is 512KB and individual values cap out at 64KB. You're not putting product data in here. You're putting configuration — booleans, percentage rollouts, variant assignments.

Setting Up Edge Config in Under 5 Minutes

First, install the SDK. You need @vercel/edge-config — nothing else for reads. Writes go through the REST API or the Vercel dashboard directly.

npm install @vercel/edge-config

Create an Edge Config store from your Vercel project settings under Storage → Edge Config → Create. Then grab the connection string — it looks like ecfg_xxxxxxxxxxxx — and drop it into your environment variables as EDGE_CONFIG.

# .env.local
EDGE_CONFIG=ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

That's literally it for setup. The SDK auto-picks up that env var. Now you can read values anywhere your middleware or edge runtime code runs.

import { get } from '@vercel/edge-config';

// This runs at the edge — sub-1ms read
const isNewDashboardEnabled = await get<boolean>('new_dashboard');

Quick aside: if you're running local dev with vercel dev, the connection string works there too. If you're using plain next dev, you'll need to mock it or set up a local fallback. Plan for that before you're blocked.

Feature Flags in Next.js Middleware

The right place to evaluate feature flags is middleware.ts — it runs at the edge before routing, which means you can redirect users to entirely different page variants without them ever seeing a flash or a loading state. This is the 2024/2025 pattern that's now table stakes for serious Next.js apps.

Here's a minimal feature flag setup. The key thing is the matcher — you want to be specific about which routes need flag evaluation so you're not paying the (tiny but real) latency cost on every static asset request.

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { get } from '@vercel/edge-config';

export const config = {
  matcher: ['/dashboard/:path*', '/checkout/:path*'],
};

export async function middleware(req: NextRequest) {
  const showNewDashboard = await get<boolean>('show_new_dashboard');

  if (showNewDashboard) {
    // Rewrite internally — URL stays the same for the user
    const url = req.nextUrl.clone();
    url.pathname = `/v2${req.nextUrl.pathname}`;
    return NextResponse.rewrite(url);
  }

  return NextResponse.next();
}

In practice, you'll want to type your config values so TypeScript yells at you when you misread a key. Create a small module that centralizes your Edge Config reads:

// lib/edge-config.ts
import { get } from '@vercel/edge-config';

type FeatureFlags = {
  show_new_dashboard: boolean;
  checkout_variant: 'control' | 'v2' | 'v3';
  maintenance_mode: boolean;
};

export async function getFlag<K extends keyof FeatureFlags>(
  key: K
): Promise<FeatureFlags[K] | null> {
  return get<FeatureFlags[K]>(key);
}

That pattern catches config key typos at compile time instead of at 1am in production. Small thing, big deal.

Running A/B Tests at the Edge

A/B testing at the edge is different from client-side testing tools like Optimizely or LaunchDarkly's browser SDK — there's no flicker, no layout shift, and the test variant is decided before the HTML even leaves Vercel's CDN. That's a meaningful conversion rate improvement if you're testing above-the-fold content.

The pattern uses a cookie to ensure consistent variant assignment across requests. You check for an existing assignment first, then bucket new visitors and set the cookie in the response.

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { get } from '@vercel/edge-config';

const VARIANT_COOKIE = 'ab_checkout_variant';

export async function middleware(req: NextRequest) {
  const testConfig = await get<{
    enabled: boolean;
    variants: Array<{ id: string; weight: number }>;
  }>('checkout_ab_test');

  if (!testConfig?.enabled) return NextResponse.next();

  // Respect existing assignment
  const existingVariant = req.cookies.get(VARIANT_COOKIE)?.value;
  const variant = existingVariant ?? assignVariant(testConfig.variants);

  const res = NextResponse.rewrite(
    new URL(`/checkout/${variant}`, req.url)
  );

  // Persist assignment — 30-day expiry
  if (!existingVariant) {
    res.cookies.set(VARIANT_COOKIE, variant, {
      maxAge: 60 * 60 * 24 * 30,
      httpOnly: true,
      sameSite: 'lax',
    });
  }

  return res;
}

function assignVariant(variants: Array<{ id: string; weight: number }>): string {
  const rand = Math.random();
  let cumulative = 0;
  for (const v of variants) {
    cumulative += v.weight;
    if (rand < cumulative) return v.id;
  }
  return variants[variants.length - 1].id;
}

Your Edge Config store would hold something like this, which you can update instantly from the dashboard without touching code:

{
  "checkout_ab_test": {
    "enabled": true,
    "variants": [
      { "id": "control", "weight": 0.5 },
      { "id": "v2", "weight": 0.3 },
      { "id": "v3", "weight": 0.2 }
    ]
  }
}

Look, you probably also want to log the variant assignment somewhere so you can actually analyze results. Send a custom header from middleware and pick it up in your analytics event on the client — that's the lightest approach without adding another network call to the critical path.

Percentage Rollouts and Gradual Releases

Feature flags and A/B tests are related but not the same thing. A/B tests compare variants. Percentage rollouts are about safely releasing new code to a growing slice of your user base — 5% today, 20% tomorrow, 100% next week if nothing catches fire.

The implementation is similar but the intent is different: you're not splitting for measurement, you're splitting for safety. Store a simple rollout percentage in Edge Config and evaluate it in middleware.

export async function middleware(req: NextRequest) {
  const rollout = await get<number>('new_pricing_page_rollout'); // 0.0 to 1.0

  if (rollout && Math.random() < rollout) {
    return NextResponse.rewrite(
      new URL('/pricing-v2', req.url)
    );
  }

  return NextResponse.next();
}

That's the naive version. In a real app you'd want deterministic bucketing based on user ID so the same user always gets the same experience. Hash the user ID or session cookie into a stable 0-1 value instead of using Math.random().

// Deterministic bucketing — same user always gets same result
async function getUserBucket(userId: string): Promise<number> {
  const encoder = new TextEncoder();
  const data = encoder.encode(userId + 'my-rollout-salt-2026');
  const hash = await crypto.subtle.digest('SHA-256', data);
  const view = new DataView(hash);
  return view.getUint32(0) / 0xffffffff; // 0.0 to 1.0
}

This pairs well with the nextjs-middleware-guide patterns if you're stacking multiple middleware concerns together. Just watch your total middleware execution budget — Vercel's limit is 1.5MB memory and 150ms CPU time per invocation.

Reading Edge Config From Server Components

Middleware isn't the only place you can read Edge Config — Server Components work too, and that's useful for less-critical flags that don't need to affect routing.

// app/settings/page.tsx
import { get } from '@vercel/edge-config';

export default async function SettingsPage() {
  const showBetaAnalytics = await get<boolean>('show_beta_analytics_tab');

  return (
    <div>
      {showBetaAnalytics && <BetaAnalyticsTab />}
    </div>
  );
}

The read is still fast — Edge Config is cached at the network level — but it's happening in Node.js runtime, not the edge runtime, so latency is slightly higher than middleware. For UI-level flags that don't affect routing, that's completely fine. For redirects and rewrites, stick to middleware.

One more thing — you can subscribe to Edge Config changes using webhooks, which lets you invalidate caches or trigger side effects when config updates. That's useful if you're building an internal admin panel for non-technical team members to flip flags.

The design of your flag UI matters here too. If you're building that kind of internal tool, the UI primitives from Empire UI — toggles, badges, data tables — save you from writing boilerplate. The glassmorphism components in particular work well for admin dashboards that are internal-only and don't need to match a strict brand.

Caching, Invalidation, and What to Watch Out For

Edge Config has built-in caching at Vercel's CDN layer. Reads are cached for about 1 second by default, which means there's a potential 1-second lag between you updating a value and every edge node reflecting it. For most use cases — kill switches, gradual rollouts — that's totally fine. For latency-critical toggles where a second matters, it's something to factor in.

The SDK also handles cache invalidation automatically when you use the @vercel/edge-config package directly. If you're hitting the REST API yourself, you'll need to handle your own caching logic. Don't do that unless you have a specific reason.

In practice, the most common footgun is reading Edge Config inside getStaticProps or at build time and being surprised that the value is baked into the HTML. Edge Config is meant for *runtime* reads. If you put it inside static generation code, you've lost the whole point — you'd need to redeploy to change the value.

Also: don't put secrets in Edge Config. Connection strings, API keys, private keys — those belong in environment variables. Edge Config values are scoped to your Vercel project but they're not designed with secret management in mind. Treat them like public configuration.

If you're thinking about the broader architecture of your Next.js app router and where configuration lives, Edge Config slots in cleanly between environment variables (static, per-deploy) and a real-time database (dynamic, latency-heavy). It's the middle tier that most teams were missing.

FAQ

How fast does Edge Config update globally after I change a value?

Typically under 1 second — Vercel propagates changes to all edge nodes almost instantly. The SDK caches reads for roughly 1 second, so worst case you're looking at a 1-2 second lag globally.

Can I use Edge Config without Vercel hosting?

No. Edge Config is a Vercel-specific product tied to their edge network. If you're self-hosting or on another platform, look at Cloudflare Workers KV or similar primitives instead.

Is Edge Config free?

Vercel's free Hobby plan includes one Edge Config store with 500 reads/day. Pro plans get more stores and 500k reads/month. Check Vercel's pricing page for current limits since they update them.

What's the difference between Edge Config and Vercel environment variables?

Environment variables are baked in at deploy time — changing them requires a redeploy. Edge Config is runtime-dynamic: you update the value and it's live in seconds without touching your deployment.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Vercel Edge Functions Guide: Runtime, Limits and Real-World UsesUploadThing Guide: File Uploads in Next.js Without the S3 Config PainNext.js OG Image Generation: @vercel/og, Edge Runtime, Custom FontsEdge Runtime in Next.js: Middleware, Edge API Routes and Limits