startupbricks logo

Startupbricks

Performance Optimization for MVPs

Performance Optimization for MVPs

2025-01-16
6 min read
Technical Decision Making

Here's what most founders get wrong about performance:

"We'll optimize after we have users."

Let me share uncomfortable truth: You won't get users if you're slow.

Slow page load → 40% bounce rate. Slow API → frustrated developers abandon integration. Slow database → scaling issues before you grow.

Performance isn't optional. It's table stakes.

This guide shows you how to optimize performance for MVPs—without over-engineering or wasting time.


Performance Targets: What's "Fast Enough"?

Before optimizing, know what you're optimizing for.

Web Performance Targets

Metric

Good

Needs Work

Critical

First Contentful Paint (FCP)

<1.5s

1.5-2.5s

>2.5s

First Input Delay (FID)

<100ms

100-300ms

>300ms

Cumulative Layout Shift (CLS)

<0.1

0.1-0.25

>0.25

Time to Interactive (TTI)

<3.5s

3.5-7s

>7s

API Performance Targets

Metric

Good

Needs Work

Critical

P50 Response Time

<100ms

100-300ms

>300ms

P99 Response Time

<500ms

500-1000ms

>1000ms

Error Rate

<0.1%

0.1-1%

>1%

Startup Reality: For MVP, aim for "Good" in first column. "Excellent" is over-engineering at early stage.


Database Performance: Where Wins Come From

Most performance problems start in database.

1. Add Indexes (Biggest Win)

Before (Slow):

sql
-- 100,000 users
SELECT * FROM users WHERE email = '[email protected]';
-- Scans all 100,000 rows: 500ms

After (Fast):

sql
-- Add index
CREATE INDEX idx_users_email ON users(email);
-- Now jumps directly: 5ms

Performance Gain: 100x faster

Rule of Thumb: Index every foreign key and frequently queried column.


2. Use EXPLAIN to Find Slow Queries

sql
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = '[email protected]' AND status = 'active';

What to Look For:

  • Seq Scan: Scans entire table (bad for large tables)
  • Index Scan: Uses index (good)
  • High Cost: Query is expensive

If Seq Scan on large table: Add index or improve query.


3. Select Only Needed Columns

Bad (Slow):

sql
SELECT * FROM users WHERE id = 123;
-- Returns all 50 columns

Good (Fast):

sql
SELECT id, name, email FROM users WHERE id = 123;
-- Returns only 3 columns

Performance Gain: 2-5x faster, less data transfer


4. Use Pagination (Never Select All)

Bad (Memory Exhaustion):

sql
-- Tries to load 1,000,000 rows
SELECT * FROM orders;

Good (Efficient):

sql
-- Loads only 50 rows at a time
SELECT * FROM orders LIMIT 50 OFFSET 0;

Performance Gain: Prevents OOM errors, queries are always fast


5. Use Connection Pooling

Without Pooling (Slow):

javascript
// Creates new connection for every query
const result = await db.query("SELECT...");
connection.close();
// 100ms overhead per query

With Pooling (Fast):

javascript
const pool = new Pool({ max: 10, idleTimeoutMillis: 30000 });
const result = await pool.query("SELECT...");
// Connection reused, no overhead

Performance Gain: 2-3x faster, less database load


API Performance: Speed Without Overhead

1. Compress Responses

Without Compression:

javascript
// 1MB JSON response
return res.json({ data: largeObject });

With Compression:

javascript
import compression from "compression";
app.use(compression()); // Automatic gzip
// Now transfers ~100KB
return res.json({ data: largeObject });

Performance Gain: 10x less data transfer, faster downloads


2. Use HTTP/2

HTTP/2 multiplexes requests over single connection.

Implementation:

  • Vercel/Netlify: Automatic HTTP/2
  • Nginx: Add http2 on; directive
  • Node.js: Use spdy or built-in HTTP/2 (Node 15+)

Performance Gain: 20-50% faster for multiple concurrent requests


