This document covers load testing for the /api/og share card generation endpoint and client-side html2canvas benchmarking.
The test script lives in load-tests/og-load-test.js and uses autocannon.
Install the dependency:
npm install -g autocannon
# or run without installing:
npx autocannon <url># Start the dev server (or target a staging URL)
pnpm dev
# In a separate terminal:
BASE_URL=http://localhost:3000 node load-tests/og-load-test.js
# Against a deployed environment:
BASE_URL=https://your-staging-url.vercel.app node load-tests/og-load-test.js| Scenario | Concurrent connections | Duration |
|---|---|---|
| Light | 10 | 15 s |
| Moderate | 50 | 15 s |
| Heavy | 100 | 15 s |
| Metric | Target |
|---|---|
| p95 response time | < 2 000 ms |
| Error rate | < 1 % |
| Timeout rate | < 0.5 % |
| Bottleneck | Root cause | Mitigation |
|---|---|---|
| Cold start latency | Vercel Edge Function first-request spin-up (~200–400 ms) | Keep-alive via scheduled synthetic pings; upgrade to a warm-tier plan |
| Font loading | @vercel/og fetches fonts on each cold start |
Pre-load fonts as module-level ArrayBuffer constants in route.tsx |
| Archetype image fetch | fetch(baseUrl + archetypeImagePath) adds RTT per request |
Cache fetched image buffers in module scope (survives across warm invocations) |
| Canvas encoding | btoa on large images is CPU-bound |
Use Buffer.from(buf).toString('base64') (faster) or reduce image resolution |
The endpoint already sets:
Cache-Control: public, s-maxage=86400, stale-while-revalidate=604800
This allows Vercel's CDN (and any downstream CDN) to cache OG images for 24 hours and serve stale content for up to 7 days while revalidating. For high-traffic deployments, add a CDN prefix cache key on the full query string to maximise hit rate.
- Open Chrome DevTools → Performance tab.
- Click the CPU throttle dropdown and select 4× slowdown (simulates a mid-range phone).
- Start recording, trigger share card generation on
/share, stop recording. - Look for the
html2canvastask in the flame graph.
| Device profile | Target render time |
|---|---|
| Desktop (no throttle) | < 800 ms |
| Mid-range phone (4× throttle) | < 3 000 ms |
| Low-end phone (6× throttle) | < 5 000 ms |
| Bottleneck | Mitigation |
|---|---|
3× scale factor (scale: 3 in html2canvas config) |
Reduce to scale: 2 for mobile user agents |
| Complex CSS gradients & blur filters | Simplify backdrop-filter usage in the card DOM subtree |
| Web font loading at render time | Pre-warm fonts with document.fonts.load() before calling html2canvas |
| Large DOM subtree | Render a lightweight shadow clone element instead of the full page |
Animated pages, wallet libraries, and Stellar SDK can significantly increase frontend bundle size. Bundle size tracking helps catch regressions and identify optimization opportunities.
Run the bundle analyzer locally to inspect your bundle composition:
pnpm analyzeThis will:
- Build the Next.js application with bundle analysis enabled
- Generate interactive HTML reports in
.next/analyze/ - Automatically open the reports in your browser
The bundle size tracking workflow (.github/workflows/bundle-size.yml) automatically:
- Runs on every pull request and push to main/master/develop branches
- Builds the application with bundle analysis enabled
- Uploads bundle analysis reports as artifacts (retained for 30 days)
- Comments on pull requests with bundle size statistics
-
Download the artifact from the GitHub Actions run
-
Open the HTML files in
.next/analyze/directory:client.html- Client-side JavaScript bundlesnodejs.html- Server-side Node.js bundlesedge.html- Edge runtime bundles (if applicable)
-
Analyze the treemap visualization:
- Larger rectangles represent larger modules
- Color intensity often indicates module size
- Click on modules to drill down into dependencies
Current baseline targets (to be refined after initial analysis):
| Bundle Type | Target Size | Rationale |
|---|---|---|
| Main JS bundle | < 500 KB | Ensures fast initial load on 3G connections |
| Total page weight | < 2 MB | Includes all JS, CSS, and assets |
| Individual chunks | < 200 KB | Prevents large lazy-loaded chunks |
| Issue | Common Cause | Solution |
|---|---|---|
| Large main bundle | Heavy dependencies (Stellar SDK, motion libraries) | Code splitting, dynamic imports, tree shaking |
| Duplicate dependencies | Multiple versions of same package | Deduplicate via pnpm, update to compatible versions |
| Large node_modules in bundle | Bundling server-only code on client | Use next.config.ts to exclude server-only packages |
| Unoptimized images | Large asset files | Use Next.js Image component, optimize assets |
The CI workflow will comment on PRs with bundle size changes. Review these comments to ensure:
- No significant bundle size increases without justification
- New dependencies are properly code-split
- Lazy-loaded chunks remain reasonably sized
- Run
pnpm analyzelocally and review the report - Check that new features use dynamic imports for heavy dependencies
- Verify that tree-shaking is working (no unused code in bundles)
- Ensure bundle size increase is justified by feature value
| Limit | Value | Rationale |
|---|---|---|
| Max concurrent OG requests per deployment | ~200 rps | Vercel Edge plan limit; above this, add request queuing |
| OG image cache TTL | 24 h (s-maxage=86400) |
Sufficient freshness for persona data that changes monthly |
html2canvas scale on mobile |
2 |
Balances quality and speed on mid-range devices |
| Client-side generation timeout | 10 s | Show error state if canvas encoding exceeds this threshold |