ccnames.dev

cnames.dev / blog

Tutorial12 min read

How to add custom domains to your Next.js app

Route customer domains in Next.js with middleware and the Host header, forward the real client IP, and issue SSL — the DIY way and the one-API-call way.

S
Saeed
July 6, 2026

Adding custom domains to a Next.js app comes down to two jobs: routing a customer's domain to the right tenant, and getting a valid SSL certificate for it. Next.js handles the routing cleanly with middleware; the certificate has to come from whatever sits in front of your app. Here is the complete DIY path, then the one-API-call version.

// middleware.ts — route by the incoming Host header
import { NextRequest, NextResponse } from 'next/server';

export function middleware(req: NextRequest) {
  const host = req.headers.get('host') ?? '';

  // Your own domains pass straight through.
  if (host === 'yourapp.com' || host.endsWith('.yourapp.com')) {
    return NextResponse.next();
  }

  // Everything else is a customer domain -> rewrite to the tenant route.
  const url = req.nextUrl.clone();
  url.pathname = `/_sites/${host}${url.pathname}`;
  return NextResponse.rewrite(url);
}

export const config = { matcher: ['/((?!_next|api|.*\\..*).*)'] };

Step 1 — Point DNS at your app

The customer creates a DNS record aimed at infrastructure you control. For a subdomain, a CNAME is best because it lets you change IPs later without touching their DNS:

app.customer.com.  CNAME  proxy.yourapp.com.

For a bare domain you hit the CNAME-at-apex problem and need an A record or ALIAS instead. Verify it resolves with the free CNAME checker before you promise the customer it is live.

Step 2 — Route by Host in middleware

The snippet above reads host and rewrites unknown hosts into a /_sites/[host] segment. In that route you resolve the host to a tenant and render their content:

// app/_sites/[host]/page.tsx
export default async function TenantHome({ params }: { params: Promise<{ host: string }> }) {
  const { host } = await params;
  const tenant = await getTenantByDomain(host); // your lookup
  if (!tenant) return <NotFound />;             // fail closed — never a default tenant
  return <SiteHome tenant={tenant} />;
}

Fail closed. An unknown host must 404, never fall through to a default tenant — that is how you avoid serving one customer's domain another customer's data.

Step 3 — The real client IP

Once a proxy sits in front of Next.js, request.ip and the socket address are the proxy's, not the visitor's. Read the forwarded header instead, and only trust it from your own edge:

const clientIp =
  req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ??
  req.headers.get('x-real-ip') ??
  'unknown';

Step 4 — SSL is not Next.js's job

Next.js never terminates TLS itself. Your options:

The one-API-call way

With cnames.dev in front of your app, you register the domain and the SSL, edge routing, and real-client-IP forwarding are handled. Your Next.js middleware stays exactly as above — it just receives clean, TLS-terminated requests with the right headers.

curl -X POST https://api.cnames.dev/v1/domains \
  -H "Authorization: Bearer sk_live_..." \
  -d '{"hostname":"app.customer.com","origin":"your-nextjs-app.internal"}'

This is a common pattern for AI app builders and site generators publishing user apps. The full architecture — issuance, SNI, tenant routing — is in the complete guide.

Building a SaaS that needs custom domains? cnames.dev gives every customer their own domain — SSL, edge routing, and per-tenant isolation in one API call. Free for 25 domains, no demo call. Start free · Read the docs

Frequently asked questions

Can Next.js issue SSL certificates for custom domains itself?

No. Next.js runs behind a server or platform that terminates TLS. The certificate must come from your host (Vercel, a reverse proxy like Caddy/Nginx with ACME) or a custom-domains service. Next.js only sees the already-decrypted request and the Host header.

How do I read the custom domain in Next.js?

In middleware or a server component, read the host from the request headers (request.headers.get("host") in middleware, or the headers() helper in the app router). Use it to look up the tenant and rewrite accordingly.

Should I use rewrites or redirects for multi-tenant routing?

Rewrites. A rewrite keeps the customer domain in the address bar while internally serving the tenant route. A redirect would change the URL and defeat the point of a custom domain.

Keep reading

S

SaeedFounder, cnames.dev — runs custom-domain infra for 5,000+ production sites at Lindo.ai.