Deploying the docs to GitHub Pages
The TeslaSync user docs are a VitePress site under docs/. The published site at https://teslasync.dev/ is a build artefact, not something you edit on the live server. This page describes how that pipeline works end to end so you can preview locally, ship safely, and debug when it breaks.
What you're publishing
Only the user-facing docs ship. Internal-only material — audits, runbooks, observability notes, architecture ADRs, signal audits, dev guidelines like A11Y_GUIDELINES.md — stays in the repo for contributors but is excluded from the built site.
The exclusion list lives in docs/.vitepress/config.ts under srcExclude. Anything matched there is skipped during build. If you add a new internal-only doc, add it to the list. If you want a previously-internal doc to be published, remove it from the list and verify it renders cleanly (some internal docs contain Vue-interpolation-confusing characters that need escaping for VitePress to parse them).
Preview locally
cd docs
npm install
npm run docs:devVitePress watches the markdown files and reloads instantly on save. The dev server is at http://localhost:5173 (port may shift if 5173 is busy).
Build a production bundle locally before pushing if you've made structural changes:
npm run docs:buildThis runs the same build as CI. Run the custom-domain asset gate too:
node scripts/verify-pages-assets.mjsThe gate checks the root-relative CSS/JS/hero references, screenshot files, and the published CNAME. A build can succeed while those asset URLs are wrong.
Preview the production bundle:
npm run docs:previewThis serves docs/.vitepress/dist at http://localhost:4173. Use this to verify the production base path and asset URLs work — that's where the most common Pages-specific bugs show up.
How the production build differs from dev
VitePress builds for a specific base path. The official site uses the custom domain https://teslasync.dev/, served at the domain root. Its CSS, JS, links, and screenshots must resolve under /, not /teslasync/. GitHub's project Pages URL redirects to the custom domain.
docs/.vitepress/config.ts reads the base path from the environment:
const base = process.env.DOCS_BASE || '/'CI (.github/workflows/docs.yml) builds with DOCS_BASE=/ to match the custom domain. Local dev and docs:preview also default to /.
If you fork without a custom domain, set DOCS_BASE in the workflow to /<repo-name>/ and remove the docs/public/CNAME file. A wrong base path produces a site that loads index.html but 404s on JS, CSS, and image assets.
Markdown links and images get the base applied automatically, but raw HTML in markdown does not. Link cards and other raw-HTML anchors must use Vue bindings:
<a :href="withBase('/guide/getting-started')">Install</a>with <script setup>import { withBase } from 'vitepress'</script> at the top of the page. (Theme .vue files should use withBase() the same way.)
What ships in dist
After a build:
docs/.vitepress/dist/
├── assets/ # hashed JS, CSS, fonts
├── images/ # static images from docs/public/images
├── logo.svg # static asset from docs/public
├── index.html # generated home
├── guide/ # generated per-page HTML
├── features/
├── deployment/
├── contributing/
└── 404.htmlAnything in docs/public/ is copied verbatim to the root of dist. Anything in docs/.vitepress/theme/ participates in the VitePress build pipeline. Don't put runtime-fetched data in docs/ — the docs are static.
The CI pipeline
The workflow lives in .github/workflows/docs.yml. The shape is:
- Trigger on push to
mainwith docs changes or by manual workflow dispatch. - Set up Node.
cd docs && npm ci.npm run docs:build.- Run
docs/scripts/verify-pages-assets.mjsto reject a subpath build on the custom domain. - Upload
docs/.vitepress/distas a Pages artefact. - Deploy the artefact to the
github-pagesenvironment.
The repo's Pages settings (Settings → Pages in GitHub) must be configured to deploy from GitHub Actions, not from a branch. Branch-based Pages is the legacy mode and will fight the workflow.
Triggering a deploy
A deploy happens when:
- Anyone merges to
mainwith changes underdocs/ - A maintainer manually re-runs the workflow from the Actions tab (e.g., to re-publish after a Pages settings change)
Pull requests do not trigger this workflow or deploy the site. Build and run the asset gate locally before merging a docs change.
Custom domain
If you're forking and want to publish on a custom domain:
- Replace
docs/public/CNAMEwith your domain (e.g.,docs.example.com). - Set
DOCS_BASE: /in.github/workflows/docs.yml(custom domains serve from the root). - Configure your DNS provider with the CNAME / A records GitHub Pages requires.
- Add the custom domain in Settings → Pages and wait for the TLS certificate to provision (a few minutes).
The bundled CNAME records the official custom domain in the published artifact.
Theme overrides
VitePress uses Vue Single-File Components for theme customisation. docs/.vitepress/theme/ is the override directory. We currently use the default theme with a small amount of CSS tuning; if you add component overrides, keep them additive — extend the default theme rather than replacing it, so future VitePress upgrades don't break your customisations.
Common things that go wrong
| Symptom | Likely cause | Fix |
|---|---|---|
Build fails with Element is missing end tag | A markdown file has Vue-style double-brace interpolation outside a fenced code block | Wrap the offending example in a fenced code block, or add the file to srcExclude |
| Site loads but every page is 404 | base is wrong for where you're publishing | Set DOCS_BASE: / in docs.yml for teslasync.dev (/<repo-name>/ without a custom domain) |
| Assets 404 in production but work in dev | Same as above — base path mismatch | Same fix |
| Search returns nothing | The local-search index didn't build (corrupt cache) | Delete docs/.vitepress/cache/ and rebuild |
| Build "succeeds" but the workflow fails | Pages settings aren't set to deploy from Actions | Settings → Pages → Source: GitHub Actions |
| Deploy succeeds but the URL still shows the old site | Cloudflare / CDN cache between you and Pages | Purge the cache, or wait it out (usually ~5 minutes) |
npm ci fails in CI but works locally | Lockfile drift | Commit the regenerated package-lock.json from CI's diff |
Excluding files mid-stream
If a doc breaks the build and you need to ship without it temporarily, add the path to srcExclude in docs/.vitepress/config.ts. The file stays in the repo for editors but is omitted from the production site until you fix it.
This is the mechanism the project uses for the development-guideline docs (A11Y_GUIDELINES.md, FORM_GUIDELINES.md, etc.) that contain Vue-interpolation-conflicting examples like double-brace expressions outside fenced code blocks.
Verifying before you push docs changes
The pre-push baseline for docs is:
cd docs
npm run docs:build
node scripts/verify-pages-assets.mjsIf either command errors, fix it locally — a VitePress build alone does not guarantee that assets resolve on the production domain.
Where to learn more
- VitePress docs for the upstream feature reference
- Contributing → Code Structure for where docs sit in the broader repo
- The
.github/workflows/docs.ymlfile for the canonical CI definition