Configuring Custom Subpaths

How to support custom subpaths for customers using a custom domain

Custom subpaths let customers host your platform content on any path of their exisitng domain, like company.com/docs or startup.com/help, while you maintain a single Next.js application and they host the rest of their site separetely.

Single app, multiple subpaths

Use a catch-all route to handle all customer requests in one application:

// app/sites/[...slug]/page.tsx
export default async function CustomerSite({
  params,
}: {
  params: { slug: string[] };
}) {
  const [customerSlug, ...contentPath] = params.slug;

  // Load customer config
  const customer = await getCustomer(customerSlug);
  if (!customer) return notFound();

  // Load customer-specific content
  const content = await getContent(customer.id, contentPath.join("/"));

  return (
    <div>
      <h1>{customer.name}</h1>
      <div>{content}</div>
    </div>
  );
}

This handles requests like:

  • yourapp.com/sites/acme/getting-started
  • yourapp.com/sites/startup/api-reference

Redirect subdomains to paths

Redirect customer subdomains to path-based routes:

// middleware.ts
import { NextRequest, NextResponse } from "next/server";

export async function middleware(request: NextRequest) {
  const hostname = request.headers.get("host") || "";
  const { pathname } = request.nextUrl;

  // Check if it's a customer subdomain
  const subdomain = hostname.split(".")[0];

  if (
    subdomain !== "www" &&
    subdomain !== "app" &&
    hostname.includes("yourapp.com")
  ) {
    // Redirect vercel.yourapp.com/guide -> yourapp.com/sites/vercel/guide
    const redirectUrl = new URL(`/sites/${subdomain}${pathname}`, request.url);
    redirectUrl.host = "yourapp.com";

    return NextResponse.redirect(redirectUrl);
  }

  return NextResponse.next();
}

Set unique asset prefix

Configure Next.js to use a unique asset prefix to avoid conflicts:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  assetPrefix: "/your-platform-assets",

  async rewrites() {
    return [
      {
        source: "/your-platform-assets/_next/:path*",
        destination: "/_next/:path*",
      },
    ];
  },
};

module.exports = nextConfig;

This ensures your CSS, JS, and images load from /your-platform-assets/_next/... instead of /_next/....

Customer domain setup

Customers map two paths on their domain to your platform:

Content mapping:

/docs/:path* -> https://yourapp.com/sites/customer-slug/:path*

Asset mapping:

/your-platform-assets/:path* -> https://yourapp.com/your-platform-assets/:path*

Example with Vercel routing middleware:

// Customer's middleware.ts
import { NextRequest, NextResponse } from "next/server";

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // Route content requests
  if (pathname.startsWith("/docs/")) {
    const targetPath = pathname.replace("/docs/", "/sites/customer-slug/");
    const targetUrl = `https://yourapp.com${targetPath}`;

    return NextResponse.rewrite(new URL(targetUrl));
  }

  // Route asset requests
  if (pathname.startsWith("/your-platform-assets/")) {
    const targetUrl = `https://yourapp.com${pathname}`;
    return NextResponse.rewrite(new URL(targetUrl));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/docs/:path*", "/your-platform-assets/:path*"],
};

Handle customer configuration

Store customer settings and customize the experience:

// app/sites/[...slug]/layout.tsx
export default async function CustomerLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { slug: string[] };
}) {
  const customerSlug = params.slug[0];
  const customer = await getCustomer(customerSlug);

  return (
    <html>
      <head>
        <title>{customer.siteTitle}</title>
        <style
          dangerouslySetInnerHTML={{
            __html: `
            :root {
              --primary-color: ${customer.primaryColor};
              --font-family: ${customer.fontFamily};
            }
          `,
          }}
        />
      </head>
      <body>
        <nav style={{ backgroundColor: customer.primaryColor }}>
          <img src={customer.logo} alt={customer.name} />
        </nav>
        {children}
      </body>
    </html>
  );
}