This guide shows you how to build geo-targeted landing pages in a Next.js 15 app using Vercel Edge Middleware and Edge Config. You will serve country-specific content without separate deployments, and the whole setup takes roughly four to six hours for a developer familiar with Next.js.

What You'll Build

  • A Next.js 15 app that detects visitor country at the edge before any React code runs
  • A rewrite-based routing layer that maps countries to localised landing page variants
  • A Vercel Edge Config store that lets you update geo rules without redeploying
  • A fallback strategy so visitors with unknown locations always see a valid page
  • A lightweight QA checklist for verifying geo behaviour across Australia, Singapore, Canada, and the US

Prerequisites

  • Node.js 20 or later and pnpm 9 or later installed locally
  • A Vercel account with a project connected to a GitHub repository
  • Familiarity with Next.js App Router and TypeScript
  • Basic knowledge of HTTP headers and request routing

Step 1: Scaffold the Next.js Project

Start with a clean Next.js 15 project or use an existing one. The App Router is required for this pattern.

pnpm create next-app@latest geo-landing --typescript --tailwind --app
cd geo-landing
pnpm install @vercel/edge-config

The @vercel/edge-config package lets your middleware read key-value rules stored in Vercel's globally replicated Edge Config store. Reads take under 1 ms in most regions, so they do not add meaningful latency to your edge function.

Why scaffold at the edge and not in a server component?

Server components run in a specific datacenter region. Middleware runs at the edge, meaning it executes in the datacenter closest to the visitor. For geo routing, this distinction matters. A visitor in Sydney should not wait for a server in Virginia to decide which page they see.

Step 2: Create Your Landing Page Variants

Create one folder per locale under app/. Keep the folder names consistent so your middleware rules stay readable.

mkdir -p app/au app/sg app/ca app/us app/global

Add a minimal page.tsx to each folder. Here is the Australian variant as an example.

// app/au/page.tsx
export default function AustraliaPage() {
  return (
    <main>
      <h1>Welcome, Australian visitors</h1>
      <p>Prices shown in AUD. Free shipping on orders over $80.</p>
    </main>
  );
}

Repeat this for sg, ca, us, and global. The global folder is your fallback for any country not explicitly mapped.

Common pitfall: Do not put locale-specific copy inside shared components. When marketing wants to update the Australian headline, they should touch one file, not hunt through shared logic.

Step 3: Set Up Vercel Edge Config

Edge Config is a Vercel feature (generally available as of 2025) that stores small JSON payloads at the edge. You read them inside middleware without a network round trip to your origin.

  1. Open your Vercel dashboard and go to Storage > Edge Config.
  2. Create a new store named geo-rules.
  3. Add the following JSON to the store.
{
  "countryRoutes": {
    "AU": "/au",
    "SG": "/sg",
    "CA": "/ca",
    "US": "/us"
  },
  "fallback": "/global"
}
  1. Copy the Edge Config connection string from the dashboard.
  2. Add it to your project's environment variables as EDGE_CONFIG.
# .env.local
EDGE_CONFIG=https://edge-config.vercel.com/ecfg_xxxx?token=yyyy

You also need to link the Edge Config store to your Vercel project under Project Settings > Edge Config.

When should you skip Edge Config and hardcode the routes instead?

If your geo rules never change between deployments, hardcoding them is simpler. Use Edge Config when marketing or ops teams need to add or remove country routes without a code release. For most growing SMBs, the flexibility is worth the two minutes of setup.

Step 4: Write the Edge Middleware

Create middleware.ts at the project root. This file runs on Vercel's edge network before any page renders.

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

export const config = {
  matcher: ['/', '/home'],
};

export async function middleware(request: NextRequest) {
  const country = request.geo?.country ?? 'UNKNOWN';

  const routes = await get<Record<string, string>>('countryRoutes');
  const fallback = await get<string>('fallback') ?? '/global';

  const destination = routes?.[country] ?? fallback;

  const url = request.nextUrl.clone();
  url.pathname = destination;

  return NextResponse.rewrite(url);
}

The request.geo object is populated by Vercel's infrastructure using the visitor's IP address. It contains country, city, and region fields. The country value follows the ISO 3166-1 alpha-2 standard, so AU for Australia, SG for Singapore, and so on.

Expected result: A visitor from Sydney navigating to / sees the content from app/au/page.tsx without the URL changing. The rewrite is invisible to the browser.

Common pitfall: Do not use NextResponse.redirect here. A redirect changes the URL, which means your analytics will show /au instead of /, and your canonical tags will break. Use NextResponse.rewrite to keep the URL clean.

Step 5: Handle SEO Correctly

Serving different content at the same URL path confuses search engines if you do not handle it explicitly. Add a Vary header so caches know the response differs by geography.

// Inside the middleware function, before returning
const response = NextResponse.rewrite(url);
response.headers.set('Vary', 'Accept-Language');
return response;

In each page variant, use Next.js metadata to set the correct canonical URL and hreflang tags.

