Skip to main content
HostwaresHostwares

Performance Optimization

Make your Hostwares sites fast — caching, CDN, image optimization, scaling, and observability.

Overview

Hostwares runs your containers on fast infrastructure with automatic TLS and global routing, but application-level choices dominate real-world latency. This guide covers framework-agnostic techniques to keep p50 low and p95 stable.

LeverImpactEffort
Cache headers & CDNHigh — cuts TTFB for repeat viewsLow — add headers
Image & asset optimizationHigh — reduces LCPLow — build-time compression
Runtime scalingMedium — smooths p95 under loadMedium — tune pack / concurrency
DB indexing & poolingHigh — fixes slow queriesMedium — analyze & migrate
ObservabilityEnabler — find the bottleneckLow — add logs & metrics

What to Measure

Track these before and after each optimization:

MetricWhat It Tells YouTarget
TTFBServer + network latency< 200 ms (cached), < 600 ms (dynamic)
LCPLargest contentful paint< 2.5 s
CLS / INPVisual stability & interactivityCLS < 0.1, INP < 200 ms
p95 latencyTail latency under loadWithin 2× p50
Error rate5xx / timeouts< 0.1%
# Quick checks
curl -w "TTFB: %{time_starttransfer}s  Total: %{time_total}s\n" -o /dev/null -s https://your-site.hostwares.app/

# Lighthouse CI (any framework)
npx lighthouse https://your-site.hostwares.app/ --only-categories=performance --view

# k6 load test
import http from 'k6/http';
export default function () { http.get('https://your-site.hostwares.app/'); }

Caching

Cache at the right layer — browser, CDN, and application — with explicit headers.

HTTP cache headers

# Static assets — immutable, long-lived
Cache-Control: public, max-age=31536000, immutable

# HTML / dynamic — revalidate often
Cache-Control: public, max-age=0, s-maxage=60, stale-while-revalidate=300

# Private / user-specific — never cache at edge
Cache-Control: private, no-store
// Framework-agnostic middleware example
function withCacheHeaders(res, type) {
  if (type === "static") {
    res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
  } else if (type === "html") {
    res.setHeader("Cache-Control", "public, max-age=0, s-maxage=60, stale-while-revalidate=300");
  } else if (type === "private") {
    res.setHeader("Cache-Control", "private, no-store");
  }
}

// Nginx
location ~* \.(js|css|png|jpg|woff2)$ {
  add_header Cache-Control "public, max-age=31536000, immutable";
}

Application cache

// In-memory (per container) — good for hot keys
const cache = new Map();
async function getUser(id) {
  if (cache.has(id)) return cache.get(id);
  const user = await db.users.findUnique({ where: { id } });
  cache.set(id, user);
  setTimeout(() => cache.delete(id), 60_000);
  return user;
}

// Shared — Redis / Upstash (works across containers)
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
await redis.set("user:" + id, JSON.stringify(user), "EX", 60);

CDN & Edge

