Vercel
Learn two ways to serve your Jamdesk documentation at /docs on your Vercel-deployed domain: vercel.json rewrites or Edge Middleware.
Serve your Jamdesk documentation at /docs on your Vercel-deployed domain. Pick one of two setups. They produce the same result.
- Option A: vercel.json rewrites: no code, any framework. Start here.
- Option B: Edge Middleware: for sites that already run Edge Middleware and want Jamdesk in the same file.
Prerequisites
- A project deployed on Vercel
- Your Jamdesk subdomain (found in dashboard settings), with Host at a subpath enabled
- Your custom domain registered and verified in the dashboard
Option A: vercel.json Rewrites
Using a custom subpath instead of the default /docs? Replace every /docs in the snippets below with your subpath, in the source and destination of each rule. Redeploy after you rename the subpath in the dashboard. Nothing regenerates this file for you.
A rewrite can change where a request goes. It can't add a request header. Each destination carries a public marker, ?jd_proxy=1, instead. The marker tells Jamdesk the request came through your proxy. Jamdesk strips it before rendering. Your visitors never see it.
Create or edit vercel.json in your project root, replacing YOUR_SLUG with your Jamdesk subdomain:
{
"rewrites": [
{ "source": "/docs", "destination": "https://YOUR_SLUG.jamdesk.app/docs?jd_proxy=1" },
{ "source": "/docs/:path*", "destination": "https://YOUR_SLUG.jamdesk.app/docs/:path*?jd_proxy=1" },
{ "source": "/_next/:path*", "destination": "https://YOUR_SLUG.jamdesk.app/_next/:path*" },
{ "source": "/_jd/:path*", "destination": "https://YOUR_SLUG.jamdesk.app/_jd/:path*?jd_proxy=1" }
]
}/_next/:path* needs no marker. It is a static-asset path Jamdesk never gates. A site that isn't a Next.js app has no /_next/ files of its own. Every request under that path falls through to Jamdesk.
The same four rules work in next.config.js for Next.js projects:
/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return [
{ source: "/docs", destination: "https://YOUR_SLUG.jamdesk.app/docs?jd_proxy=1" },
{ source: "/docs/:path*", destination: "https://YOUR_SLUG.jamdesk.app/docs/:path*?jd_proxy=1" },
{ source: "/_next/:path*", destination: "https://YOUR_SLUG.jamdesk.app/_next/:path*" },
{ source: "/_jd/:path*", destination: "https://YOUR_SLUG.jamdesk.app/_jd/:path*?jd_proxy=1" },
];
},
};
export default nextConfig;Forward your root files too
robots.txt, sitemap.xml, llms.txt, and llms-full.txt live at the domain root, outside /docs. Search engines and AI agents need them to discover your docs. If Jamdesk owns your domain root, add four more rules:
{
"rewrites": [
{ "source": "/robots.txt", "destination": "https://YOUR_SLUG.jamdesk.app/robots.txt?jd_proxy=1" },
{ "source": "/sitemap.xml", "destination": "https://YOUR_SLUG.jamdesk.app/sitemap.xml?jd_proxy=1" },
{ "source": "/llms.txt", "destination": "https://YOUR_SLUG.jamdesk.app/llms.txt?jd_proxy=1" },
{ "source": "/llms-full.txt", "destination": "https://YOUR_SLUG.jamdesk.app/llms-full.txt?jd_proxy=1" }
]
}If your marketing site already serves its own robots.txt or sitemap.xml at the root, merge the two instead of overwriting one with the other.
Deploy and verify
Deploy with vercel --prod (or push to your connected Git repository). Open https://yoursite.com/docs and view the page source. Check two things:
<link rel="canonical">points at your domain, notYOUR_SLUG.jamdesk.app- There is no
<meta name="robots" content="noindex">
If either is wrong, a rewrite is missing its ?jd_proxy=1 marker. See Why the Marker Matters.
Option B: Edge Middleware
Set the X-Jamdesk-Forwarded-Host request header in your Edge Middleware instead of using the marker. The two signals are equivalent. Pick this option to keep routing logic in code you already maintain.
Create a middleware.ts file in your project root. Replace YOUR_SLUG with your Jamdesk subdomain:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const JAMDESK_HOST = 'YOUR_SLUG.jamdesk.app';
export function middleware(request: NextRequest) {
const url = request.nextUrl;
const destination = new URL(url.pathname + url.search, `https://${JAMDESK_HOST}`);
// Clone headers and tell Jamdesk which domain the visitor actually used.
const headers = new Headers(request.headers);
headers.set('X-Jamdesk-Forwarded-Host', url.hostname);
return NextResponse.rewrite(destination, {
request: { headers },
});
}
export const config = {
matcher: ['/docs', '/docs/:path*', '/_jd/:path*'],
};On a custom subpath, replace /docs and /docs/:path* in the matcher above with your configured subpath.
Do not add /_next/:path* to the matcher. Middleware runs before Vercel's filesystem check. Matching /_next/ sends your own site's JavaScript and CSS to Jamdesk and breaks your styles. The next step routes /_next/ the safe way.
Name the file middleware.ts and export a function named middleware. Next.js 16 suggests renaming it to proxy.ts. Vercel production doesn't invoke proxy.ts yet. Renaming silently disables it.
Route assets and root files in vercel.json
Jamdesk's pages load their bundles from /_next/. Root files like robots.txt sit outside the matcher above. Route both with rewrites. Rewrites run after the filesystem check. Your own build output wins, and only requests your site can't serve fall through to Jamdesk:
{
"rewrites": [
{ "source": "/_next/:path*", "destination": "https://YOUR_SLUG.jamdesk.app/_next/:path*" },
{ "source": "/robots.txt", "destination": "https://YOUR_SLUG.jamdesk.app/robots.txt?jd_proxy=1" },
{ "source": "/sitemap.xml", "destination": "https://YOUR_SLUG.jamdesk.app/sitemap.xml?jd_proxy=1" },
{ "source": "/llms.txt", "destination": "https://YOUR_SLUG.jamdesk.app/llms.txt?jd_proxy=1" },
{ "source": "/llms-full.txt", "destination": "https://YOUR_SLUG.jamdesk.app/llms-full.txt?jd_proxy=1" }
]
}The root files carry ?jd_proxy=1. The middleware doesn't run on them, and a rewrite can't set the header. Add them only if Jamdesk owns your domain root. If your site serves its own robots.txt or sitemap.xml, merge instead.
Deploy and verify
Deploy with vercel --prod (or push to your connected Git repository). Open https://yoursite.com/docs and view the page source. Check two things:
<link rel="canonical">points at your domain, notYOUR_SLUG.jamdesk.app- There is no
<meta name="robots" content="noindex">
If either is wrong, the X-Jamdesk-Forwarded-Host header isn't reaching Jamdesk. Confirm /docs is in the matcher.
Why the Marker Matters
Jamdesk needs a signal that a request arrived through your proxy, not as a direct hit on your *.jamdesk.app subdomain. The ?jd_proxy=1 marker and the X-Jamdesk-Forwarded-Host header carry that signal. Jamdesk treats them as equivalent. Either one does three things:
- Confirms your registered domain
- Points canonical, Open Graph, and sitemap links at it
- Lets Custom domain only tell proxy traffic apart from direct hits
A missing signal fails quietly. Pages still render. With no custom domain registered, they carry noindex and subdomain canonicals. With a custom domain registered, the Custom-domain-only check fails. A 403 means the signal is present but names a domain that isn't registered and active for this project.
Troubleshooting
Your matcher likely includes /_next/:path*. Middleware runs before the filesystem check. It routes your own application's assets to Jamdesk. Remove /_next/ from the matcher and route it through vercel.json instead. See Option B.
View the page source at https://yoursite.com/docs. A <meta name="robots" content="noindex"> tag means neither the marker nor the header is reaching Jamdesk. On Option A, confirm every rewrite destination ends in ?jd_proxy=1 (except /_next/:path*). On Option B, confirm /docs is in the middleware matcher. A vercel.json rewrite alone cannot set the header.
The marker or header reaches Jamdesk but names a domain Jamdesk won't serve for this project:
- Verify your domain is registered in the Jamdesk dashboard
- Complete DNS verification (TXT record)
- Check the domain maps to the correct project and is marked active
- On Option B, confirm the middleware sets the header to your visitor-facing domain, not a preview URL
Verify your rewrites (or matcher) cover /docs and /docs/:path*. Without the wildcard, nested pages fail.
Route /_jd/:path* and /_next/:path*. /_jd/ serves Jamdesk fonts, images, and branding. /_next/ serves the documentation bundles. Missing either breaks the layout.
Check the file is named middleware.ts (not proxy.ts) at your project root. It must export a function named middleware. Next.js 16's deprecation warning recommends proxy.ts. Vercel production does not invoke it.
Check that the destination URL uses https:// and points to jamdesk.app, not back at your own domain.
The check fetches /_jd/preflight on your live domain and inspects what reached Jamdesk. "Doesn't identify itself" means a rewrite reached Jamdesk without the marker or header. Recheck every rewrite destination (Option A) or your middleware headers (Option B). See Custom domain only.
