A simple yet fully functional frontend deployment management system, with the following features:
- Deploy frontend assets (
.tar/.tar.gz/.tar.xz) via web interface - Auto-incrementing version directories (001, 002, ...)
- Header-based version routing (
x-env-version) for parallel testing - Symlink-based version switching
While cloud services are commonly used for frontend deployments (especially for SPAs where index.html is served through rendering services and assets are hosted on CDNs), there are scenarios where a single Nginx server suffices for lower-traffic applications.
This solution addresses two key challenges:
- Manual deployment processes are cumbersome
- Testing parallel features is difficult when only one version is available (merging all feature branches mixes changes, whereas isolated feature testing is often preferable)
- Node.js >= 22.6 (uses TypeScript type stripping)
- Nginx (or use the built-in
simulate-nginxfor local development)
.
├── backend/ # Koa server (TypeScript, runs directly via node)
├── frontend/ # Management UI (React 19 + antd 6 + Vite 7)
├── conf/ # Configuration templates & Dockerfile
│ ├── Dockerfile # Multi-stage Docker build
│ ├── nginx.conf # Example Nginx config
│ └── site.db.json # Site config template
└── simulate-nginx/ # Koa-based Nginx simulator for local dev
The backend reads from an external data directory (set via DIR env variable):
$DIR/
├── site.db.json # Site configuration (site-key → display-name)
├── site_dist/ # Versioned assets per site
│ └── <site-key>/ # e.g. sitea/001, sitea/002, sitea/current -> 002
└── upload_temp/ # Temporary upload storage
Create the data directory and copy the config template:
# Example: DIR=/Users/aaa/frontendmanagement
mkdir -p $DIR
cp conf/site.db.json $DIR/site.db.json
site_dist/andupload_temp/subdirectories are created automatically by the backend on startup.
Edit $DIR/site.db.json to define your deployment targets. Each key is a site identifier (lowercase letters only, used as the directory name under $DIR/site_dist/), and the value is a display name.
{
"sitea": "Site A",
"siteb": "Site B"
}The Nginx root path should point to $DIR/site_dist/<site-key>/$asset_env_version.
See conf/nginx.conf for a full example. The key parts:
Add a map directive to the http block:
map $http_x_env_version $asset_env_version {
default "current";
"~^[a-z]+$" $http_x_env_version;
}Set the root in each server/location block:
root /path/to/data/site_dist/sitea/$asset_env_version;If you don't have Nginx, use the
simulate-nginxdirectory to run a Koa-based static file server that emulates the same header-routing behavior.
# Install dependencies
cd frontend && npm install
cd ../backend && npm install
# Build frontend
cd ../frontend && npm run build
# Start backend (DIR and PORT are required as env variables)
cd ../backend && DIR=/path/to/data PORT=3000 npm startFor frontend development with hot reload:
cd frontend && npm run devThe Vite dev server proxies
/frontend-publish-management/apirequests tohttp://localhost:3000.
Visit http://localhost:3000/frontend-publish-management
Prepare your frontend assets tarball:
tar -cvf a.tar -C ./dist .
# or
tar -czvf a.tar.gz -C ./dist .- Select a site from the dropdown
- Upload a
.tar/.tar.gz/.tar.xzfile - Use "Publish/Unpublish" to create/remove symlinks (e.g., link
current→003)
All files must be at the root of the archive. Avoid having a nested folder (e.g.,
dist/) inside the archive.
- Upload multiple versions
- Create a named symlink (e.g.,
featurea→002) - Visit the site with header
x-env-version: featurea - Use browser extensions like ModHeader for header injection
- Runtime: Node.js with native TypeScript type stripping (no build step)
- Framework: Koa 3 + @koa/router
- Config:
$DIR/site.db.json(site-key → display-name; asset paths derived as$DIR/site_dist/<key>) - APIs: upload tarball, create/remove symlinks, list versions, inspect files
- Static serving: serves
frontend/distunder the/frontend-publish-managementprefix
- Stack: React 19 + antd 6 + TanStack React Query + Vite 7
- Features: site selector, tarball upload, version list, symlink management, config editor
- Maps the
x-env-versionrequest header to a subdirectory name - Falls back to
currentwhen the header is absent or invalid - Serves static assets from the resolved version directory
$DIR/site_dist/sitea/
├── 001/ # version 001 (uploaded assets)
├── 002/ # version 002
├── 003/ # version 003
├── current -> 003 # symlink: default version
└── featurea -> 002 # symlink: feature branch version
- Uploading a tarball extracts it into the next auto-incremented directory
- Creating a symlink (e.g.,
current→003) switches the served version - Nginx resolves
x-env-version: featureato serve fromfeaturea/→002/ - Without the header, Nginx serves from
current/→003/
- No authentication/authorization
- No error tracking or operation auditing
- No deployment locking (concurrent uploads may conflict)
- No auto-restart (use PM2 or systemd in production)