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:
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-startedyourapp.com/sites/startup/api-reference
Redirect subdomains to paths
Redirect customer subdomains to path-based routes:
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")
) {
// Rewrite vercel.yourapp.com/guide -> yourapp.com/sites/vercel/guide
const rewriteUrl = new URL(`/sites/${subdomain}${pathname}`, request.url);
rewriteUrl.host = "yourapp.com";
return NextResponse.rewrite(rewriteUrl);
}
return NextResponse.next();
}Set unique asset prefix
Configure Next.js to use a unique asset prefix to avoid conflicts:
/** @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:
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:
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>
);
}