Deploy
Publish on Vercel with Neon or run a Node server with Postgres.
The repository README includes a Vercel Deploy Button. It clones the source,
creates a project, and requests a Neon Postgres store through the Marketplace.
The store supplies pooled DATABASE_URL and direct DATABASE_URL_UNPOOLED
connections. Keep its environment-variable prefix unchanged.
Vercel setup
- Use the Deploy Button or import your fork into Vercel.
- Connect Neon to the project for production and preview environments.
- Set
SITE_NAMEif you want to override the content’s display name. It is read at build time, so redeploy after changing it. - Keep the repository’s install and build commands:
bun installandbun run vercel-build. - Edit the docs folder and push.
The button follows Vercel’s current
Deploy Button source reference.
The storage parameter is stores, a URI-encoded JSON array:
[
{
"type": "integration",
"integrationSlug": "neon",
"productSlug": "neon",
"protocol": "storage"
}
]Vercel derives SITE_ORIGIN from the production project URL or deployment URL.
Set it explicitly for a custom canonical origin. Production requests redirect
to its host unless DOCS_CANONICAL_REDIRECT=0; previews do not redirect.
Build-time ingest
bun run vercel-build resolves a content key once, builds icons, runs database
migrations, builds the server, checks the bundle budget, and then ingests
content. Validation failures stop deployment. The server bundle contains the
same DOCS_CONTENT_SHA that ingest used. Ingest runs last so a build that fails
never advances the production pointer: retention protects the version that
pointer named, which is therefore always one a successful build published.
The key resolves from DOCS_CONTENT_SHA, then VERCEL_GIT_COMMIT_SHA, then
local git HEAD, then a generated development key. Each site, SHA, and kind
identifies a content version. A ready version is reused unless DOCS_INGEST=force
or the ingest CLI’s --force is set. Force replaces content under the same key;
use a new key for a normal immutable publish.
Production ingest advances the site’s current pointer. Preview ingest writes
preview versions without changing that pointer. A deployed server selects
its baked commit, so a shared database does not make a preview replace the
production content. Local development serves the current pointer.
Retention and rollback
After ingest, collection protects the new version, the prior production
pointer (the last successful deploy build), and the current pointer. It also keeps the newest DOCS_KEEP_VERSIONS
other production versions (default 20) and production versions younger than
30 days. Production versions beyond both limits and previews or failed
attempts older than 30 days may be deleted. Watcher-created development versions
can be collected sooner beyond the count. Versions with Blob references stay
when their blobs cannot be deleted safely.
Age is measured from the content row’s created_at, not from the deployment’s
creation time. Redeploying a ready SHA returns “already ingested”; its creation
time and sequence do not change. A brand-new deployment reusing an old row can
lose that content during a later ingest if the row falls outside the keep count
and is not one of the protected pointers. DOCS_KEEP_VERSIONS=1 makes this easy
to encounter; a recent deployment is not a new 30-day guarantee.
To refresh an existing content key, check out its commit and run:
bun scripts/ingest.ts --dir docs --slug docs --sha <commit-sha> --kind production --forceAlternatively, use DOCS_INGEST=force for that commit’s deployment build.
Forced ingest atomically replaces the row with a new creation time and sequence.
It changes what deployments pinned to that key read; production ingest also
advances the current pointer. If the row was already collected, an ordinary
redeploy ingests it again. Count retention can preserve rows longer than the
age window, but it does not extend preview retention. Back up Postgres and any
Blob store.
Self-hosting
Use Node.js 24 or newer and a reachable Postgres database. Set DATABASE_URL,
SITE_ORIGIN, and NITRO_PRESET=node-server, then build through the same
migration and ingest sequence:
NITRO_PRESET=node-server bun run vercel-build
node .output/server/index.mjsExport the variables to both the build and the running server. Supply
DATABASE_URL_UNPOOLED when the main connection uses a transaction-mode pooler.
If Postgres uses a private CA, export NODE_EXTRA_CA_CERTS=/path/to/ca.pem
before process startup for the build, migrations, ingest, and server.
This adds the trusted CA while preserving certificate verification.
Optional integrations
Sentry is off without SENTRY_DSN; source-map uploads also require org,
project, and auth token. Local development stays silent even with a DSN unless
SENTRY_ENVIRONMENT is set (for example SENTRY_ENVIRONMENT=development). Agent-journey analytics are off without POSTHOG_KEY.
The assistant needs AI Gateway access: use AI_GATEWAY_API_KEY outside Vercel
or enable Gateway access with the deployment’s OIDC identity on Vercel.
x-margin.search.mode: "static" explicitly selects static search.
No Blob store is needed for ordinary docs. Without BLOB_READ_WRITE_TOKEN,
assets and the gzipped search corpus stay in Postgres within ingest size caps.
With it, large assets and the corpus use Vercel Blob. One Blob store can serve
production, every preview branch database and local development: each database
writes under its own namespace (minted per database address, so a branched copy
gets a new one) and deletes only its own blobs. Pointing a database at a new
address starts a new namespace and leaves the old one’s blobs in the store.
Hugeicons Pro is a
build-time opt-in through HUGEICONS_PRO_TOKEN; Free icons are the default.