C CoolAdmin v3.3.0

Deployment

CoolAdmin's deploy story is the easy kind — static HTML + CSS + JS, no Node runtime. Drop the repo on any host that serves static files. This page covers static hosts (Netlify, Vercel, Cloudflare Pages, GitHub Pages, S3), CDN configuration, and what to do about Bootstrap loaded from CDN.

Last updated May 22, 2026

CoolAdmin builds to plain static files — there’s no dist/ directory because the built HTML and CSS already ship at the repo root. Any host that serves static files works.

Two deployment models:

  • Use the repo as-is. The 35 root *.html files + css/ + js/ + vendor/ + images/ are everything you need. Upload them.
  • Run the build first, then deploy the result. If you’ve edited the Pug or SCSS source, run npm run build to regenerate the static artifacts. The output overwrites the files already in the repo root.

Either way, what you ship is a static directory.

What’s in the deploy

CoolAdmin/                       ← upload this
├── *.html                       (35 pages)
├── css/
│   ├── theme.css                (legacy)
│   ├── app.css                  (modern overlay)
│   └── font-face.css
├── js/
│   ├── vanilla-utils.js
│   ├── bootstrap5-init.js
│   ├── main-vanilla.js
│   └── modern-plugins.js
├── vendor/                      (Bootstrap, Chart.js, FullCalendar, Font Awesome)
└── images/

Things you can skip:

  • src/ — Pug + SCSS sources. Build artifacts live at the repo root; deploy doesn’t need them.
  • scripts/ — Node build scripts. Not used at runtime.
  • node_modules/ — local-only.
  • package.json / package-lock.json — only needed by contributors who run the build.
  • CLAUDE.md / CHANGELOG.md / RELEASE.md — repo docs, not runtime.

A clean deploy is around 3–4 MB total (most of which is vendor/ and images/).

Static hosts

Cloudflare Pages

# Connect a GitHub repo via the Cloudflare dashboard:
#   Build command:     (leave blank — repo is pre-built)
#   Build output dir:  /
#   Node version:      (not needed)

Or use Wrangler to deploy a local copy:

npx wrangler pages deploy . --project-name=cooladmin --branch=main

If you want the build to run on every push (so contributors don’t have to commit built artifacts), set:

  • Build command: npm install && npm run build
  • Build output directory: .

Cloudflare Pages will run the build server-side and serve the result.

Netlify

netlify.toml at the repo root:

[build]
  # If contributors commit build artifacts, leave command blank
  publish = "."

# If you want Netlify to build on push:
# [build]
#   command = "npm install && npm run build"
#   publish = "."

# Cache aggressive on vendor + css + js
[[headers]]
  for = "/vendor/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/css/*"
  [headers.values]
    Cache-Control = "public, max-age=86400, must-revalidate"

[[headers]]
  for = "/js/*"
  [headers.values]
    Cache-Control = "public, max-age=86400, must-revalidate"

[[headers]]
  for = "/*.html"
  [headers.values]
    Cache-Control = "public, max-age=0, must-revalidate"

Vercel

vercel.json:

