Self-hosting on any static host
ui.plan.ai is a fully static site. pnpm build emits a self-contained dist/ directory; any host that serves static files can serve it. This page is the generic-host counterpart to Cloudflare Pages configuration.
What you’re deploying
Section titled “What you’re deploying”A single dist/ tree:
- Main app at
/(dist/index.html,dist/_astro/*, …) - Starlight docs at
/docs/(dist/docs/index.html,dist/docs/_astro/*,dist/docs/pagefind/*,dist/docs/sitemap-index.xml) - Header policy at
dist/_headers(Cloudflare/Netlify format) - Redirect rules at
dist/_redirects(Cloudflare/Netlify format) dist/robots.txt,dist/favicon.svg,dist/favicon.ico
No SSR, no Workers, no Edge Functions, no environment variables.
Build settings (any host)
Section titled “Build settings (any host)”| Setting | Value |
|---|---|
| Build command | pnpm build |
| Output / publish directory | dist |
| Root directory | / |
| Node version | from .node-version (24.15.0) |
| Package manager | pnpm via Corepack (already pinned in packageManager) |
| Environment variables | none |
If the host doesn’t auto-read .node-version, set Node 24.15.0 (or newer) explicitly. If it doesn’t auto-enable Corepack, prepend the build command with corepack enable && corepack prepare [email protected] --activate.
Trailing slashes
Section titled “Trailing slashes”Both Astro configs use trailingSlash: 'always' + build.format: 'directory'. The build emits page/index.html, so the URL space is directory-style.
- Requests for
/foo/must serve/foo/index.html(every static host does this by default). - Requests for
/foo(no slash) ideally 301 to/foo/. Cloudflare Pages, Netlify, and Vercel do this automatically; for raw Nginx, addtry_files $uri $uri/ =404;or rewrite to add the slash.
Redirects
Section titled “Redirects”public/_redirects (lands in dist/_redirects after build) holds the only required redirect:
/docs /docs/start-here/welcome/ 301/docs/ /docs/start-here/welcome/ 301Translation per host:
- Cloudflare Pages / Netlify — read
_redirectsnatively. Nothing to do. - Vercel — add to
vercel.json:{"redirects": [{ "source": "/docs", "destination": "/docs/start-here/welcome/", "permanent": true },{ "source": "/docs/", "destination": "/docs/start-here/welcome/", "permanent": true }]} - Nginx — in the relevant
server { }block:location = /docs { return 301 /docs/start-here/welcome/; }location = /docs/ { return 301 /docs/start-here/welcome/; } - Apache /
.htaccess:RedirectMatch 301 ^/docs/?$ /docs/start-here/welcome/ - CloudFront / S3 — use a CloudFront Function on viewer-request, or a redirect rule on the bucket.
- GitHub Pages — no native server-side redirects. The Starlight build also emits a meta-refresh HTML fallback at
dist/docs/index.html, so the redirect still works (with a brief flash) even when_redirectsis ignored.
Headers
Section titled “Headers”public/_headers (lands in dist/_headers) holds the security and caching baseline:
- Global
/*—Referrer-Policy: strict-origin-when-cross-origin,X-Content-Type-Options: nosniff,X-Frame-Options: DENY, deny-allPermissions-Policyfor sensors and payments,Cross-Origin-Opener-Policy: same-origin. No CSP yet (Starlight inlines styles/scripts). - Long-lived caches on content-hashed paths —
/_astro/*,/docs/_astro/*,/docs/pagefind/*→public, max-age=31536000, immutable. - Favicons — 1-day cache with
must-revalidate.
Per host:
- Cloudflare Pages / Netlify — read
_headersnatively. Nothing to do. - Vercel — port to the
headersarray invercel.json. - Nginx / Apache / CloudFront — port to the host’s native header/CDN config. Treat
public/_headersas the source of truth and keep your host config in sync.
The headers are a hardening baseline, not a requirement — the site renders fine without them.
Changing the production origin
Section titled “Changing the production origin”If you serve from anything other than https://ui.plan.ai, update site: in both astro.config.mjs and starlight/astro.config.mjs and rebuild. This drives sitemap absolute URLs and Starlight’s canonical <link> tags. Also update public/robots.txt (which references the sitemap URL).
Search
Section titled “Search”The docs site uses Starlight’s built-in Pagefind search. The index is generated at build time into dist/docs/pagefind/ and is fully static — no server-side search infrastructure needed.
Sitemap & robots
Section titled “Sitemap & robots”- Starlight emits
dist/docs/sitemap-index.xml(becausebase: '/docs'). dist/robots.txtreferences that sitemap. Submit to Search Console at the sitemap URL after deploy.
There is no root sitemap. If the main app gains real content, add one and extend robots.txt with a second Sitemap: line.
Troubleshooting
Section titled “Troubleshooting”/docs/ returns 404 after deploy
Section titled “/docs/ returns 404 after deploy”build:docs ran but public/docs/ wasn’t included in the upload. Re-run pnpm build locally and confirm dist/docs/index.html exists. Most common cause: using npm or yarn instead of pnpm — workspaces only resolve under pnpm here.
/docs (no slash) doesn’t redirect to the welcome page
Section titled “/docs (no slash) doesn’t redirect to the welcome page”Either the host isn’t honoring _redirects or it strips trailing slashes before applying redirects. Add the rule in the host’s native format (see Redirects). The Starlight meta-refresh at dist/docs/index.html is a fallback, not a primary mechanism.
Internal links 404 in production but work locally
Section titled “Internal links 404 in production but work locally”site: in one of the astro.config.mjs files doesn’t match the served origin, or your host is stripping trailing slashes. Fix site: and ensure the host serves page/index.html for both /page and /page/.
Pages load but assets 404 (CSS/JS/images)
Section titled “Pages load but assets 404 (CSS/JS/images)”The build output wasn’t uploaded in full. Re-upload dist/ recursively. Watch for hosts that skip dotfiles (_astro/, _headers, _redirects start with _ but are real files — not hidden).
Headers don’t apply
Section titled “Headers don’t apply”Your host doesn’t read _headers. Port the rules to the host’s native config. Verify with curl -I https://your-domain/ after deploy.
Stale assets after redeploy
Section titled “Stale assets after redeploy”/_astro/* and /docs/_astro/* are content-hashed and immutable for one year. Hard-refresh the browser; if the HTML still references old hashes, the deploy didn’t pick up the new build output.
See also
Section titled “See also”- Cloudflare Pages configuration — how
ui.plan.aiitself is hosted, with CF-specific dashboard settings and preview-deploy mechanics. - File & route conventions — docs/spec/runtime path layout.