Appearance
Deployment
How to run the app in development and produce/ship a production build. All commands run from the repo root (d:\WorkSpace\DevArea\NiftyToolKIT\xtract) unless noted.
TL;DR
| Goal | Command | Output |
|---|---|---|
| Dev server | npm run dev | http://localhost:3000 |
| Production build | npm run build | dist/ at the repo root |
| Build to another path | npx vite build --outDir "D:/path/to/out" --emptyOutDir | that path |
| Preview a build locally | npm run preview | serves dist/ on http://localhost:3000 |
Where is dist/?
The production build always lands in dist/ at the repository root — d:\WorkSpace\DevArea\NiftyToolKIT\xtract\dist. There is no build.outDir override in vite.config.js, so Vite uses its default (dist).
1. Development
bash
nvm use # Node 20.19.1 (from .nvmrc)
npm install # first time / after dependency changes
npm run dev # Vite dev server on port 3000 (HMR)npm run dev runs vite --host :: --port 3000. Edit files in src/ and changes hot-reload. No build artifact is produced in dev — everything is served from memory.
2. Production build
bash
npm run buildThis runs node tools/generate-llms.js && vite build:
generate-llms.jswritespublic/llms.txt(non-fatal — a failure here no longer blocks the build).vite buildcompilessrc/+index.html+public/*intodist/.
A successful run ends with a line like ✓ built in 1m 7s and a list of emitted dist/assets/* chunks. If you don't see that, the build didn't complete — see Troubleshooting below.
What dist/ contains
dist/
index.html
assets/… ← hashed JS/CSS chunks (code-split per route)
llms.txt manifest.json robots.txt sitemap.xml ← copied from public/Preview the build locally
bash
npm run preview # serves the built dist/ on http://localhost:3000Use this to sanity-check the production bundle before shipping.
3. Build to a specific path (outside the codebase)
Use Vite's --outDir. When the target is outside the project root, also pass --emptyOutDir (Vite refuses to clear a directory outside the root without it):
bash
# absolute path
npx vite build --outDir "D:/builds/nefoxx" --emptyOutDir
# or via the npm script (args after -- are appended to `vite build`)
npm run build -- --outDir "D:/builds/nefoxx" --emptyOutDirNotes:
- A relative
--outDiris resolved relative to the repo root (e.g.--outDir ../nefoxx-dist). --emptyOutDirwipes the target directory first — point it at a dedicated build folder, never at a directory that holds other files.generate-llms.jsstill writes topublic/llms.txtin the repo (then Vite copies it into your--outDir).- Deploying to a sub-path (e.g.
https://host/app/)? Add--base=/app/so asset URLs resolve.
4. Shipping to production (Cloudflare Pages, via GitHub Actions)
Production is static hosting on Cloudflare Pages (moved off Hostinger 2026-07-02). Deployment is no longer a manual dist/ upload — it's automated end-to-end through GitHub Actions. You never run npm run build and upload anything by hand for a normal release; you push code and, when ready, trigger a promotion.
Three environments, three domains, one pipeline:
| Environment | Domain | How it deploys |
|---|---|---|
| dev | devv.nefoxx.com | Automatic — every push to develop that passes CI |
| UAT | uatt.nefoxx.com | Manual — run Deploy to UAT from the Actions tab (pick develop) |
| production | nefoxx.com | Manual — run Deploy to Production from the Actions tab (pick main) |
Every deploy is gated on CI (Lint, Test, Build + SonarQube Scan + E2E Smoke (Playwright)) actually passing for the exact commit being shipped — a failing or not-yet-run CI check blocks the deploy outright, dev included. After the build is pushed live, deploy.yml also runs a deploy-smoke Playwright check against the just-deployed domain itself (real rendered content + zero console errors on /auth and a public content page, read-only) before the job is considered successful — a plain HTTP-200 response is no longer treated as proof the deploy actually works. See E2E Testing Strategy §Phase 3 for the full design.
Each domain has its own Supabase backend
dev/uat builds point at the Nefoxx-Dev Supabase project; production builds point at Nefoxx-Prod — two fully independent projects, not one shared backend. deploy.yml injects the correct VITE_SUPABASE_* values per environment automatically (GitHub Environment variables). See Environment Strategy for the full architecture and the mandatory promotion rule for anything that touches the Supabase side (migrations, Edge Functions, secrets).
Normal release flow:
bash
git push origin develop # ships to devv.nefoxx.com automatically once CI is green
# test on devv.nefoxx.com, then when ready:
# Actions tab -> "Deploy to UAT" -> Run workflow -> branch: develop
# test on uatt.nefoxx.com, then when ready to release:
git push origin develop:main # bring main up to date (or merge via PR once branch protection lands)
# Actions tab -> "Deploy to Production" -> Run workflow -> branch: mainFull pipeline design, the Cloudflare Pages "Production branch" setup prerequisite, and the rationale behind each gate: SonarQube Integration §4c. Day-to-day branch/PR mechanics: Git Setup & Workflow.
Manual fallback (only if the pipeline itself is broken — not the normal path): npx wrangler pages deploy dist --project-name=<project> --branch=<dev|uat|production> after npm run build, using the same Cloudflare API token GitHub Actions uses. --branch must match that Cloudflare Pages project's configured Production branch setting exactly, or the upload silently becomes an unlisted Preview build instead of updating the live domain — see the prerequisite section linked above.
5. Rolling back a deploy
A bad deploy (broken build that somehow passed CI, a bad frontend/backend pairing, or a regression the deploy-smoke check missed) needs a fast, manual way back to the last known-good state. There is no automated rollback in this pipeline — consistent with every other deploy gate in this repo (manual trigger, human judgment, no auto-revert) — a human always decides and acts.
Option A — Cloudflare Pages dashboard rollback (fastest, no rebuild)
Every previous deployment's built assets stay live on Cloudflare's edge until the project's retention limit — rolling back re-points the domain at one of them instantly, with no rebuild and no GitHub Actions run:
- Cloudflare dashboard → Workers & Pages → the affected project (
CF_PAGES_PROJECT_DEV/_UAT/_PROD— see the GitHub Environment variables for the exact project names). - Deployments tab → find the last deployment that was known-good (commit SHA + timestamp shown per row — cross-reference against GitHub's commit history / Actions run log to identify it).
- Open that deployment's
...menu → Rollback to this deployment. Confirm. - Verify: reload the domain (
devv.nefoxx.com/uatt.nefoxx.com/nefoxx.com), confirm the regression is gone. Optionally re-runnpx playwright test --project=smoke-deploylocally withE2E_TARGET=deployandDEPLOY_SMOKE_BASE_URL=https://<domain>to confirm functionally, not just visually.
This only reverts the static frontend artifact. If the bad release also shipped a Supabase-side change (migration, Edge Function, secret), the dashboard rollback does not undo that — see the backend note below.
Option B — Re-run the GitHub Actions workflow against a known-good commit (rebuild)
Use when Option A isn't available (e.g. the bad deployment already aged out of Cloudflare's retention), or when you specifically want a fresh, auditable build rather than reusing old assets:
- Identify the last known-good commit SHA on the relevant branch (
developfor dev/UAT,mainfor production). git revert <bad-commit-sha>on that branch and push — this keeps history honest (no force-push, no rewritten history) and produces a new commit that CI must pass like any other.- Once CI is green on the revert commit: dev redeploys automatically on push to
develop; for UAT/ production, Actions tab → Deploy to UAT / Deploy to Production → Run workflow → pick the branch with the revert commit on it. - Verify the same way as Option A step 4.
Backend rollback (migrations / Edge Functions) — out of scope here, flag it
This runbook covers the frontend static artifact only. If the bad release included a Supabase migration or Edge Function change:
- Never edit or delete the merged migration file — per the Database Rules, write a new compensating migration (e.g. a matching
DROP/reverse-ALTER) and promote it through both environments the normal way. - Edge Functions:
npx supabase functions deploy <name>with the prior version's source (check out the pre-bad-commitsupabase/functions/<name>/tree and redeploy it), or delete/disable the function if the prior behavior is safer than any available version. - Full backend disaster-recovery tooling (automated PITR restore, etc.) is tracked as Phase F of the backend CI/CD initiative (NFX-027) and is not built yet — today this is a manual, deliberate action against the Supabase dashboard/CLI, same as the frontend options above.
Troubleshooting
npm run build finishes instantly / dist/ isn't updated. The old build script chained operators as node generate-llms.js || true && vite build. On Windows, npm runs scripts through cmd.exe, which groups that as generate-llms || (true && vite build) — so when generate-llms succeeds, vite build is skipped and the build exits 0 without producing anything. This was fixed: the script is now node tools/generate-llms.js && vite build and generate-llms.js is non-fatal. If you ever hit a similar silent build, run the steps explicitly:
bash
node tools/generate-llms.js # optional; safe to skip
npx vite build # the actual build → dist/Can't find dist/. It's at the repo root: d:\WorkSpace\DevArea\NiftyToolKIT\xtract\dist. Confirm the build printed ✓ built in …. A dist/ may also be .gitignore-excluded from view in some tools, but it exists on disk after a successful build.
Build is slow (~1 min). Expected for this app size. The > 500 kB chunk warning is informational.
Note: the internal docs portal builds separately
This documentation portal is not part of the app's production build. It has its own commands and output and must never ship to production:
bash
npm run docs:gen # regenerate auto-generated reference pages
npm run docs:dev # serve the portal locally
npm run docs:build # static HTML → documentations/portal/docs/.vitepress/dist