Serving under a path
Your docs can live at a path of your product’s domain - example.com/docs - instead of a domain of their own. Your site keeps serving everything else, and a proxy in front of it passes /docs and everything under it to the docs site.
Tell Writedocs the path with basePath, and every address the docs make starts with it.
basePath
{
"domain": "example.com",
"basePath": "/docs"
}The path the site is served under, like /docs or /help/docs. It starts with / and has no / at the end; each part has lowercase letters, digits, - and _ only. Leave it out (or set "" or "/") for a site at the root of its domain - the default. Any other value is an error in writedocs validate and in the editor.
Set domain to the domain your readers see (example.com), not the address of the host behind the proxy: full URLs - canonical links, sitemap.xml, llms.txt, link previews - are domain + basePath, like https://example.com/docs/guides/setup/.
What changes
With "basePath": "/docs", every address the site makes starts with /docs:
- the links in the sidebar, tabs, version and language switchers, dropdowns, breadcrumbs, the footer, the topbar and the page menu;
- the site’s own files - styles, scripts, fonts, icons, the logo and favicon, link preview images;
- search;
sitemap.xml,robots.txt,llms.txt,llms-full.txtand each page’s Markdown copy;- the MCP server, at
/docs/mcp; - redirects, and the redirects from
/docsand from folder addresses to the first page.
Your pages keep site addresses
In your pages and in writedocs.json, keep writing addresses from the site’s root, without /docs. The base is added for you:
| You write | Readers get |
|---|---|
[Setup](/guides/setup) | /docs/guides/setup |
 | /docs/images/flow.png |
<a href="/api/">, <img src="/logo.svg"> | /docs/api/, /docs/logo.svg |
<Card href="/guides/setup"> | /docs/guides/setup |
"favicon": "/images/favicon.svg" | /docs/images/favicon.svg |
{ "source": "/old", "destination": "/new/" } in redirects | /docs/old → /docs/new/ |
../setup, https://example.com/pricing, #install | unchanged |
A link written with the base already in it - /docs/guides/setup - gets it twice and leads nowhere. writedocs broken-links reports it.
The same pages build with or without basePath, so you can move the docs between docs.example.com and example.com/docs without editing them.
The built site
writedocs build writes the site into a folder named after the path, so the files sit where their addresses say:
dist/
├── docs/ ← the whole site: pages, search, sitemap.xml, llms.txt, ...
│ ├── index.html
│ ├── guides/setup/index.html
│ ├── robots.txt
│ └── sitemap.xml
├── 404.html ← the "not found" page, with links under /docs
├── _redirects ← redirects, every path under /docs, plus / → /docs/
└── _headers ← only if your project has oneAt the root of dist/ there are only the files a host reads there: 404.html, _redirects, _headers, and the MCP server’s _worker.js, _routes.json and .assetsignore for Cloudflare. Serve dist/ at the root of any static host and /docs/... works with no extra rules.
writedocs dev serves the preview under the path too: it opens at http://localhost:4321/docs/.
Behind your proxy
Deploy the built site to a host - call it docs-host.example.net - and have your main site pass /docs and everything under it on to that host, with the path unchanged: a request for example.com/docs/guides/setup/ asks the host for /docs/guides/setup/. Everything outside /docs stays yours.
const DOCS_HOST = 'docs-host.example.net';
export default {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === '/docs' || url.pathname.startsWith('/docs/')) {
url.hostname = DOCS_HOST;
return fetch(new Request(url, request));
}
return fetch(request); // the rest of your site
},
};Route the Worker to example.com/docs*, or to the whole domain if it also serves the rest of your site.
{
"rewrites": [
{ "source": "/docs", "destination": "https://docs-host.example.net/docs" },
{ "source": "/docs/:path*", "destination": "https://docs-host.example.net/docs/:path*" }
]
}module.exports = {
async rewrites() {
return [
{ source: '/docs', destination: 'https://docs-host.example.net/docs' },
{ source: '/docs/:path*', destination: 'https://docs-host.example.net/docs/:path*' },
];
},
};location /docs {
proxy_pass https://docs-host.example.net; # no path after the host: the path goes as it came
proxy_set_header Host docs-host.example.net;
proxy_ssl_server_name on;
}- Keep the path. A proxy that strips
/docsbefore passing the request on breaks every page: the site’s files are under/docs/on the host too. - Send the host’s name. The request that reaches the docs host should carry its own name in the
Hostheader (docs-host.example.net), not yours - the Cloudflare, Vercel and Next.js examples do this by themselves; in Nginx it’s theproxy_set_header Hostline. - Redirects pass through. The site’s redirects answer with addresses under
/docs, so the reader stays on your domain. - “Not found” pages come from the host, with the status 404 and the site’s own look. An address outside
/docsis never sent to the docs.
Limits
-
robots.txtand the sitemap. Search engines only readrobots.txtat the root of your domain -example.com/robots.txt, which is yours, not the docs’. The docs’ own copy at/docs/robots.txthas the rules and theSitemap:line to copy into yours:example.com/robots.txtUser-agent: * Allow: /docs/ Sitemap: https://example.com/docs/sitemap.xmlOr submit
https://example.com/docs/sitemap.xmlin your search engine’s webmaster tools (Google Search Console, Bing Webmaster Tools). -
Your own CSS and JavaScript files are used as written. An address inside a
.cssor.jsfile of your project, or in thecontentof ascriptsentry, doesn’t get the base: write it in full (url(/docs/images/bg.png)), or, in a file inpublic/, relative to the file. The same goes for a_redirectsor_headersfile of your own: its rules match full paths, so they start with/docs. -
Addresses computed in code aren’t changed. An attribute written as an expression -
<a href={link}>- and anything a React snippet renders are left as they are; include the base yourself there. -
Only one place at a time. A build serves the docs under one path. To publish them at the root of a domain as well, build again without
basePath.