// app/au/page.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
  alternates: {
    canonical: 'https://yourdomain.com/',
    languages: {
      'en-AU': 'https://yourdomain.com/',
      'en-SG': 'https://yourdomain.com/',
      'en-CA': 'https://yourdomain.com/',
      'en-US': 'https://yourdomain.com/',
    },
  },
};

Google's documentation on hreflang recommends pointing all variants to the same canonical when you use a single URL. This avoids splitting link equity across multiple paths.

What if Google indexes the wrong variant?

Googlebot's crawls originate from US-based IPs. Without additional configuration, it will always see your US variant. Add a rule to your middleware that checks for the Googlebot user agent and serves your default content instead of geo-routing it. This keeps your indexed content predictable.

const ua = request.headers.get('user-agent') ?? '';
if (ua.toLowerCase().includes('googlebot')) {
  return NextResponse.next();
}

Step 6: Test Geo Routing Locally and in Preview

Vercel does not populate request.geo in local development. You need to simulate it.

// middleware.ts — development fallback
const country =
  process.env.NODE_ENV === 'development'
    ? (request.headers.get('x-test-country') ?? 'US')
    : (request.geo?.country ?? 'UNKNOWN');

Now you can test specific countries using curl or a browser extension that sets custom headers.

curl -H "x-test-country: AU" http://localhost:3000/

For production testing, deploy a preview branch to Vercel. Vercel populates request.geo in preview deployments exactly as it does in production. Use a VPN set to each target country to verify the routing.

Teams at Lenka Studio use a simple four-country QA checklist when verifying geo routing on client projects: AU, SG, CA, and US, plus one country not in the rules (for example, Japan) to confirm the fallback fires correctly.

Step 7: Update Geo Rules Without Redeploying

One of the main advantages of Edge Config is that you can change routing rules in the Vercel dashboard and they propagate globally in under 10 seconds without a new deployment.

To add New Zealand (NZ) to your rules, open your Edge Config store and update the JSON.

{
  "countryRoutes": {
    "AU": "/au",
    "NZ": "/au",
    "SG": "/sg",
    "CA": "/ca",
    "US": "/us"
  },
  "fallback": "/global"
}

New Zealand visitors now see the Australian variant without any code change. This is particularly useful for regional campaigns where a marketing team needs to run a country-specific promotion on short notice.

Step 8: Track Geo Performance in Analytics

Set a custom dimension in GA4 to capture which geo variant a visitor saw. Pass it via a cookie set in the middleware.

response.cookies.set('geo_variant', destination, {
  maxAge: 60 * 60 * 24,
  path: '/',
});

Read this cookie in your root layout and push it to your analytics layer. This lets you compare conversion rates by geo variant in Looker Studio or GA4 explorations.

If you are running paid campaigns to specific countries, this data will show you whether your AU landing page converts at a different rate than your US page, which is where geo-targeting pays for itself. For businesses measuring brand performance across markets, a brand health score assessment can also surface which regions have the strongest resonance before you invest in localised content.

Frequently Asked Questions

Does this work on the free Vercel plan?

Edge Middleware and request.geo are available on all Vercel plans including the free Hobby tier. Edge Config has a free tier with up to 1 MB of storage and 1 million reads per month, which is enough for most SMBs running geo routing.

What if request.geo returns undefined in production?

This usually means the deployment is not running on Vercel's edge infrastructure. Check that your middleware file is at the project root (not inside src/) and that the matcher pattern matches the routes you expect. If you are self-hosting Next.js on a platform other than Vercel, request.geo will always be undefined and you will need a third-party geo IP library like geoip-lite instead.

How is this different from Next.js i18n routing?

Next.js built-in i18n routing handles locale-based URL structures like /en-AU/ or /en-US/. This guide uses geo detection to serve variants at a single URL path, which is better for campaigns where you want a clean URL. The two approaches solve different problems and can be combined if you need both localised URLs and geo-specific content.

Will this pattern slow down my page load time?

Edge Middleware adds roughly 1 to 5 ms of latency in practice, according to Vercel's own benchmarks. Edge Config reads add under 1 ms. The total overhead is negligible compared to server component rendering time, which typically runs between 50 and 300 ms depending on data fetching.

Can I use this with Cloudflare instead of Vercel?

Yes. Cloudflare Workers exposes request.cf.country, which provides the same ISO country code. You would replace the Edge Config reads with Cloudflare KV reads. The routing logic is identical. This guide is specific to Vercel, but the pattern transfers directly.

Next Steps

You now have a geo-targeted landing page system that runs at the edge, requires no redeployment to update, and handles SEO cleanly. From here, consider adding A/B testing within each geo variant using PostHog or Statsig to optimise conversion rates by country. You could also extend the middleware to route by city for campaigns targeting specific metro areas like Sydney, Toronto, or Singapore.

If you are building a multi-market product and want a second opinion on your architecture or localisation strategy, the team at Lenka Studio works with SMBs across Australia, Singapore, Canada, and the US on exactly this kind of problem. Get in touch and we can take a look at your setup together.