---
title: Deployment
description: Deploy static docs to any host with zero configuration, or switch to server-side rendering with an adapter when you need dynamic behavior.
sidebar:
  label: Deployment
  order: 2
---

## Deploy anywhere (static)

`blume build` compiles your docs to plain HTML, CSS, and a local search index in `dist/`. There's no server to run — point any static host at the folder.

| Setting          | Value          |
| ---------------- | -------------- |
| Build command    | `blume build`  |
| Output directory | `dist`         |
| Node version     | 22.12 or newer |

These settings work on Vercel, Netlify, Cloudflare Pages, GitHub Pages, Amazon S3 + CloudFront, or any bucket or CDN. Make sure `blume` is a dependency so the host can run the build.

A static build includes:

- every docs and custom page as static HTML
- a local search index (Orama by default, Pagefind opt-in)
- a [`sitemap.xml`](/docs/configuration/seo#sitemap) and [`robots.txt`](/docs/configuration/seo#robots) when `deployment.site` is set
- `llms.txt` and `llms-full.txt` for AI tools
- redirect pages
- prerendered [Open Graph images](/docs/configuration/seo#open-graph-images) when `seo.og.enabled` is on

### Set your site URL

Sitemaps, canonical tags, RSS, and Open Graph images need an absolute origin. On **Vercel**, **Netlify**, and **Cloudflare Pages**, Blume detects it from the platform's environment at build time — no config required.

Set `deployment.site` to override the detected value, or to provide one on hosts that don't expose it (GitHub Pages, S3, a custom CDN):

```ts blume.config.ts lineNumbers
deployment: {
  site: "https://docs.example.com",
}
```

When detecting automatically, Blume prefers your stable production domain over per-deploy preview URLs, so the canonical origin stays put across deploys.

During `blume dev`, the site URL falls back to your local dev server (e.g. `http://localhost:4321`) when none is set, so site-gated features — Open Graph images, canonicals, the sitemap — work out of the box. Builds never use this fallback, so production output is never pointed at localhost.

## Preview locally

Before you ship, preview the production build exactly as a static host would serve it:

```bash
blume build
blume preview
```

## Subpath deploys

Serving docs under a path like `example.com/docs`? Set `deployment.base` — common for GitHub Pages project sites. The whole site, root included, moves under the base, and internal links and assets are rewritten to include it.

```ts blume.config.ts lineNumbers
deployment: {
  base: "/docs",
}
```

## Mount the docs under a path

`basePath` mounts every generated route under a segment (`/docs/getting-started`) while leaving the sidebar untouched — the top level is your sections, not a wrapper group. Use it when the docs live at `/docs/*` but the site root stays yours (like Docusaurus `routeBasePath` or Fumadocs `baseUrl`).

```ts blume.config.ts lineNumbers
basePath: "/docs",
```

Write links as if mounted at root (`/getting-started`); Blume rewrites them, along with redirects, the sitemap, canonical URLs, Open Graph images, `llms.txt`, and the search index. Public assets (images, files under `public/`) stay at the site root.

This is a distinct concept from the two paths above:

- A per-source [`prefix`](/docs/content/sources#multiple-sources) namespaces **one** source and **does** add a sidebar group.
- `deployment.base` is the host subdirectory the **whole** app is served from. The two compose — with both set, a page lands at `{deployment.base}/{basePath}/page`.

## Server rendering

Static output covers most docs. Switch to server output when you need request-time features — most notably the [Ask AI](/docs/configuration/ai) endpoint:

```ts blume.config.ts lineNumbers
deployment: {
  output: "server",
  adapter: "vercel",
}
```

The `vercel` and `node` adapters ship with Blume — choosing one just works. The `netlify` and `cloudflare` adapters must be installed in your project (e.g. `bun add -d @astrojs/netlify`); the CLI warns you if the package is missing:

| Adapter      | Package               | Use for                              |
| ------------ | --------------------- | ------------------------------------ |
| `vercel`     | `@astrojs/vercel`     | Vercel — the most polished path      |
| `netlify`    | `@astrojs/netlify`    | Netlify Functions                    |
| `node`       | `@astrojs/node`       | Self-hosted Node servers, containers |
| `cloudflare` | `@astrojs/cloudflare` | Cloudflare Workers and Pages         |

On **Vercel**, **Netlify**, and **Cloudflare Pages**, Blume picks the matching adapter automatically for server output — set `output: "server"` and deploy (on Netlify and Cloudflare, install the adapter package too). Set `adapter` explicitly to override the detected value, or when self-hosting with `node`.

A server build includes everything a static build does, plus any Astro endpoints or middleware you add. The `node` adapter produces a standalone server you can run directly.

On Vercel and Cloudflare, a server build also turns on [`Accept: text/markdown` content negotiation](/docs/configuration/ai#content-negotiation), so an agent requesting any content page with that header receives its raw-Markdown mirror at the same URL. On Vercel, Blume splices header-conditional rewrites into the deploy's routing config; on Cloudflare, it generates a small Worker in front of the Astro one and scopes `assets.run_worker_first` to the content routes, since the platform would otherwise serve the prerendered pages before any server code runs — other assets keep their zero-Worker fast path.

:::note
Server features have their own configuration — for example, Ask AI needs a model API key. See the [AI guide](/docs/configuration/ai) for setup.
:::

## Redirects

Map old URLs to new ones in `blume.config.ts`:

```ts blume.config.ts
redirects: [{ from: "/old", to: "/new", status: 301 }];
```

`status` accepts `301`, `302`, `307`, or `308` (default `301`). Server builds handle redirects at request time. Static builds emit redirect pages **and** platform files so your host issues a real HTTP redirect: `_redirects` (Netlify, Cloudflare Pages), `vercel.json` (Vercel), and `blume-redirects.json` — a structured manifest for anything else (nginx/Apache rules, an edge worker). A `_redirects` or `vercel.json` you ship in `public/` is left untouched.

:::note
`from` is matched as an exact path — wildcards and pattern matching (e.g. `/blog/:slug` or `/old/*`) aren't supported. If you need pattern-based rules, handle them in an infrastructure file like `vercel.json` (which supports wildcard `source` patterns) or your host's redirect config instead. A `vercel.json` you ship in `public/` is preserved as-is.
:::

:::note
Write both `from` and `to` as if mounted at root — under [`deployment.base`](#subpath-deploys) and [`basePath`](#mount-the-docs-under-a-path) alike, Blume rewrites both sides for you, so a redirect lands inside the base. A base you've already written into `to` by hand is preserved rather than doubled.
:::

## Content types

A static build also emits a `_headers` file that pins `charset=utf-8` onto the raw AI-ready endpoints — `/<route>.md`, `/<route>.mdx`, and the `.txt` files (`llms.txt`, `llms-full.txt`). Those responses are valid UTF-8, but many static hosts serve them as `text/markdown` / `text/plain` with **no** charset, and browsers then fall back to Windows-1252 — so non-ASCII docs (Japanese, accented Latin, …) render as mojibake when the raw URL is opened directly. HTML pages are unaffected because they carry `<meta charset>`. Netlify and Cloudflare (Pages/Workers static assets) read `_headers`; hosts that don't (Vercel, S3) ignore the file harmlessly. A `_headers` you ship in `public/` is left untouched.

## Environment variables

When a feature needs a runtime secret, Blume warns at `blume dev`/`build` if it's missing — so the problem surfaces early instead of at the first request:

| Feature | Variable |
| --- | --- |
| Ask AI (AI Gateway) | `AI_GATEWAY_API_KEY` (or Vercel OIDC) |
| Ask AI (other providers) | the provider's default key env var (`OPENROUTER_API_KEY`, `LLMGATEWAY_API_KEY`, `INKEEP_API_KEY`), or your configured `apiKeyEnv` |
| Mixedbread search | `MIXEDBREAD_API_KEY` |

Set them in `.env.local` for local dev and in your host's environment for production. Build-time secrets for search-index sync (Algolia, Orama Cloud, Typesense) are warned about separately during the sync step.

## Build summary

Every build prints a summary — output mode, adapter, resolved site URL, search provider, redirect count, sitemap and `llms.txt` status, and any enabled server features — so you can confirm what shipped (including anything auto-detected) before you deploy.
