> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.makeswift.com/developer/docs/guides/how-to/multi-tenancy/path-based/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.makeswift.com/_mcp/server. # Path-based routing > Route tenants using URL path prefixes in a multi-tenant Makeswift application. Route tenants using URL path prefixes in a multi-tenant Makeswift application. Path-based routing uses the first segment of the URL path to identify the tenant for public visitors. For example, `example.com/siteA/products` serves the products page for Site A, while the bare root domain (`example.com`) serves the default tenant. This is a hybrid approach: public visitors are identified by the URL path, while the Visual Builder connects via subdomain (because a Makeswift host URL is an origin only and cannot contain a path). Both styles render the same tenant content. Before following this guide, complete the [common configuration](/developer/docs/guides/how-to/multi-tenancy) steps. ## How it works The middleware resolves the tenant in three steps: 1. **Subdomain first.** If the host header has a valid tenant subdomain (for example, `siteA.localhost:3000`), rewrite to `/siteA/...`. This is how the Visual Builder connects. 2. **Path fallback.** If the first path segment is already a valid tenant (for example, `/siteA/products`), pass it through unchanged. 3. **Default.** Otherwise — the bare root domain or any unrecognized host — prepend the `default` tenant. ``` Public (path): localhost:3000/siteA/products └─> Middleware: no tenant subdomain; first path segment "siteA" is a valid tenant → passed through unchanged → /siteA/products Public (subdomain) / Builder: siteA.localhost:3000/products └─> Middleware: subdomain "siteA" is a valid tenant → rewritten → /siteA/products Both → app/[[...path]]/page.tsx with params.path = ["siteA", "products"] │ ├─> Extracts tenantId = "siteA" from the first path segment ├─> Calls getApiKey("siteA") → Site A's API key ├─> Creates a Makeswift client with Site A's API key ├─> Fetches the page snapshot for "/products" from Site A └─> Renders the page with tenant-specific content ``` ## Add the middleware Create a `middleware.ts` file at the root of your project: **`middleware.ts`** ```ts title="middleware.ts" import { NextRequest, NextResponse } from "next/server"; import { DEFAULT_TENANT_ID, getSubdomainFromHost, isValidTenantId, } from "./lib/makeswift/tenants"; export function middleware(request: NextRequest) { const host = request.headers.get("host") ?? ""; const url = request.nextUrl.clone(); // 1. Prefer the subdomain — this is how the Visual Builder connects. const subdomain = getSubdomainFromHost(host); if (subdomain != null && isValidTenantId(subdomain)) { if ( url.pathname !== `/${subdomain}` && !url.pathname.startsWith(`/${subdomain}/`) ) { url.pathname = `/${subdomain}${url.pathname}`; } return NextResponse.rewrite(url); } // 2. Fall back to path-based routing for public viewing. const firstPathSegment = url.pathname.split("/").at(1) ?? null; if (firstPathSegment != null && isValidTenantId(firstPathSegment)) { return NextResponse.next(); } // 3. No tenant in the host or the path — use the default tenant. url.pathname = `/${DEFAULT_TENANT_ID}${url.pathname}`; return NextResponse.rewrite(url); } export const config = { matcher: [ "/((?!api|_next/static|_next/image|favicon.ico).*)", ], }; ``` | Request | Rewritten to | | ------------------------------------------- | ------------------- | | `siteA.localhost:3000/products` (subdomain) | `/siteA/products` | | `localhost:3000/siteA/products` (path) | `/siteA/products` | | `localhost:3000/products` | `/default/products` | | `www.localhost:3000/products` | `/default/products` | Makeswift API routes (`/api/makeswift/*`) are excluded via `config.matcher` — the API handler resolves the tenant from the host itself. ## Configure the Visual Builder host URLs Path-based URLs don't carry tenant information in the host header, so the Visual Builder cannot connect using a path URL alone. Each Makeswift site needs its host URL set to the subdomain URL. In the Makeswift dashboard, open the settings for each site and set the host URL: | Site | Host URL | | ------- | ----------------------------- | | Default | `http://localhost:3000` | | Site A | `http://siteA.localhost:3000` | | Site B | `http://siteB.localhost:3000` | For production, replace `localhost:3000` with your deployed domain (for example, `siteA.example.com`). ## Test the setup Start the development server and verify each tenant loads: ```bash npm run dev ``` Both path and subdomain URLs work for public viewing: | URL | Tenant | | ----------------------------- | ------------------ | | `http://localhost:3000` | Default | | `http://localhost:3000/siteA` | Site A (path) | | `http://localhost:3000/siteB` | Site B (path) | | `http://siteA.localhost:3000` | Site A (subdomain) | | `http://siteB.localhost:3000` | Site B (subdomain) | > **Note** > > Path URLs work everywhere. For the subdomain URLs (and the Visual Builder), most browsers (Chrome, Firefox, Edge) resolve `*.localhost` to `127.0.0.1` automatically. Safari does not — see [Resolving subdomains on Safari](#resolving-subdomains-on-safari) below. ## Production deployment The same logic works in production with two changes: * **Set `ROOT_DOMAIN` to your real domain** (for example, `ROOT_DOMAIN=example.com`). Tenant subdomains are resolved relative to this value, and the bare apex domain maps to the default tenant. * **Point each tenant subdomain at your deployment.** Path-based public URLs (`example.com/siteA`) work without any DNS changes, but the subdomain URLs and the Visual Builder require each tenant subdomain to resolve. Configure wildcard DNS (`*.example.com`) or an individual DNS record per tenant. ## Resolving subdomains on Safari Chrome, Firefox, and Edge resolve `*.localhost` to `127.0.0.1` automatically, but Safari does not. To make the tenant subdomains work in Safari during local development, map them in `/etc/hosts`: 1. Edit `/etc/hosts`: ```bash sudo vim /etc/hosts ``` 2. Add an entry for each subdomain: **`/etc/hosts`** ```text title="/etc/hosts" 127.0.0.1 siteA.localhost 127.0.0.1 siteB.localhost ``` 3. Visit `http://siteA.localhost:3000`, `http://siteB.localhost:3000`, and so on. ## Example repository For a complete working implementation, see the [multi-tenant path example](https://github.com/makeswift/multi-tenant-example-path/) repository. > Documentation for Makeswift — the composable visual page builder for marketing teams and developers