Three.js From Zero · Article s14-05
Deploying Three.js Apps
Deploying Three.js Apps is Article s14-05 of Three.js From Zero, a MasterAllArts free interactive lesson for artists learning creative 3D on the web.
Season 14 · Article 05 · Setup & Tooling
Three.js apps deploy like any static site, but the asset choices change everything: a 30MB scene vs a 3MB scene is the difference between a viral demo and an instant bounce. Hosts, cache headers, CDN strategy.
Hosts compared
| Host | Free tier | Why pick it |
|---|---|---|
| Cloudflare Pages | Unlimited bandwidth | Big .glb files welcome. Generous limits, fast global CDN. |
| Vercel | 100GB/month | Best DX for Next.js + React. Edge functions if you need server logic. |
| GitHub Pages | Unlimited | Zero setup if your code is already on GitHub. No build step needed. |
| Netlify | 100GB/month | Forms, identity, simple redirects without a config file. |
For Three.js specifically, Cloudflare Pages wins for asset-heavy apps. No bandwidth caps means you can ship a 50MB scene and not worry. For React/Next.js projects with server-rendered parts, Vercel's edge integration is cleaner.
Cache headers — the big win
Your .glb, .hdr, .ktx2 files never change. Tell the browser:
# public/_headers (Cloudflare Pages syntax)
/*.glb
Cache-Control: public, max-age=31536000, immutable
/*.hdr
Cache-Control: public, max-age=31536000, immutable
/*.ktx2
Cache-Control: public, max-age=31536000, immutable
immutable means the browser doesn't even check for updates — it serves from cache forever. Your repeat visitors load 0KB of assets. Combined with content-hashed filenames (Vite's default: model-a8d3f.glb), this is safe.
Compression — the bigger win
Before deploying, optimize your assets:
# Compress glTF with Draco + Meshopt
npx gltf-transform optimize input.glb output.glb
# 30MB → 3MB typical
# Convert textures to KTX2 with BasisU
npx gltf-transform ktx2 model.glb out.glb
# 4096² PNG → 1MB KTX2
# Strip unused channels
npx gltf-transform metalrough model.glb out.glb
# packs metallic+roughness into one texture
Three commands take a 30MB tourist trap of a model down to 3MB without visible quality loss. The Three.js side needs DRACOLoader + KTX2Loader configured — covered in S6-02.
Build for production
npm run build
# Vite outputs to dist/
# Preview locally
npm run preview
Always test the production build locally before deploying. Dev mode tolerates slow imports; production has different module resolution. Verify your scene actually renders in npm run preview.
One-command deploys
# Cloudflare Pages
npx wrangler pages deploy dist
# Vercel
npx vercel --prod
# Netlify
npx netlify deploy --prod --dir=dist
# GitHub Pages (via GitHub Actions, no manual deploy)
# Push to main → workflow runs npm run build → publishes dist/
Custom domain
All four hosts: add domain in their dashboard → set DNS CNAME to their target. Propagation 5–60 minutes. Free HTTPS via Let's Encrypt, automatic.
Common first-time pitfalls
/ assume root. Vite handles this; for plain HTML, use relative paths or set the <base> tag.Access-Control-Allow-Origin: *.Exercises
- Lighthouse audit. Run Lighthouse on your deployed site. Target Performance 90+. Fix the biggest items (LCP, CLS).
- Compress one model. Pick your biggest .glb. Run gltf-transform on it. Compare load times before/after on Slow 3G throttling.
- Set immutable cache. Add _headers / vercel.json. Verify in DevTools Network tab: response Cache-Control header has "immutable".
UP NEXT
S14-06 — Debugging with Spector.js → The Three.js debugger.