Shoalrun documentation framework
For agents
Start with https://www.shoalrun.com/llms.txt for the documentation index, then read the Markdown pages it links. Skip to article. Documentation index. Read this page as Markdown.
How to

Publish on Vercel

Build a Vercel deployment with Markdown negotiation and correct discovery headers.

Updated
Read as Markdown

Shoalrun can produce a Vercel Build Output API directory. It contains the compiled static site and routing rules for each published page. Vercel serves HTML to browsers and the generated Markdown twin when a request includes Accept: text/markdown. The route rules also set Vary: Accept and an HTTP Link header to the .md page. Explicit .md URLs remain available.

The content negotiation guide explains the same HTTP contract for any host. This page shows how to configure Vercel specifically.

Prepare your project

Start with the quickstart. Your documentation repository installs the private package and keeps its own content and configuration. It needs no framework source checkout:

.npmrc
package.json
package-lock.json
docs.config.yaml
content/
vercel.json

Set site.origin in docs.config.yaml to your intended public HTTPS origin before release. This controls canonical and share-image URLs. Generate deployment output locally:

npx --no-install shoalrun vercel build docs.config.yaml .vercel/output

Generating output does not deploy it.

Configure the project

For host installs, commit a project .npmrc containing only the registry and an environment-variable placeholder:

.npmrc
@shoalrun:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}

Set NODE_AUTH_TOKEN in Vercel's build environment to a GitHub classic token with read:packages, owned by an account with access to the package. Set it for each environment that should install the package, including Preview if enabled. Keep the token out of the repository. Local npm ci with this .npmrc also requires that environment variable.

Set the Vercel project root to the documentation repository root and choose the Other framework preset. Add:

vercel.json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "framework": null,
  "installCommand": "npm ci",
  "buildCommand": "npx --no-install shoalrun vercel build docs.config.yaml .vercel/output"
}

Keep the build output directory setting unset: this command produces the Build Output API directory directly. If your config lives under docs/, adjust its path. Connecting the Git repository can trigger builds on pushes; check your build budget before enabling that integration.

Deploy Shoalrun's own documentation

The docs repository's root vercel.json serves the site under docs/. It runs npm ci and npm run docs:vercel using the installed package. Production and local builds use the public https://www.shoalrun.com origin in docs/docs.config.yaml for absolute page and share-image URLs. Preview builds use their VERCEL_URL instead. Public link previews require the shared page and its image to be accessible without sign-in. Vercel deployment protection can prevent crawlers from reading preview deployments.

The Vercel output puts pages and discovery files in .vercel/output/static/. Its config.json defines page routes and content types, including text/markdown for .md files and the linkset type for /.well-known/api-catalog when that file exists. Keep the entire .vercel/output/ directory together for deployment. The API catalog is optional and appears only when the project declares a public API endpoint and publishes its source.

For Shoalrun's own site, npm run docs:build-all makes a complete prebuilt bundle: it compiles the pages and embeds the passages for hosted chat and search. Repeat builds reuse private passage vectors when retrieval settings and passage digests match. Only new or changed passages need embedding. The build reports embedded and reused counts. It generates answer cards from docs/config/answers.yaml when the passages, questions, chat settings, or generator code have changed; otherwise it reuses the previous verified cards. It checks passage coverage and card digests before returning. The index and cards stay inside private functions. New embeddings call the configured provider; generating cards also calls the relevance scorer and chat model. Review answers.json in the function before deployment. Deleting .vercel/output removes the local vector and card caches. The docs repository's README has the local command sequence. The hosted search endpoint uses embeddings within shared rate and daily budgets, then returns lexical results when its embedding budget is exhausted.

The card cache lives in the previous local .vercel/output bundle. The command compares a fingerprint of its passage index, embedding metadata, question list, chat and retrieval settings, and compiled generator code with the new bundle. When they match, it copies the prior cards into the new private function and checks their source digests again. If the inputs changed, the old bundle is missing, or validation fails, it regenerates all cards. The final line says reused or generated. This saves card model and scorer calls on an unchanged rebuild. A fresh checkout or deleted .vercel/output has no local card or passage vector cache.

Deploy without connecting GitHub

You can keep building locally and deploy the Build Output API directory with the Vercel CLI --prebuilt option. Use vercel link to connect the local directory to the existing Vercel project; this does not require importing your source repository. From the docs repository root, use npm run docs:build-all for Shoalrun's own site when you want fresh embeddings and answer cards. For another site, build its Vercel output as configured:

npm run docs:vercel
vercel link
vercel deploy --prebuilt --prod

The command uploads .vercel/output and applies its routes and headers. A folder upload of docs/dist through Vercel Drop publishes the static files but does not apply Shoalrun's Vercel routing rules. It is suitable for a visual preview; the deployment checker will still report missing Markdown negotiation and response headers. Run it against the final domain after a prebuilt deploy.

The routes also cover:

  • Redirects. Each entry in the project's redirects config becomes a route with its status, 308 by default. A second route sends the old .md URL to the target's Markdown twin.
  • The site root. When no page has the slug /, a request for / gets a 302 to the page with ID guide.overview. If there is no such page, it goes to /reference, then to the first page. shoalrun serve uses the same rule.

Each run of vercel build deletes and rewrites .vercel/output/static/, so don't keep other files there.

Include an MCP endpoint

Enable mcp in your project config before running shoalrun vercel build. The output includes a separate Node function for the selected public publication, without requiring Ask or embeddings. See connect an MCP client for configuration, host trust, limits, and the current verification status.

MCP analytics remains off unless you also configure its server adapter. If enabled, set its credentials on the function’s runtime environment. Browser analytics settings do not collect MCP traffic.

Check the deployment

After Vercel gives you a deployment URL, run the provider-neutral deployment checker before release. Shoalrun's docs repository includes it as a site-owned command:

npm run deploy:check -- https://your-docs.example/

It checks every published page, discovery resources, and missing routes. To inspect one page manually, replace https://your-docs.example below and compare the responses:

curl -I -H 'Accept: text/html' https://your-docs.example/install/
curl -I -H 'Accept: text/markdown' https://your-docs.example/install/
curl -I https://your-docs.example/install.md

The first request should return Content-Type: text/html; the second should return Content-Type: text/markdown, Vary: Accept, and Content-Location: /install.md. Both page responses include an HTTP Link to the Markdown twin. The third request tests the explicit .md URL. Check /llms.txt and, if enabled, /.well-known/api-catalog as well.

The Vercel static routing rule selects Markdown when the Accept header explicitly includes text/markdown with nonzero quality. It keeps HTML when Markdown has a fractional weight and text/html has full weight, or when text/html is absent and text/* or */* has full weight. A header rule can't compare two fractional weights. So text/html;q=0.9, text/markdown;q=0.8 and text/markdown;q=0.3, text/*;q=0.5 get Markdown on Vercel and HTML from the Node server.

Run the post-deploy checker after publishing each new revision. The fractional-weight limitation above remains.

Next steps