Put a CDN in front of your Hostwares site to cache static assets and absorb traffic spikes. Any CDN works — Cloudflare, Fastly, CloudFront.

  • Point your domain to Hostwares, then proxy through your CDN (orange cloud / CDN enabled).
  • Cache static paths at the edge; bypass cache for /api/* and authenticated routes.
  • Use s-maxage and stale-while-revalidate to keep edge hits high without stale HTML.
# Cloudflare cache rule example (via dashboard or API)
# Cache static, bypass API
# Rule 1: (http.request.uri.path contains "/_next/static" or ends with .js/.css/.png)
#   -> Cache: Eligible, Edge TTL 1 month, Browser TTL 1 year
# Rule 2: (http.request.uri.path contains "/api/")
#   -> Cache: Bypass
Path PatternEdge BehaviorHeader to Set
/_next/static/*, *.js, *.cssCache 30dpublic, max-age=31536000, immutable
/images/*, /assets/*Cache 7dpublic, max-age=604800
/, /blog/*Cache 60s + SWR 300ss-maxage=60, stale-while-revalidate=300
/api/*, /dashboard/*Bypassprivate, no-store

Images & Assets

Images are usually the biggest LCP contributor — optimize at build and serve time.

  • Compress and resize at build — serve webp/avif with fallbacks.
  • Use responsive images (srcset / sizes) so mobile does not download desktop-sized files.
  • Lazy-load below-the-fold images; preload the LCP image.
  • Bundle-split and tree-shake — ship only JS the route needs.
<!-- Responsive, lazy, modern format -->
<img
  src="/images/hero-800.webp"
  srcset="/images/hero-400.webp 400w, /images/hero-800.webp 800w, /images/hero-1200.webp 1200w"
  sizes="(max-width: 640px) 100vw, 800px"
  width="800" height="450"
  loading="lazy" decoding="async"
  alt="Hero"
/>

<!-- Preload LCP image -->
<link rel="preload" as="image" href="/images/hero-800.webp" imagesrcset="/images/hero-800.webp 800w" />

# Build-time compression (any stack)
npx sharp -i ./public/images -o ./public/images --webp --avif
# or: squoosh, imageoptim, vite-imagetools

Runtime & Scaling

Hostwares runs each site in dedicated containers. Tune concurrency and resources to match your workload.

KnobWhen to AdjustHow
Pack size (CPU/RAM)OOM, high p95, build timeoutsDashboard → Site → Settings → Resources
Concurrency / workersCPU-bound or high-throughput APIsWEB_CONCURRENCY / cluster mode
Health checkSlow cold startsIncrease timeout; add /api/health
Keep-aliveMany short requestsEnable HTTP keep-alive in server
# Node: cluster via WEB_CONCURRENCY
# Procfile / start command
web: node --max-old-space-size=1536 server.js
# Set WEB_CONCURRENCY=2..4 on larger packs

# Python: gunicorn workers
gunicorn app.wsgi --workers 3 --threads 2 --timeout 30

# Generic: respect PORT and graceful shutdown
const port = process.env.PORT || 3000;
process.on("SIGTERM", () => server.close(() => process.exit(0)));

For sustained traffic growth, increase the pack before adding app-level caching — memory pressure causes GC pauses that hurt tail latency more than average latency.

Database

A slow query will dominate any edge optimization. Fix the DB first.

  • Index frequently filtered/sorted columns; use EXPLAIN ANALYZE to verify.
  • Use connection pooling — direct connections exhaust quickly under concurrency.
  • Paginate large result sets; avoid SELECT * over wide tables.
-- Find slow queries
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 'u_123' ORDER BY created_at DESC LIMIT 20;

-- Add index if missing
CREATE INDEX CONCURRENTLY idx_orders_user_created ON orders(user_id, created_at DESC);

-- Pooled connection (Pgbouncer)
DATABASE_URL=postgresql://user:pass@host:6543/db?pgbouncer=true

-- App: always limit
-- const orders = await db.orders.findMany({ where: { userId }, orderBy: { createdAt: "desc" }, take: 20 });

Observability

Use logs and metrics to prove where time is spent.

  • Deployment logs — check build time and image size (large images slow cold starts).
  • Runtime logs — add structured request logging with duration.
  • Resource metrics — watch memory and CPU in Site → Metrics; alert on sustained > 80%.
// Structured request log (any framework)
app.use((req, res, next) => {
  const start = Date.now();
  res.on("finish", () => {
    console.log(JSON.stringify({
      method: req.method,
      path: req.path,
      status: res.statusCode,
      durationMs: Date.now() - start,
    }));
  });
  next();
});

// Query logs via AI
// "Show me the slowest requests in the last hour for my-site"
// "Are there any 5xx errors after the last deploy?"

Performance Checklist

  • ✅ Static assets have immutable cache headers and are on a CDN
  • ✅ HTML uses s-maxage + stale-while-revalidate, not no-store globally
  • ✅ Images are compressed, responsive, and lazy-loaded (LCP preloaded)
  • ✅ Pack sized for workload; no recurring OOM or CPU throttling
  • ✅ DB has correct indexes and uses pooled connections
  • ✅ Request logging enabled; p95 and error rate monitored
  • ✅ Rollback tested — can revert a bad deploy in under a minute