Jamdesk Documentation logo

Vercel

Learn two ways to serve your Jamdesk documentation at /docs on your Vercel-deployed domain: vercel.json rewrites or Edge Middleware.

If your site is deployed on Vercel, you can serve your Jamdesk documentation at /docs on your own domain. There are two ways to set this up, and both produce the same result:

Prerequisites

  • A project deployed on Vercel
  • Your Jamdesk subdomain (found in dashboard settings), with Host at /docs enabled
  • Your custom domain registered and verified in the dashboard

Option A: vercel.json Rewrites

A rewrite can change where a request goes, but it can't add a request header — so each destination instead carries a public marker, ?jd_proxy=1, that 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:

vercel.json
{
  "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's a static-asset path Jamdesk never gates. If your site isn't a Next.js app it has no /_next/ files of its own, so the rewrite is still correct: every request under that path simply falls through to Jamdesk.

The same four rules work in next.config.js for Next.js projects:

next.config.js
/** @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 — without them, search engines and AI agents can't discover your docs. If Jamdesk owns your domain root, add four more rules:

vercel.json
{
  "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), then open https://yoursite.com/docs and view the page source. Check two things:

  1. <link rel="canonical"> points at your domain, not YOUR_SLUG.jamdesk.app
  2. 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

If you already run Edge Middleware, set the X-Jamdesk-Forwarded-Host request header there instead of using the marker. The two signals are equivalent — pick this option only if you'd rather keep routing logic in code you already maintain.

Create a middleware.ts file in your project root, replacing YOUR_SLUG with your Jamdesk subdomain:

middleware.ts
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*'],
};

Do not add /_next/:path* to the matcher. Middleware runs before Vercel's filesystem check, so matching /_next/ sends your own site's JavaScript and CSS to Jamdesk and breaks your styles. The next step routes /_next/ the safe way.

The file must be named middleware.ts and export a function named middleware. Next.js 16 suggests renaming it to proxy.ts, but 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/, and root files like robots.txt sit outside the matcher above. Route both with rewrites — rewrites run after the filesystem check, so your own build output always wins and only requests your site can't serve fall through to Jamdesk:

vercel.json
{
  "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 because the middleware doesn't run on them and a rewrite can't set the header. Only add them 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), then open https://yoursite.com/docs and view the page source. Check two things:

  1. <link rel="canonical"> points at your domain, not YOUR_SLUG.jamdesk.app
  2. 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 rather than as a direct hit on your *.jamdesk.app subdomain. The ?jd_proxy=1 marker and the X-Jamdesk-Forwarded-Host header both carry that signal, and Jamdesk treats them as equivalent. Either one confirms your registered domain, points canonical, Open Graph, and sitemap links at it, and lets Custom domain only tell proxy traffic apart from direct hits.

A missing signal fails quietly — pages still render, but with noindex and subdomain canonicals if no custom domain is registered, or a failing Custom-domain-only check if one is. A 403 is the opposite problem: the signal is present but names a domain that isn't registered and active for this project.

Troubleshooting

Your matcher almost certainly includes /_next/:path*. Middleware runs before the filesystem check, so this 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. If you see <meta name="robots" content="noindex">, 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 is reaching Jamdesk, but names a domain Jamdesk won't serve for this project:

  1. Verify your domain is registered in the Jamdesk dashboard
  2. Complete DNS verification (TXT record)
  3. Check the domain maps to the correct project and is marked active
  4. On Option B, confirm the middleware sets the header to your visitor-facing domain, not a preview URL

Verify your rewrites (or matcher) cover both /docs and /docs/:path*. Missing the wildcard causes nested pages to fail.

Confirm /_jd/:path* and /_next/:path* are both routed. /_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, and that it exports a function named middleware. Next.js 16's deprecation warning recommends proxy.ts, but 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 actually reached Jamdesk. If it reports your proxy "doesn't identify itself," 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.

What's Next?

Custom Domain Only

Stop your subdomain from answering directly

Custom Domains

Verify DNS and troubleshoot

Subpath Hosting

Serve docs at /docs