# Serving under a path

> For the complete documentation index, see [llms.txt](https://docs.writedocs.io/llms.txt).

Publish the docs at a path of your own domain, like example.com/docs, behind your proxy - the basePath field, what it changes, proxy examples and limits.

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`

```json title="writedocs.json"
{
  "domain": "example.com",
  "basePath": "/docs"
}
```

**`basePath`** `string`

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.txt` and each page's Markdown copy;
- the [MCP server](https://docs.writedocs.io/docs/configuration/mcp/), at `/docs/mcp`;
- [redirects](https://docs.writedocs.io/docs/configuration/redirects/), and the redirects from `/docs` and 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` |
| `![Diagram](/images/flow.png)` | `/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:

```text
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 one
```

At 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](https://docs.writedocs.io/docs/configuration/mcp/#deploying). 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.

**Cloudflare Worker**

```js title="worker.js"
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.

**Vercel**

```json title="vercel.json"
{
  "rewrites": [
    { "source": "/docs", "destination": "https://docs-host.example.net/docs" },
    { "source": "/docs/:path*", "destination": "https://docs-host.example.net/docs/:path*" }
  ]
}
```

**Next.js**

```js title="next.config.js"
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*' },
    ];
  },
};
```

**Nginx**

```nginx title="nginx.conf"
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 `/docs` before 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 `Host` header (`docs-host.example.net`), not yours - the Cloudflare, Vercel and Next.js examples do this by themselves; in Nginx it's the `proxy_set_header Host` line.
- **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 `/docs` is never sent to the docs.

## Limits

- **`robots.txt` and the sitemap.** Search engines only read `robots.txt` at the root of your domain - `example.com/robots.txt`, which is yours, not the docs'. The docs' own copy at `/docs/robots.txt` has the rules and the `Sitemap:` line to copy into yours:

  ```text title="example.com/robots.txt"
  User-agent: *
  Allow: /docs/

  Sitemap: https://example.com/docs/sitemap.xml
  ```

  Or submit `https://example.com/docs/sitemap.xml` in 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 `.css` or `.js` file of your project, or in the `content` of a `scripts` entry, doesn't get the base: write it in full (`url(/docs/images/bg.png)`), or, in a file in `public/`, relative to the file. The same goes for a `_redirects` or `_headers` file 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`.