---
created: 2026-03-12
source: 50-Dollar-App
tags: [agent-archive, 50-dollar-app]
---

# 🔨 Performance Audit — 26 Feb 2026
## rateright.com.au Post-Build Metrics

**App:** Next.js 16.1.6 (Turbopack) | Single VPS (2 vCPU, 4GB RAM, syd1)  
**Memory:** 123MB resident (peak 183MB) — healthy for 4GB box  
**Uptime:** Stable since 00:23 AEDT, no OOMs

---

## 📊 Response Times (Server-Side, localhost → nginx → Next.js)

| Page | TTFB | Total | Size (gzip) | Status |
|------|------|-------|-------------|--------|
| `/` (landing) | 327ms* | 327ms | 10.9 KB | ✅ Good |
| `/auth/login` | 50ms | 50ms | 5.2 KB | ✅ Excellent |
| `/faq` (static) | 92ms | 92ms | 12.3 KB | ✅ Good |
| `/dashboard` | 43ms | 43ms | 307 redirect | ✅ Excellent |

*First request includes DNS resolution (285ms). Subsequent: ~42ms.

**Verdict:** Sub-100ms TTFB on all pages. Excellent for a VPS.

---

## 📦 Bundle Analysis

| Metric | Value | Rating |
|--------|-------|--------|
| Total JS chunks | 2.4 MB | ⚠️ Moderate |
| Largest chunk | 304 KB | ⚠️ Worth investigating |
| Second largest | 220 KB | ⚠️ Likely framework chunk |
| Third largest | 201 KB | ⚠️ Likely lib chunk |
| Dependencies | 30 prod, 12 dev | Normal |
| node_modules | 824 MB | Heavy but typical |

**Heavy client-side deps:** `@react-pdf/renderer`, `pdf-lib`, `lucide-react`, `date-fns`, `react-hot-toast`, `@supabase/supabase-js`

The 304KB chunk is likely `@react-pdf/renderer` — it's only used on `/worker/onboarding-pack`. This should be lazy-loaded.

---

## 🔒 Security Headers

| Header | Value | Rating |
|--------|-------|--------|
| HSTS | `max-age=63072000; includeSubDomains; preload` | ✅ A+ |
| X-Frame-Options | DENY | ✅ |
| X-Content-Type-Options | nosniff | ✅ |
| Referrer-Policy | strict-origin-when-cross-origin | ✅ |
| Permissions-Policy | camera=(), microphone=(self), geolocation=(self) | ✅ |
| CSP | Configured, allows Stripe iframes | ✅ |
| Server | nginx/1.24.0 (Ubuntu) | ⚠️ Leaks version |
| X-Powered-By | Next.js | ⚠️ Leaks framework |

---

## 🗜️ Compression

| Test | Result |
|------|--------|
| gzip enabled | ✅ Yes (via Next.js built-in) |
| nginx gzip config | ⚠️ Commented out in nginx.conf |
| Landing page compression | 78KB → 11KB (86% reduction) | 
| Static chunks | `Cache-Control: public, max-age=31536000, immutable` ✅ |

Gzip works because Next.js handles it internally. The commented-out nginx gzip lines don't matter for proxied requests — Next.js compresses responses itself. However, enabling nginx gzip would provide a fallback for static file serving.

---

## 🖼️ Image Optimization

| Issue | Details |
|-------|---------|
| `hero-image.png` | **868 KB** — large unoptimized PNG in `/public` |
| `icon-512.png` | 283 KB — oversized, not used by PWA manifest |
| `icon-192.png` | 42 KB — duplicate of `icon-192x192.png` (12 KB) |
| Logo PNGs | 112-120 KB each — 3 separate logo files |

**hero-image.png is not referenced in code** — appears to be dead. Should be deleted or converted to WebP and served via `<Image>`.

---

## 🐛 Runtime Errors (Last Hour)

```
Error: Failed to find Server Action "x". This request might be from an older or newer deployment.
```

**2 occurrences in 57 minutes.** This is the known Next.js cache mismatch issue — happens when clients have stale JS from a previous build. Harmless for new users at launch, but will spike after every deploy.

**Fix:** Add `stale-while-revalidate` headers or implement a "new version available" toast that prompts reload.

---

## 🎯 Optimization Recommendations

### Quick Wins (< 30 min each)

1. **Enable nginx gzip** — uncomment the gzip lines in `/etc/nginx/nginx.conf`. Provides compression fallback and handles static file compression at the proxy level.

2. **Remove `X-Powered-By` header** — Add `poweredByHeader: false` to `next.config.ts`. Reduces fingerprinting.

3. **Hide nginx version** — Add `server_tokens off;` to nginx config. Same reason.

4. **Delete dead images** — `hero-image.png` (868KB) and `icon-512.png` (283KB duplicate) aren't referenced. Save 1.1MB of public directory.

### Medium Effort (1-2 hours)

5. **Lazy-load react-pdf** — The `@react-pdf/renderer` chunk (~300KB) loads on every page but is only needed on `/worker/onboarding-pack`. Use `next/dynamic` with `ssr: false`:
```tsx
const PackDocument = dynamic(() => import('./PackDocument'), { ssr: false });
```

6. **Add notification polling** — Replace the disabled realtime with a 30-second `setInterval` fetch. Workers need to see hires without refreshing. (Also flagged as P1 in launch audit.)

7. **Convert logo PNGs to SVGs** — Three logo PNG variants (112-120KB each) when SVGs already exist. Use SVGs everywhere.

### Lower Priority (Post-Launch)

8. **Tree-shake lucide-react** — Import individual icons instead of the full set. Can reduce bundle by 20-50KB.

9. **Add `rel="preconnect"` for Supabase** — The app connects to `eciepjpcyfurbkfzekok.supabase.co` on every auth check. Preconnecting in `<head>` saves ~100ms on first load.

10. **Consider ISR for landing + FAQ** — Both pages are dynamic but content rarely changes. `revalidate: 3600` would serve cached HTML.

---

## 📈 Performance Score Summary

| Category | Score | Notes |
|----------|-------|-------|
| Server Response | 🟢 A | Sub-100ms TTFB |
| Compression | 🟢 A | 86% compression ratio |
| Caching | 🟢 A | Immutable static chunks, 1yr max-age |
| Security Headers | 🟢 A- | Full suite, minor info leaks |
| Bundle Size | 🟡 B | 2.4MB total, largest chunk 304KB |
| Image Optimization | 🟡 B- | Dead images, no WebP |
| Error Rate | 🟢 A | 2 known errors/hr (cache mismatch) |
| Memory | 🟢 A | 123MB / 4GB, stable |

**Overall: B+ — Production-ready.** The only real optimization that would be noticeable to users is lazy-loading react-pdf to reduce initial bundle on non-PDF pages.

---

*Audit complete. Build green. Ship it. 🔨*