3. Cache Frequently Accessed Data

In-Memory Caching (Redis):

javascript
// Without cache: Every request hits database
const user = await db.query("SELECT * FROM users WHERE id = ?", [id]);
// With cache: First request hits DB, others from memory
let user = await redis.get(`user:${id}`);
if (!user) {
user = await db.query("SELECT * FROM users WHERE id = ?", [id]);
await redis.set(`user:${id}`, JSON.stringify(user), "EX", 3600);
}

Performance Gain: 50-100x faster for cached data


4. Use CDN for Static Assets

Serve static files from edge, not your servers.

Setup:

javascript
// Express example
app.use(
express.static("public", {
maxAge: "1y", // Cache for 1 year
etag: true, // Enable ETag
})
);

CDN Options:

  • Cloudflare: Free, easy setup
  • Vercel: Automatic CDN for deployments
  • AWS CloudFront: Paid but powerful

Performance Gain: 50-80% faster static asset loading


Frontend Performance: First Impressions Matter

1. Optimize Images (Huge Win)

Problem: Images are often 50-80% of page weight.

Solutions:

Compress Images:

bash
# Use modern formats
ffmpeg -i input.png -output.webp # 30-50% smaller
ffmpeg -i input.jpg -output.avif # 50-70% smaller

Lazy Loading:

html
<img
src="placeholder.jpg"
data-src="actual-image.jpg"
loading="lazy"
alt="Description"
/>

Responsive Images:

html
<picture>
<source srcset="image-small.webp" media="(max-width: 600px)" />
<source srcset="image-medium.webp" media="(max-width: 1200px)" />
<img src="image-large.jpg" alt="Description" />
</picture>

Performance Gain: 30-70% faster page loads


2. Minify and Bundle CSS/JS

Build Time Optimization:

javascript
// Vite (automatic minification)
export default defineConfig({
build: {
minify: "terser",
rollupOptions: {
output: {
manualChunks: {
vendor: ["react", "react-dom"],
},
},
},
},
});

Performance Gain: 30-50% smaller bundles, faster downloads


3. Use Code Splitting

Split code so users only load what they need.

javascript
// Lazy load routes
const Dashboard = lazy(() => import("./Dashboard"));
const Settings = lazy(() => import("./Settings"));
// Load on demand
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>;

Performance Gain: 40-60% faster initial page loads


4. Optimize Fonts

Problem: Web fonts block rendering while loading.

Solutions:

Font Display Strategy:

css
@font-face {
font-family: "CustomFont";
src: url("/font.woff2") format("woff2");
font-display: swap; /* Shows fallback immediately */
}

Preload Critical Fonts:

html
<link rel="preload" href="/font.woff2" as="font" crossorigin="anonymous" />

Performance Gain: Eliminates FOIT (Flash of Invisible Text)


5. Reduce Third-Party Scripts

Every third-party script adds delay.

Audit Scripts:

html
<!-- Remove or defer these if not critical -->
<script src="https://cdn.tracking.com/analytics.js"></script>
<script src="https://cdn.chat.com/widget.js"></script>

Strategies:

  • Remove: Unused scripts (old tracking, deprecated widgets)
  • Defer: Load after critical content
  • Lazy Load: Load only when needed (chat widgets, modals)

Performance Gain: 100-500ms faster initial render


Caching Strategy: Cache Smart, Not Everything

Caching Levels

1. Browser Caching (Free)

javascript
// Cache headers for static assets
app.use(
express.static("public", {
maxAge: "1y", // Cache for 1 year
etag: true, // Revalidate only if changed
})
);

2. Application Caching (Redis/Memcached)

javascript
// Cache expensive operations
const getExpensiveData = async (key) => {
let data = await redis.get(key);
if (!data) {
data = await expensiveOperation();
await redis.set(key, JSON.stringify(data), "EX", 3600); // 1 hour
}
return JSON.parse(data);
};

3. CDN Caching (Cloudflare/Vercel)

