Deployment
Build Adminator for production with one command. The output is plain static files — drop them on any host that serves HTML. This page covers production builds, static hosts (Netlify, Vercel, GitHub Pages, S3, Cloudflare Pages), and CDN configuration.
Last updated May 21, 2026
Adminator builds to plain static files — dist/ contains HTML, JS, CSS, fonts, and images. No Node runtime, no serverless functions, no edge config. Any host that serves static files works.
Building for production
Two flavors:
# Minified — what you'd actually deploy
npm run release:minified
# Unminified — useful for debugging the production output
npm run release:unminified
Both write to dist/. The minified build is ~700 KB of JS (across 5 chunks: runtime.js, 2026.js, vendor-chartjs.js, vendor-fullcalendar.js, vendors.js) plus ~90 KB of CSS.
The unminified build produces the same chunks without minification — useful when you want to inspect what was emitted or trace a bundling issue.
What’s in dist/
After npm run release:minified:
dist/
├── index.html # Dashboard
├── email.html / calendar.html / charts.html / ... # 18 HTML pages
├── 2026.js # Main bundle (custom code)
├── runtime.js # Webpack runtime
├── vendor-chartjs.js # Chart.js + registerables
├── vendor-fullcalendar.js # FullCalendar + 4 plugins
├── vendors.js # Other npm deps (jsvectormap, etc.)
├── style.css # All SCSS compiled and concatenated
└── assets/
├── fonts/ # Self-hosted webfonts (optional)
└── img/ # Page screenshots, icons, logo
Every page links the same JS chunks and the same style.css. The HTML is the only per-page artifact.
Deploying to static hosts
Netlify
Drag-and-drop deploy: build locally with npm run release:minified, then drag dist/ into Netlify’s deploy zone.
Continuous deploy from a git repo — add a netlify.toml at the project root:
[build]
command = "npm run release:minified"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
# SPA-style fallback isn't needed — every page is a real HTML file.
# But you may want clean URLs (e.g. /email instead of /email.html):
[[redirects]]
from = "/email"
to = "/email.html"
status = 200
Vercel
Create a vercel.json:
{
"buildCommand": "npm run release:minified",
"outputDirectory": "dist",
"framework": null
}
Or use the CLI: vercel --prod.
Cloudflare Pages
Build settings in the dashboard:
- Build command:
npm run release:minified - Build output directory:
dist - Node version: 20
For preview deploys on every PR, point Cloudflare at your GitHub repo and the integration handles the rest.
GitHub Pages
GitHub Pages serves the contents of a branch or a /docs folder. The cleanest path is a GitHub Actions workflow:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [master]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm run release:minified
- uses: actions/upload-pages-artifact@v3
with: { path: dist }
- uses: actions/deploy-pages@v4
In repo settings, set Pages source to “GitHub Actions”. Pushes to master deploy.
S3 + CloudFront
npm run release:minified
aws s3 sync dist/ s3://your-bucket/ --delete \
--cache-control "public, max-age=31536000, immutable" \
--exclude "*.html"
aws s3 sync dist/ s3://your-bucket/ \
--cache-control "public, max-age=0, must-revalidate" \
--include "*.html"
The two-pass sync sets long cache for hashed assets (JS/CSS/images) and no-cache for HTML — so users get the latest HTML, which in turn references the latest hashed JS/CSS.
In CloudFront, invalidate /index.html, /email.html, etc., after every deploy (or use the /*.html pattern).
Cloudflare R2
Like S3 but with no egress fees. Use rclone for syncing:
npm run release:minified
rclone sync dist/ r2remote:bucket-name/ --progress
Behind a Cloudflare Worker or a custom domain, R2 serves static files at zero egress cost.
NPM publishing
If you maintain a fork of Adminator and want to publish your version to npm, the package already ships both src/ and a pre-built dist/:
# Update version in package.json first
npm run lint # must pass 0/0
npm run build # produces dist/
npm publish
The prepublishOnly script runs lint + build automatically — npm publish won’t go out with a failing lint or stale build.
Consumers install with:
npm install your-adminator-fork
And import the pre-built CSS + JS from node_modules/your-adminator-fork/dist/ — handy when shipping a Rails / Django / WordPress integration.
CDN configuration
A few headers worth setting:
# JS / CSS / fonts / images — hashed, safe to cache forever
Cache-Control: public, max-age=31536000, immutable
# HTML — must revalidate
Cache-Control: public, max-age=0, must-revalidate
# Compress everything
Content-Encoding: gzip (or br for Brotli)
Brotli compression typically shaves another ~15% off the gzipped size. Most modern CDNs (Cloudflare, Vercel, Netlify, CloudFront) do this automatically.
What you don’t need
A few things the template deliberately doesn’t require:
- No server-side rendering — pages are static HTML
- No client-side routing — navigation is real
<a href="...">links - No build-step environment variables — the template has no API URLs to inject; configuration lives in your own code
- No
dist/.htaccessor rewrite rules — clean URLs are optional, not required
If you do want clean URLs (/email instead of /email.html), each host has its own way: Netlify uses redirects, Vercel uses cleanUrls: true, S3 uses a Lambda@Edge function, Cloudflare uses Workers. Adminator doesn’t ship a specific config for any of them — the template itself is host-agnostic.
Quick checklist before deploy
npm run lint # 0 errors, 0 warnings
npm run release:minified # builds to dist/
ls -la dist/ # sanity check the output
du -sh dist/ # ~3 MB total (with fonts and screenshots)
If lint fails, fix it before deploying — both ESLint and Stylelint catch real production issues (unused CSS rules, undefined variables, accessibility regressions).