{
  "buildCommand": null,
  "outputDirectory": ".",
  "headers": [
    {
      "source": "/vendor/(.*)",
      "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
    },
    {
      "source": "/(css|js)/(.*)",
      "headers": [{ "key": "Cache-Control", "value": "public, max-age=86400, must-revalidate" }]
    },
    {
      "source": "/(.*\\.html)",
      "headers": [{ "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }]
    }
  ]
}

Or via CLI: vercel --prod.

GitHub Pages

A .github/workflows/deploy.yml:

name: Deploy to Pages
on:
  push:
    branches: [master]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      pages: write
      id-token: write
    steps:
      - uses: actions/checkout@v4

      # Skip these two steps if you commit built artifacts directly
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci && npm run build

      - uses: actions/upload-pages-artifact@v3
        with: { path: . }
      - uses: actions/deploy-pages@v4

In repo settings, set Pages source to “GitHub Actions”. Pushes to master deploy.

S3 + CloudFront

# Sync everything except node_modules / src / scripts
aws s3 sync . s3://your-bucket/ --delete \
  --exclude "node_modules/*" \
  --exclude "src/*" \
  --exclude "scripts/*" \
  --exclude ".git/*" \
  --exclude "package*" \
  --cache-control "public, max-age=0, must-revalidate" \
  --include "*.html"

aws s3 sync . s3://your-bucket/ \
  --include "vendor/*" \
  --cache-control "public, max-age=31536000, immutable"

aws s3 sync . s3://your-bucket/ \
  --include "css/*" --include "js/*" \
  --cache-control "public, max-age=86400, must-revalidate"

Invalidate /*.html and /css/* and /js/* in CloudFront after each deploy.

Cloudflare R2

rclone sync . r2remote:bucket-name/ \
  --exclude "node_modules/**" \
  --exclude "src/**" \
  --exclude "scripts/**" \
  --exclude ".git/**" \
  --exclude "package*" \
  --progress

Wrap with a Worker or set up a custom domain for HTTPS + caching.

CDN dependencies

A few resources load from external CDNs by default. Decide whether to keep them or vendor them locally:

ResourceURLWhy it’s external
Poppins fontfonts.googleapis.comGoogle Fonts CDN — universally cached, no SRI hash on font files
Leaflet (map.html)unpkg.com/leaflet@1.9.4Vendored CSS would need its own asset folder; loading from CDN keeps the per-page tax low

If you’re shipping for offline use or a network-restricted environment, vendor them:

# Poppins — download woff2 files into vendor/fonts/
# Then update css/font-face.css to point to local paths

# Leaflet — already partially vendored in vendor/leaflet/ for some templates; otherwise:
npm install leaflet
cp -r node_modules/leaflet/dist vendor/leaflet
# Update map.html to reference vendor/leaflet/leaflet.js + leaflet.css

Everything else (Bootstrap, Chart.js, FullCalendar, Font Awesome) is already vendored in vendor/.

Cache strategy

Three tiers of cache headers map to three asset groups:

TierPatternCache-ControlWhy
Immutable/vendor/*public, max-age=31536000, immutableVendored libs are versioned by filename (chart.umd.js-4.5.1.min.js); they never change in place
Daily/css/*, /js/*public, max-age=86400, must-revalidateApp CSS/JS may change between deploys but don’t need second-fresh — daily revalidate strikes the balance
No-cache/*.htmlpublic, max-age=0, must-revalidateHTML changes most often + has to reference fresh CSS/JS hashes; always revalidate

The downside of “Daily” for app CSS/JS is that a user with a cached app.css might see ~24h of staleness after a deploy. Two ways to avoid that:

  1. Add a query string for cache-busting. Update <link href="css/app.css?v=3.3.0"> on each release. The query string changes the resource URL from the browser’s perspective.
  2. Move to filename hashing. Run the SCSS build with a hash in the output filename and template the link tag. More setup, but cleaner cache behavior. CoolAdmin doesn’t do this by default because the repo’s “no build for end users” promise would break.

For most deployments, the Daily tier is fine.

What you don’t need

A few things CoolAdmin 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 .htaccess or rewrite rules. Pages are served by filename (/inbox.html, not /inbox).
  • No service worker. CoolAdmin doesn’t ship a PWA manifest or service worker by default. Add one if you want offline support — see the <head> template in src/pug/partials/_head.pug for where to inject the link tag.

If you want pretty URLs (/inbox instead of /inbox.html), each host has its own way: Netlify uses redirects, Vercel uses cleanUrls: true, S3 uses Lambda@Edge, Cloudflare uses Workers. CoolAdmin doesn’t ship a specific config for any of them — the template is host-agnostic.

Quick checklist before deploy

# If you've edited source files:
npm run build              # rebuild HTML + CSS

# Verify it works locally:
python3 -m http.server 8000
# Open http://localhost:8000

# Check for broken Bootstrap dropdowns / charts / icons by visiting:
#   /             (operations dashboard)
#   /chart.html   (Chart.js sanity)
#   /calendar.html (FullCalendar sanity)
#   /modal.html   (Bootstrap interactivity sanity)

# Then upload everything except node_modules/src/scripts/.git

After deploy, smoke-test:

  • All 35 pages load (open a few at random)
  • Sidebar collapses + expands
  • ⌘K palette opens
  • Theme switcher applies a preset and survives a refresh
  • No 404s in network tab on any page
  • Charts render on chart.html
  • Calendar populates on calendar.html

See also