Deploy to Vercel
Serve a Reed build on Vercel, with the _headers rules rewritten into vercel.json and trailing slashes kept.
This recipe is written from Vercel’s documentation and Reed’s output layout. It has not
been run as a live deploy. After your first deploy, check two things: a .md mirror comes
back as text/markdown, and a nested page URL such as /docs/ship/deploy/ resolves. The
commands are in the last section.
Vercel serves a static output directory well, but it does not read _headers. The one
file Reed writes for your host is ignored here, so you restate its rules in vercel.json.
Everything else is choosing the output directory and the trailing-slash behaviour.
1. Point Vercel at the build
Section titled “1. Point Vercel at the build”Choose Other as the framework preset, then set the build command and output directory.
You can do this in the project settings or in vercel.json at the repository root:
{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "reed prefetch && reed build --out dist", "outputDirectory": "dist"}Only the contents of the output directory are served. The build command has to find a
reed CLI in Vercel’s build image. See Offline builds in CI for
what reed prefetch needs.
2. Stage the tree if you set base
Section titled “2. Stage the tree if you set base”Vercel serves the output directory at the root of the deployment’s domain. With
base: "/docs", every link Reed emits starts with /docs/, but the files sit at the root
of dist/. Serve dist/ as it is and /docs/ is empty. The deploy succeeds and every
page 404s.
Serve a staged directory with a real docs/ segment:
set -eu
reed prefetchreed build --out dist
rm -rf stagemkdir -p stage/docscp -R dist/. stage/docs/
# Vercel does not read _headers. Drop it so it is not served as a file.rm stage/docs/_headersSave that as scripts/stage.sh and point the build at stage/:
{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "sh scripts/stage.sh", "outputDirectory": "stage"}The general rule is on Deploy.
3. Translate _headers into vercel.json
Section titled “3. Translate _headers into vercel.json”Reed’s _headers file says two things. .md mirrors are text/markdown; charset=utf-8,
and llms.txt is text/plain; charset=utf-8. Vercel needs the same rules as headers
entries. Each entry has a source pattern and a list of key/value pairs:
{ "$schema": "https://openapi.vercel.sh/vercel.json", "outputDirectory": "stage", "trailingSlash": true, "headers": [ { "source": "/(.*)\\.md", "headers": [ { "key": "Content-Type", "value": "text/markdown; charset=utf-8" } ] }, { "source": "/docs/llms.txt", "headers": [ { "key": "Content-Type", "value": "text/plain; charset=utf-8" } ] } ]}Notes on the translation:
sourceis a path pattern, not a_headerssplat./(.*)\\.mdis a regular expression group. The doubled backslash is JSON escaping for a literal dot. It matches a.mdpath at any depth.llms.txtis an exact path, so write the one you serve. On a based site that is/docs/llms.txt. On a site at the root, use/llms.txt. Reed’s_headerscovers both cases with two rules. Invercel.jsonyou know which one applies.- Nothing reads the
_headersfile on Vercel. Keeping it only publishes it as a file, which is why the staging script deletes it.
Skip this step and the site still works. What you lose is the machine-readable half: the
mirrors and llms.txt arrive without the content type assistants rely on. See
Response headers and llms.txt.
4. Trailing slashes and cleanUrls
Section titled “4. Trailing slashes and cleanUrls”Reed writes one directory per page, ship/deploy/index.html, and emits its page links and
canonical URLs with a trailing slash: /docs/ship/deploy/. Footer links from docs.json
are emitted as written, without an added slash, and reach the page through the redirect
below. Match that on Vercel:
trailingSlash: true, as in the example above./docs/ship/deploygets a 308 to/docs/ship/deploy/. Paths with a file extension are left alone, sodeploy.md,llms.txtand everything in_astro/are served as they are.- Do not use
trailingSlash: false. It redirects/docs/ship/deploy/to/docs/ship/deploy, the opposite of every URL in the build, the sitemap and the canonical tags. Every internal click then costs a redirect. - Do not leave it unset. Vercel then serves both forms without redirecting, and documents that this invites duplicate-content indexing.
cleanUrls does not help here. It strips .html from file names and redirects
/about.html to /about. Apart from 404.html, Reed does not emit page.html files. Each page is already an
index.html inside its own directory, so there is nothing for cleanUrls to clean. Leave
it at its default of false.
5. Deploy
Section titled “5. Deploy”With Git integration, Vercel runs the build command on every push. To deploy from your own machine or CI with the Vercel CLI:
vercel --prodRun it from the project root. Vercel’s first deployment of a new project is a production
deployment even without --prod.
Redirects
Section titled “Redirects”Reed’s redirects in docs.json compile to meta-refresh HTML pages and a _redirects
file. Vercel does not read _redirects, so it serves the meta-refresh pages with a 200.
See Redirects. For a real status code, add a
redirects entry to vercel.json:
{ "redirects": [ { "source": "/docs/old-guide", "destination": "/docs/guides/getting-started/", "permanent": true } ]}On Vercel, permanent: true sends 308 and false sends 307. Use statusCode if you need
exactly 301. Write the base prefix yourself. vercel.json sees served paths, unlike
docs.json, where Reed adds the prefix for you.
Verify after the first deploy
Section titled “Verify after the first deploy”Check that a nested Markdown mirror has the right content type:
curl -s -o /dev/null -w "%{content_type}\n" https://example.vercel.app/docs/ship/deploy.mdExpect text/markdown; charset=utf-8. Anything else means the headers entry did not
match. Check the source pattern and its escaping first.
Check that a nested page resolves, and that the slashless form redirects:
curl -s -o /dev/null -w "%{http_code}\n" https://example.vercel.app/docs/ship/deploy/curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" https://example.vercel.app/docs/ship/deployExpect 200, then 308 to the trailing-slash URL. A 404 on the first request, on a
based site, means the output directory is dist rather than stage.
Request an unknown URL under /docs/ too, and look at what comes back. Reed’s page is at
docs/404.html on a based site. If Vercel shows its own 404 page instead, copy
docs/404.html to the root of stage/ and test again. Then grep a live page for a string
from this build, as Deploy describes.
Related
Section titled “Related”- Netlify and Cloudflare Pages
read Reed’s
_headersfile directly. - nginx for another host that needs the rules translated.
- site and base for what
basechanges. - Build output for every file in the tree you are serving.