javascript
// Cache API responses
app.get("/api/data", async (req, res) => {
const data = await getData();
res.set("Cache-Control", "public, max-age=300"); // 5 minutes
res.json(data);
});

What to Cache vs What Not:

Cache

Don't Cache

Static assets (CSS, JS, images)

User-specific data

Expensive computations

Authenticated data

Search results (short-lived)

Payment transactions

Static assets (CSS, JS, images)

User-specific data

Expensive computations

Authenticated data

Search results (short-lived)

Payment transactions


Performance Monitoring: Measure to Improve

You can't optimize what you don't measure.

Essential Monitoring Tools

Web Vitals:

javascript
// Automatic Web Vitals measurement
import { onCLS, onFID, onFCP, onLCP, onTTFB } from "web-vitals";
onLCP((metric) => console.log(metric));
onFID((metric) => console.log(metric));
onCLS((metric) => console.log(metric));

APM Tools:

  • Sentry: Error tracking + performance
  • Datadog: Full-stack monitoring
  • New Relic: APM and infrastructure
  • Google Lighthouse CI: Automated performance tests

What to Track

Core Web Vitals:

  • First Contentful Paint (FCP)
  • Largest Contentful Paint (LCP)
  • First Input Delay (FID)
  • Cumulative Layout Shift (CLS)
  • Time to Interactive (TTI)

API Metrics:

  • Response time (p50, p95, p99)
  • Error rate by endpoint
  • Request throughput
  • Database query times

Performance Optimization Checklist

Use this before launching MVP.

Database

  • Indexes on foreign keys and frequent queries
  • Slow queries identified with EXPLAIN
  • Pagination implemented (no SELECT *)
  • Connection pooling configured
  • N+1 queries addressed
  • Caching strategy defined

API

  • Response compression enabled (gzip/brotli)
  • HTTP/2 enabled
  • Cache headers configured
  • Expensive operations cached
  • Rate limiting prevents abuse
  • Error responses don't leak info

Frontend

  • Images optimized (compressed, lazy loaded, responsive)
  • CSS/JS minified and bundled
  • Code splitting implemented
  • Fonts optimized (preload, font-display)
  • Third-party scripts minimized
  • Critical CSS inlined

Infrastructure

  • CDN configured for static assets
  • HTTP/2 enabled
  • SSL/TLS configured
  • Gzip/brotli compression enabled
  • Monitoring and alerting configured

Common Performance Mistakes

1. Premature Optimization

Mistake: "Let's optimize everything before launching"

Reality: You optimize things that don't matter, miss actual bottlenecks.

Fix: Measure first, then optimize. Focus on p95/p99, not averages.


2. Over-Caching

Mistake: "Cache everything for speed"

Reality: Stale data causes bugs. Cache invalidation is complex.

Fix: Cache carefully. Understand cache invalidation costs.


3. Ignoring Mobile Performance

Mistake: Optimizing only desktop

Reality: 50%+ of traffic is mobile. Mobile CPUs are slower.

Fix: Test and optimize for mobile. Use lightweight solutions.


4. Not Monitoring Production

Mistake: "Performance is good in dev"

Reality: Production is always slower. Users have slower networks.

Fix: Monitor real user performance in production.


5. Micro-Optimizations

Mistake: Saving 10ms in 10 different places

Reality: Total impact is 100ms, but complexity increased 10x.

Fix: Focus on biggest wins first. Low-hanging fruit > micro-optimizations.


Related Reading

If you found this helpful, you might also enjoy:


Need Help Optimizing Your MVP?

At Startupbricks, we've helped dozens of startups optimize performance from slow to fast. We know what bottlenecks to look for, what optimizations provide biggest wins, and how to implement without over-engineering.

Whether you need:

  • Performance audit and recommendations
  • Database optimization
  • API performance improvements
  • Frontend optimization

Let's talk about making your MVP fast.

Ready to optimize? Download our free Performance Checklist and start today.

Share: