Production-ready monorepo boilerplate for React + TypeScript + Vite apps with automated CI/CD, E2E testing, and staging-to-production deployment pipelines.
A reference architecture for running multiple apps in a single monorepo with automated deployment on merge to main. Contains a static site and two React SPAs that share components and utilities, with full CI/CD validation gates including E2E testing.
- Monorepo with pnpm workspaces — Multiple apps and shared packages in a single repository with efficient dependency management
- React 19 + TypeScript + Vite 6 — Modern frontend stack with fast HMR and strict type checking
- Shared component library — Reusable React UI components and utility functions across all apps
- Automated CI/CD pipelines — GitHub Actions and GitLab CI with 7 validation gates before any deployment
- Intelligent change detection — Only validates and deploys apps affected by each change
- Staging + production environments — Branch-based deployment with environment-specific builds and approval gates
- E2E testing with Playwright — End-to-end tests, cross-app navigation tests, visual regression, and accessibility testing (axe-core)
- Docker-based local development — nginx routing for all apps with Docker Compose, matching production topology
- Automated rollback — Timestamped backups with one-command rollback via rsync over SSH
- Code quality enforcement — ESLint, Prettier, commitlint (Conventional Commits), Husky pre-commit hooks
- Dependency management — Dependabot configured for npm, GitHub Actions, and Docker image updates
- Comprehensive documentation — Architecture guides, deployment flow, setup instructions, and extension guides
Monorepo
┌──────────────────────────────────────────────────┐
│ │
│ apps/ packages/ │
│ ├── main-site/ ├── shared-ui/ │
│ │ (static HTML/CSS) │ (React components)│
│ ├── admin-spa/ └── shared-utils/ │
│ │ (React + Vite) (utilities) │
│ └── portal-spa/ │
│ (React + Vite) │
│ │
└──────────────────────────────────────────────────┘
│ │
open PR to main push to staging
│ │
▼ ▼
┌──────────────────────────────────────────────────┐
│ CI/CD Pipeline │
│ │
│ detect changes │
│ │ │
│ ├── format check ──┐ │
│ ├── lint ──────────┤ │
│ ├── typecheck ─────┤ all must │
│ ├── unit tests ────┤ pass │
│ ├── build ─────────┤ │
│ ├── security audit ┤ │
│ └── E2E tests ─────┘ │
│ │ │
│ ▼ │
│ deploy only changed apps │
│ (staging or production depending on branch) │
│ │ │
│ ▼ │
│ health check │
└──────────────────────────────────────────────────┘
│ │
rsync over SSH rsync over SSH
│ │
▼ ▼
┌────────────────────┐ ┌─────────────────────────┐
│ Staging Server │ │ Production Server │
│ (push to staging)│ │ (merge to main) │
│ │ │ │
│ verify & approve │──▶│ /var/www/html/ │
│ before prod │ │ /var/www/html/admin/ │
│ │ │ /var/www/html/portal/ │
└────────────────────┘ └─────────────────────────┘
Tests run at three stages — not just before merge:
| Trigger | What happens |
|---|---|
Pull request to main |
Full validation (lint, typecheck, unit tests, build, security audit, E2E). All gates must pass before the PR can be merged. |
Push to staging |
Full validation runs again, then deploys to staging on success. |
Push to main (merge) |
Full validation runs again, then deploys to production on success. |
Tests re-run after merge because the merged code may differ from the PR (e.g., merge conflicts, concurrent PRs). This ensures nothing broken reaches production.
Changes to packages/ automatically trigger both SPA apps. The main site is independent.
detect changes
│
├── format check ─────┐
├── lint ──────────────┤
├── typecheck ─────────┤ all must pass
├── unit tests ────────┤
├── build ─────────────┤
├── security audit ────┘
│
└── E2E tests (needs builds)
│
▼
deploy only changed apps (if on staging or main branch)
│
▼
health check
The recommended flow uses a staging branch for pre-production verification:
- Develop on a feature branch — open a PR to
main - PR validation — CI runs all gates (lint, typecheck, tests, build, E2E). All must pass.
- Deploy to staging — merge your feature branch into the
stagingbranch:CI validates again, then deploys to your staging server with staging-specific config (git checkout staging git merge feature/my-change git push origin staging
.env.staging). - Test on staging — verify the deployment works in a production-like environment. Get final approval from stakeholders.
- Deploy to production — merge your PR to
main(or mergestagingintomain):CI validates one final time, then deploys to production.git checkout main git merge staging git push origin main
- Post-deploy health check — CI automatically verifies each deployed app is responding.
Configure these in Settings > Secrets and variables > Actions:
| Secret | Staging | Production |
|---|---|---|
| SSH key | DEPLOY_SSH_KEY (shared) |
DEPLOY_SSH_KEY (shared) |
| Host | DEPLOY_STAGING_HOST |
DEPLOY_HOST |
| Port | DEPLOY_STAGING_PORT |
DEPLOY_PORT |
| User | DEPLOY_STAGING_USER |
DEPLOY_USER |
| Admin SPA path | DEPLOY_STAGING_ADMIN_SPA_PATH |
DEPLOY_ADMIN_SPA_PATH |
| Portal SPA path | DEPLOY_STAGING_PORTAL_SPA_PATH |
DEPLOY_PORTAL_SPA_PATH |
| Main site path | DEPLOY_STAGING_MAIN_SITE_PATH |
DEPLOY_MAIN_SITE_PATH |
For an approval gate before production deploy, configure Settings > Environments:
- staging — auto-deploy on push (no protection needed)
- production — add required reviewers so a team member must click "Approve" in the GitHub UI before the production deploy job runs
This gives you a manual approval step even though the pipeline is automated.
| Path | Description |
|---|---|
apps/main-site/ |
Static HTML/CSS/JS site (served at /) |
apps/admin-spa/ |
Admin dashboard — Vite + React + TypeScript |
apps/portal-spa/ |
Customer portal — Vite + React + TypeScript |
packages/shared-ui/ |
Shared React components (Button, etc.) |
packages/shared-utils/ |
Shared utility functions (formatDate, relativeTime) |
scripts/ |
Bash scripts for build, deploy, rollback, health check |
docker/ |
Docker + nginx config for local dev and E2E testing |
e2e/ |
Playwright E2E and visual regression tests |
.github/workflows/ |
GitHub Actions CI/CD pipeline |
.gitlab-ci.yml |
GitLab CI/CD pipeline |
git clone <repo-url>
cd react-spa-monorepo-cicd
# Install pnpm (pick one):
npm install -g pnpm@10 # direct install
# OR
corepack enable # uses Node's built-in package manager shim
# Run the full pipeline — install, lint, typecheck, test, build, Docker, E2E:
bash scripts/run-all.shThat single command runs all 11 pipeline steps and prints a pass/fail summary.
pnpm install
pnpm dev:admin-spa # http://localhost:5173
pnpm dev:portal-spa # http://localhost:5174pnpm build:admin-spa && pnpm build:portal-spa
docker compose -f docker/docker-compose.yml up -d
pnpm test:e2e
docker compose -f docker/docker-compose.yml downEach app deploys to a configurable remote path via rsync over SSH. Default production paths:
DEPLOY_MAIN_SITE_PATH=/var/www/html
DEPLOY_ADMIN_SPA_PATH=/var/www/html/admin
DEPLOY_PORTAL_SPA_PATH=/var/www/html/portal
Staging paths are configured separately (see Required GitHub Secrets above).
Deployments create timestamped backups automatically. Roll back with:
bash scripts/rollback-spa.sh admin-spaSPAs support Vite's --mode flag for staging vs production. CI uses this automatically — staging branch builds with staging mode, main branch builds with production mode:
bash scripts/build-spa.sh admin-spa staging # loads .env.staging
bash scripts/build-spa.sh admin-spa production # loads .env.productionPreview what would be deployed without transferring files:
DRY_RUN=true bash scripts/deploy-spa.sh admin-spa| Script | Description |
|---|---|
pnpm lint |
Lint all TypeScript/React code |
pnpm typecheck |
Type-check all SPA apps and shared packages |
pnpm test |
Run all unit tests |
pnpm test:e2e |
Run Playwright E2E tests (requires Docker) |
pnpm build:admin-spa |
Build the admin SPA |
pnpm build:portal-spa |
Build the portal SPA |
pnpm build:main-site |
Validate main site structure |
pnpm dev:admin-spa |
Start admin SPA dev server |
pnpm dev:portal-spa |
Start portal SPA dev server |
pnpm deploy:main-site |
Deploy main site |
pnpm deploy:admin-spa |
Deploy admin SPA |
pnpm deploy:portal-spa |
Deploy portal SPA |
pnpm run-all |
Run full pipeline (lint → test → build → E2E) |
├── apps/
│ ├── main-site/ # Static HTML/CSS/JS
│ │ ├── index.html
│ │ ├── css/style.css
│ │ └── js/main.js
│ ├── admin-spa/ # Vite + React + TypeScript
│ └── portal-spa/ # Vite + React + TypeScript
├── packages/
│ ├── shared-ui/ # Shared React components
│ └── shared-utils/ # Shared utility functions
├── scripts/
│ ├── build-spa.sh # Build a SPA (supports --mode)
│ ├── build-main-site.sh # Validate main site structure
│ ├── deploy-spa.sh # Deploy SPA via rsync (auto-backup)
│ ├── deploy-main-site.sh # Deploy main site via rsync
│ ├── rollback-spa.sh # Rollback to previous deployment
│ ├── health-check.sh # Post-deploy verification
│ ├── ssh-setup.sh # Configure SSH in CI
│ ├── validate-required-env.sh
│ ├── changed-files.sh
│ └── run-all.sh # Full pipeline: single-command validation
├── docker/
│ ├── docker-compose.yml # nginx serving all apps
│ ├── docker-compose.ci.yml # CI-specific overrides
│ └── nginx/default.conf # Routing: /, /admin/, /portal/
├── e2e/
│ ├── playwright.config.ts
│ └── tests/
│ ├── main-site.spec.ts
│ ├── admin-spa.spec.ts
│ ├── portal-spa.spec.ts
│ └── cross-app-navigation.spec.ts
├── config/
│ └── deploy-targets.example.json
├── docs/
│ ├── ARCHITECTURE.md
│ ├── DEPLOY_FLOW.md
│ ├── GITHUB_SETUP.md
│ ├── GITLAB_SETUP.md
│ ├── ADDING_A_NEW_SPA.md
│ └── MULTI_REPO.md
├── .github/
│ ├── workflows/validate-and-deploy.yml
│ └── dependabot.yml
└── .gitlab-ci.yml
This monorepo approach works best when apps share code and teams. The same CI/CD patterns (deploy scripts, health checks, validation gates) also work when each app lives in its own repository. See docs/MULTI_REPO.md for details.
- Architecture — Why monorepo, change detection, package graph
- Deploy Flow — Full lifecycle from PR to production, rollback
- GitHub Setup — Secrets, branch protection, environments
- GitLab Setup — CI/CD variables, branch rules
- Adding a New SPA — Step-by-step checklist
- Multi-Repo Approach — Adapting patterns for separate repos
After pushing this repo to GitHub, set the repository topics and description for maximum discoverability. Run this once with the GitHub CLI:
# Set repository description and homepage
gh repo edit --description "Production-ready monorepo boilerplate: React 19 + TypeScript + Vite 6 + Playwright E2E + GitHub Actions CI/CD + Docker + staging/production deploy pipelines" --homepage "https://github.com/InkByteStudio/react-spa-monorepo-cicd"
# Set repository topics (used by GitHub search and Explore)
gh repo edit --add-topic react,typescript,vite,monorepo,cicd,github-actions,gitlab-ci,playwright,e2e-testing,docker,pnpm,boilerplate,template,devops,deployment,react-spa,nginx,rsync,staging,automated-deployment