Best Practices
Production static site hosting - build optimization, performance, observability, cost, security
Best Practices
The platform does a lot for you, but real production still needs discipline. These habits keep your site fast, cheap, and secure.
Build Times Matter
Slow builds slow PRs. Watch:
| Practice | Effect |
|---|---|
| Lock files committed | Reproducible installs; cache works |
npm ci not npm install in CI | Faster, deterministic |
| Build cache enabled | Subsequent builds skip unchanged dependency installs |
| Code splitting | Smaller chunks; parallel build |
| Turborepo / Nx for monorepos | Only build what changed |
| Pre-built native modules | Avoid compilation during build |
For monorepos: per-app deploys, not "rebuild everything when any line changes anywhere."
Performance
Modern static hosting + frameworks make a fast site trivially achievable. Don't undo it:
| Habit | Why |
|---|---|
Use the framework's <Image> component | Auto-optimization |
| Lazy-load below-the-fold images | Smaller initial payload |
priority flag on hero / LCP image | Don't defer the critical render |
Set explicit width and height | Avoid layout shifts |
Preload critical fonts; use font-display: swap | Faster perceived render |
| Code-split routes | Don't ship admin code to logged-out users |
| Static export where possible | Edge CDN, no function invocation |
| Limit third-party scripts | Each one is a perf and privacy cost |
| Avoid client-side data fetching on every page | SSR or static where possible |
Measure with Lighthouse, PageSpeed Insights, and Real User Monitoring (Vercel Speed Insights, Cloudflare Web Analytics, your own RUM via Tracing).
Core Web Vitals: LCP < 2.5s, CLS < 0.1, INP < 200ms. Modern frameworks help; don't sabotage.
Bundle Size Discipline
JavaScript is the heaviest part of most modern sites:
@next/bundle-analyzeror platform equivalent — visualize what's in your bundle.- Big libraries are big wins to remove: moment.js → date-fns; lodash → small utilities; entire UI kit when you use 3 components → import per-component.
- Dynamic imports for heavy components (charts, maps, editors).
treeshake: trueis the default; verify with the analyzer.
The cheapest win is removing, not optimizing.
Observability
What to monitor:
| Signal | Why |
|---|---|
| Build success rate | Failed builds = blocked deploys |
| Build time | Trending up = inefficiency creep |
| Function invocations / cost | Catch runaway SSR loops |
| Function error rate | Edge / serverless errors |
| Bandwidth | Especially on bandwidth-billed platforms |
| Real User Monitoring (Web Vitals) | What real users experience |
| Page-load JS size | Trending up = code bloat |
Each platform ships analytics. For deeper analysis, integrate Sentry for errors, Cloudflare Web Analytics or Plausible for privacy-respecting metrics, Vercel Speed Insights if on Vercel.
Caching: Headers and CDN
Static assets should be cacheable forever (with hash in filename):
/_next/static/abc123.js → Cache-Control: public, max-age=31536000, immutable
/_next/static/def456.css → Cache-Control: public, max-age=31536000, immutableFrameworks do this automatically. Don't override unless you have a reason.
Page HTML (SSR / ISR):
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=86400- 0 in browser (always check).
- 300s shared CDN cache.
- Serve stale for 24h while revalidating in background.
See CDN for the deeper treatment.
Security Headers
A baseline _headers file (Netlify / Cloudflare Pages):
/*
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
X-Frame-Options: SAMEORIGIN
/admin/*
Cache-Control: no-store, no-cacheFor full Content-Security-Policy, use securityheaders.com to verify yours. CSP is finicky but worth it for production.
Cost Control
Watch:
| Cost axis | Mitigation |
|---|---|
| Bandwidth | Cloudflare Pages (free); cache aggressively; image optimization |
| Function invocations | Static where possible; cache responses |
| Build minutes | Faster builds; fewer triggered builds |
| Image transformations | Cache; limit infinite-variants |
| Team members | Pro tiers often per-seat; review usage |
For ~99% of small-to-mid teams: Cloudflare Pages free tier covers it. Vercel free tier covers many small projects too. You start paying when you hit Pro features (team, custom domains beyond N, advanced analytics).
Vercel Hobby tier prohibits commercial use — small companies on Hobby is a TOS violation. Use Pro or move to Cloudflare Pages.
Backups and Disaster Recovery
The platforms run your CDN; you don't back up the static assets (they rebuild from your repo). But:
- Your domain DNS is critical — see DNS.
- Account access — multi-admin, MFA, recovery email.
- Repo as source of truth — if the platform disappears, your code's in git.
- Database / object storage backups — separate concern; see relevant pages.
For mission-critical, practice deploying to a second platform (e.g., have a Netlify config ready alongside Vercel). Switching DNS takes a few minutes if you've practiced.
Common Pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
Secrets in NEXT_PUBLIC_* | Visible in browser bundle | Server-only env vars |
dynamic = 'force-dynamic' on every page | Every request hits a function | Audit; static where possible |
| Ginormous JS bundle | Slow page load | Bundle analyzer; split / remove |
await fetch() in getServerSideProps for static-able data | Per-request when ISR would do | Use generateStaticParams or revalidate |
Image without <Image> component | Unoptimized, no resize | Use the framework's Image |
| Preview deploys leak unfinished features publicly | Discovered by users | Lock previews behind auth |
| Forgot to set production env vars | Build fails or runs with placeholders | Verify before promoting |
| Building from a non-default branch | Deploys old code | Set production branch correctly |
| Slow build because no cache | Devs frustrated | Verify build cache is enabled |
| Missing security headers | Lower security score | _headers file with baseline |
When to Outgrow
You've outgrown a static host when:
- Serverless function costs dominate your bill — move to containers (Render, Fly, Railway, K8s).
- You need full Linux containers (heavy native deps, custom runtime).
- You're orchestrating backend services beyond simple API endpoints.
- Strict data residency they don't support.
- You want to own the infrastructure end-to-end.
For most teams: you'll stay on a static host for a long time. Vercel and Netlify customers reach IPO scale on these platforms.
Migration Strategies
Between platforms (e.g., Vercel → Cloudflare Pages):
- Set up the new platform alongside the existing one.
- Deploy to a preview URL on the new platform.
- Test thoroughly — adapter differences can surface bugs.
- Add new platform's domain as additional, then primary.
- Cut over DNS (low TTL beforehand).
- Keep old running for a week as a fallback.
- Decommission.
The biggest gotcha: framework-specific features that work differently per platform. Image optimization, ISR, middleware — verify each before cutting over.
Checklist
Production static site hosting checklist
- Production branch protected; deploys only from
main - Preview deploys behind auth if site contains anything sensitive
- Custom domain with HTTPS; HSTS preload submitted for prod
- Per-environment env vars; secrets encrypted; never in
NEXT_PUBLIC_* - Framework Image component for all
<img>-like uses - Security headers baseline (
_headersorvercel.json) - Real User Monitoring enabled
- Build cache enabled; build times measured
- Bundle size analyzed; major bloat removed
- Render modes intentional: static > ISR > SSR
- Rollback path tested
- Monitoring on function invocations + errors
- Cost dashboard reviewed monthly
- DNS TTL prepared for emergency cutover
- Account: MFA enforced; 2+ admins
- Documented "what to do if the platform is down" runbook