# Docusaurus & static builds

thin.host hosts the output of any static-site generator — Docusaurus, Astro, Vite, VitePress, Hugo, Next `output: 'export'` — and an agent can deploy it in one tool call. This page is the Docusaurus recipe; the same flow works for the others. Prefer to hand this to Claude Code? Install the [thin-host-docusaurus skill](https://thin.host/skills/thin-host-docusaurus/SKILL.md) (`curl -o ~/.claude/skills/thin-host-docusaurus/SKILL.md --create-dirs https://thin.host/skills/thin-host-docusaurus/SKILL.md`) and say "deploy my docs".

## What you get

- **One call to publish.** `publish_website(source_path="…/build", title, slug)` uploads the whole folder — hundreds of files are batched across requests automatically.
- **Clean redeploys.** `update_website(…, sync=true)` uploads the new build and deletes every file the old one left behind, so renamed hashed chunks never pile up.
- **Custom domain with TLS**, plus generated `robots.txt`, `sitemap.xml`, and canonical tags on it.
- **Your own 404.** The build's `404.html` becomes the site's not-found page and is served with a real 404 status.
- **Password or email gating** for the whole site or single pages, set in the dashboard and kept across redeploys.
- **Redirects that survive.** Redirect pages you author in the dashboard are never pruned by a sync.

## 1. Decide the baseUrl

Docusaurus bakes `baseUrl` into every asset URL. Where the site will be viewed decides the value:

| Where readers will look | baseUrl | Notes |
|---|---|---|
| A custom domain, e.g. `docs.example.com` | `/` | The normal case. The thin.host preview URL renders unstyled until the domain is mapped — expected, not a bug. |
| The preview URL `https://thin.host/s/<slug>/` | `/s/<slug>/` | Pick the slug first and pass it to `publish_website`. |

Make it switchable so both builds come from one repo:

```typescript
// docusaurus.config.ts
url: 'https://docs.example.com',
baseUrl: process.env.DOCUSAURUS_BASE_URL ?? '/',
```

```bash
# custom-domain build (default)
npm run build

# preview build for the shared thin.host origin
DOCUSAURUS_BASE_URL=/s/example-docs/ npx docusaurus build --out-dir build-preview
```

The publish tools detect Docusaurus and report `base_url`. When it will not resolve at the returned URL the response carries a `warning` that says exactly this — an agent should relay it rather than debug a "blank" site.

## 2. Publish

```python
publish_website(
    source_path="/abs/path/to/repo/build",
    title="Example Docs",
    slug="example-docs",   # needed if you built with baseUrl '/s/example-docs/'
)
# → { url: "https://thin.host/s/example-docs/", page_count: 277, asset_count: 400,
#     requests: 5, framework: "docusaurus", base_url: "/", warning: "…" }
```

Or from Claude Code, in words: *"Build the docs and publish the build folder to thin.host as example-docs."*

## 3. Redeploy with sync

```python
update_website(
    website="example-docs",              # slug, id, or live URL
    source_path="/abs/path/to/repo/build",
    sync=True,
)
# → { removed_assets: ["/assets/js/main.0a1b2c3d.js", …], kept_redirects: [], … }
```

Always pass `sync=true` for a build folder. Without it the site keeps working, but every previous build's chunks stay forever. Sync is safe to re-run; a deploy that fails partway says which request failed, and running it again finishes the job.

## No local server? Publish a zip

Through the hosted endpoint (`https://thin.host/mcp`, or any agent without a local MCP server) the build travels as a zip. Either upload it directly:

```bash
cd build && zip -qr ../site.zip . && cd ..
curl -X POST https://thin.host/v1/websites/example-docs/archive \
  -H "Authorization: Bearer $THIN_HOST_API_KEY" -F file=@site.zip     # sync=true by default
```

…or put the zip somewhere public and let the agent call `publish_website_from_url` / `update_website_from_url` with that link. Both apply the same mapping, baseUrl detection and sync rules as the local tools.

## 4. Map the domain

Dashboard → the website → Domains → add `docs.example.com` and create the CNAME shown. TLS provisions automatically, secondary hostnames 301 to the primary, and `robots.txt` / `sitemap.xml` are generated for the domain. An agent can instead call `provision_domain` and hand the user the claim URL.

## Protecting the docs

Password- or email-gate the whole site, or single pages, from the dashboard. Gating is stored on the page, and a redeploy updates pages in place, so it survives. Leave `/404` unprotected — a gated 404 page is not used for misses.

## REST, for CI

Everything above is three endpoints. Split the build into batches of at most 20MB, 200 pages, and 500 assets each:

```bash
# create (first batch) → note the slug
curl -X POST https://thin.host/v1/websites -H "X-Platform-API-Key: $THIN_HOST_API_KEY" \
  -H "Content-Type: application/json" -d @batch-0.json

# remaining batches — assets first, then pages
curl -X PATCH https://thin.host/v1/websites/example-docs -H "X-Platform-API-Key: $THIN_HOST_API_KEY" \
  -H "Content-Type: application/json" -d @batch-1.json

# prune with the full manifest
curl -X POST https://thin.host/v1/websites/example-docs/sync -H "X-Platform-API-Key: $THIN_HOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"keep": {"pages": ["/", "/docs/intro", "/404"], "assets": ["/assets/js/main.e7f12ca1.js", "…"]}}'
```

Full field reference: [REST API](https://thin.host/docs/api#post-v1websitesid_or_slugsync) · [MCP tools](https://thin.host/docs/mcp).

## Supported files

`.html` files become pages (`docs/intro/index.html` → `/docs/intro`, `404.html` → `/404`). Assets: css, js, mjs, map, png, jpg, jpeg, gif, svg, ico, webp, avif, mp4, webm, mp3, woff, woff2, ttf, otf, eot, wasm, json, xml, txt, csv, md, yaml, yml, pdf, webmanifest. Anything else (for example `.nojekyll`) is skipped and listed in `skipped_files`. Single files over ~15MB do not fit a request — compress or drop them.

---
Canonical page: https://thin.host/docs/static-sites
