How to Keep Your SaaS Fast: Performance Basics for Non-DevOps Founders | Deployxa

Slow software feels broken, and users churn from sluggish products before they churn from missing features. The performance basics a non-DevOps founder actually needs.

← Back to Dispatch Articles
Engineering Log

How to Keep Your SaaS Fast: Performance Basics for Non-DevOps Founders

Slow software feels broken, and users churn from sluggish products before they churn from missing features. The performance basics a non-DevOps founder actually needs.

Nobody files a support ticket that says "your app feels 20 percent slower than last month." They just click less, abandon the signup form halfway, and renew reluctantly or not at all. Slow software feels broken, and users churn from sluggish products long before they churn from missing features. That is why performance is not a developer vanity metric — it is retention and conversion work. Every few hundred milliseconds you take off a screen your customers touch daily is time you gave back to them, and the compound effect shows up in activation, daily usage, and renewals.

The unfair part is how quietly speed decays. Your product was fast at launch because your tables were nearly empty. Ten months and a few thousand rows later, the same code is slower on exactly the pages customers use most — the dashboard, the search box, the invoice list. Almost none of that decay is mysterious. It comes from a handful of repeating causes: a missing database index, a query pattern that quietly multiplies, a list that loads every row, and work done inside the request that should happen after the response.

This guide covers the SaaS application performance basics for founders who do not have a DevOps hire: what "fast enough" means in numbers you can act on, eight fixes ordered by return on effort, a triage walkthrough for the next slow page a customer reports, and the deploy habits that keep speed from rotting. You need a browser, your application logs, and a few afternoons — not a monitoring purchase.

What "Fast Enough" Actually Means

"Fast" is not a feeling; it is a budget, and the budget differs by what the user is doing. The bands below are rules of thumb rather than SLAs, but they line up with how people actually experience software:

  • What the user is doing: API call a user waits on (save, search, filter) — How it feels: Instant — Where to aim: Under ~200 ms of server response time
  • What the user is doing: Page interaction after load (open a modal, switch tabs) — How it feels: Instant to barely noticeable — Where to aim: Under ~1 s end to end
  • What the user is doing: First full page load (dashboard, list view) — How it feels: Quick under ~2 s; questionable past ~3 s — Where to aim: Under ~2 s
  • What the user is doing: Anything longer (report, export, bulk update) — How it feels: Acceptable only if communicated — Where to aim: Respond now, process in the background

Two distinctions matter when you measure. First, server time is not total time: a 150 ms API response can still produce a two-second screen if the frontend downloads two megabytes of JavaScript. Second, perceived speed sometimes beats raw speed. A skeleton screen, an optimistic update, or an honest progress bar can make a 1.5-second action feel acceptable. What those tricks cannot do is fix anything: if the query behind the spinner takes five seconds, someone is still waiting five seconds — they just have something to look at. Use perceived-speed techniques to smooth the last few hundred milliseconds, not to hide full seconds.

One more honesty note: latency varies by distance and device. A customer on a laptop near your server region will see better numbers than one on a phone on a train. Measure from where your customers are, not just from your desk.

SaaS Application Performance Basics: Eight Fixes, Ordered by ROI

The checklist below is ordered by return on effort for a small SaaS. The first items are free to try and fix the majority of real-world slowness; the later ones matter more as traffic grows. Each item follows the same shape — the symptom you will notice, the fix, and how to verify it worked. Work top to bottom, and do not skip to caching.

1. Measure before you fix anything

Symptom: "The app feels slow." No data, three opinions, and fixes chosen by vibes. Teams that skip this step routinely spend a week optimizing the wrong thing.

Fix: Your browser's developer tools are the fastest performance tool you own. Open the Network tab, reload the slow page, and sort by time: you will see every request, how long each took, and — crucially — how much of that time was waiting for your server versus downloading content. For server-side numbers, log a duration line for every request (most frameworks have one-line middleware for this) and turn on your database's slow query log. Response-time headers, if your framework offers them, make the same numbers visible without any tooling. No APM purchase is required to do any of this.

Verify: You can name your three slowest endpoints with real numbers attached, and you can re-measure them the same way next week. That baseline is what turns every later fix from anecdote into proof.

2. Index the columns you filter and sort by

Symptom: A page that searches, filters, or sorts gets slower every month as data grows. Fine at 500 rows, painful at 50,000. This is the single most common fix on the list, and often the only one a young SaaS needs.

Fix: Every query that says "find customers where email equals X" or "list orders sorted by created date" scans your table row by row unless the database has an index on that column. An index is a lookup structure the database maintains so it can jump straight to matching rows. Add one on the columns you actually filter and sort by:

-- Fictional example: a login lookup that slowed down as customers grewSELECT id, email FROM customers WHERE email = '[email protected]'; -- The fix: an index on the filtered columnCREATE INDEX customers_email_idx ON customers (email); -- A dashboard query that filters by tenant and sorts by recency-- wants a composite index shaped like the queryCREATE INDEX invoices_tenant_created_idx ON invoices (tenant_id, created_at DESC);

To spot a missing index in a slow query, run it with EXPLAIN (PostgreSQL or MySQL) and look for a sequential scan (Seq Scan, or type: ALL in MySQL) plus a rows-examined count wildly larger than the rows returned.

Verify: Re-run the same EXPLAIN and see an index scan instead; re-measure the endpoint and compare against your baseline. One caveat: indexes are not free. They add a small cost to every insert and update, and a composite index only helps queries that use its leading column. Index the queries you actually run, not every column you own.

3. Kill N+1 query patterns

Symptom: A list page slows down in proportion to how many items it shows, even though each item's own query is fast. Your server log shows dozens of near-identical queries per request, differing only by an ID.

Fix: This is the classic N+1 pattern, in plain words: you run one query to fetch 50 invoices, then one more query per invoice to fetch each customer — 51 queries where 2 would do. Each query is fast; the accumulated round trips are not. The fix is to fetch related data in one batch: a single query with WHERE customer_id IN (...), a join, or your ORM's eager-loading option (select_related / prefetch_related in Django, include in Prisma, includes in Rails). Most ORMs default to lazy loading, which is exactly what produces this pattern, so it is worth auditing your top list pages once.

Verify: Count queries per request before and after — most ORMs can log them. A list endpoint running 51 queries should drop to 2 or 3, and the page time falls with it.

4. Paginate every list

Symptom: A list view that was fine at 500 rows times out at 50,000. Response payloads are measured in megabytes, and memory spikes whenever someone opens the "all customers" page.

Fix: Never return an unbounded list. Paginate server-side — limit and offset, or cursor-based pagination for large tables — and cap the maximum page size in the API even if your frontend does not paginate yet, because the next client to hit that endpoint will not be your frontend. For exports and bulk operations, do not try to paginate your way out: stream the data or push the job to the background (item 7).

Verify: Every list endpoint in your app has a hard maximum response size, and payload size no longer grows with the table. If deep pages get slow with offset pagination on a big table, that is the moment to switch to cursor pagination.

5. Trim the frontend: images, bundles, caching headers

Symptom: First loads are slow even though the API responds in 200 ms. The Network tab shows multi-megabyte images served at full resolution, one large JavaScript bundle, and static files re-downloaded on every visit.

Fix: Three moves that fit in a weekend. Compress and resize images to what is actually displayed, in a modern format such as WebP or AVIF. Split your bundle so each page ships the code it uses — most modern frameworks do this with configuration, not rewrites. Set caching headers (Cache-Control) on static assets served with hashed filenames, so repeat visits load them from the browser cache instead of your server.

Verify: The Network tab on a repeat visit shows static assets coming from cache, total payload on your key pages has dropped visibly, and no single image is larger than a few hundred kilobytes. Keep the effort proportionate, though: trimming 50 KB means nothing while an API call takes three seconds. This item is for pages where assets, not queries, dominate the waterfall.

6. Pool your database connections

Symptom: Latency spikes under load even though the queries themselves are fast, errors like "too many connections" during busy periods, and a sluggish first request after idle time.

Fix: Opening a fresh database connection for every request costs tens of milliseconds each and can exhaust the database's connection limit. Use a connection pool so a fixed set of connections is opened once and reused. Two settings matter: the pool's maximum size, and the multiplication — every app instance gets its own pool, so three instances with a pool of 20 are up to 60 connections against your database's cap. Background workers need pools too; count them in the math.

Verify: Under your normal peak, or a modest load test, no connection errors appear, the database's connection count stays flat instead of climbing, and response times hold steady rather than saw-toothing.

7. Move anything slower than a blink out of the request

Symptom: "Save" takes four seconds because it also sends a welcome email. Exports time out. When a third-party API has a bad day, your whole app feels slow because every request waits on it.

Fix: The rule is simple: respond now, process async. If work takes longer than a blink — email, PDF and CSV generation, report building, calls to slow external APIs — it does not belong inside the request. Enqueue a job, return immediately, and deliver the result out of band: an email with the export attached, or a status endpoint the UI can poll. Reserve the request path for the work the user is staring at. As a bonus, a job can retry safely when a third party fails, instead of failing the user's save.

Verify: No user-facing request depends on slow work — under normal conditions your slowest endpoint is either fast or honestly labeled. Jobs land in a queue you can inspect and drain, and retried jobs are idempotent: sending the same email twice is a bug your retry design should have caught.

8. Right-size the instance — vertical before horizontal

Symptom: CPU pinned or memory swapping during traffic peaks, latency rising everywhere at once, and an app that is fine for most of the day.

Fix: Before adding servers, make the one you have bigger. Vertical scaling — more CPU and RAM — is a configuration change, not an architecture project, and for most small-SaaS traffic it buys months. Two conditions come first. Confirm the app is actually resource-bound rather than query-bound, because scaling a missing index just runs the bad query faster. And confirm the database is not the real bottleneck — check its CPU while yours is pegged. Horizontal scaling, adding more instances, is the right move only when the app is stateless and the database has headroom; otherwise you have multiplied the pressure on the weakest component.

Verify: During peak, CPU sits under roughly 70–80 percent with no swapping, response times stay flat, and the database's own graphs look as calm as the app's.

A Slow-Page Triage Walkthrough

When a customer reports a slow page, resist the urge to change code immediately. Work the steps below in order. It takes fifteen minutes and finds the biggest cause first — which is usually the only cause that matters.

  1. Reproduce it yourself. Open the same page, ideally with an account holding a similar amount of data. Note exactly what you did: which page, which filter, which tab. If you cannot reproduce it, you are guessing, and guesses turn into redeploys that fix nothing. If only one customer sees it, suspect their data volume — that points at a missing per-tenant index or an N+1, not at the server.
  2. Read the network waterfall. Open the Network tab, reload, and sort by time. The biggest bar is your suspect. Check whether the time was spent waiting for the server (your app or database) or downloading content (assets — item 5). One slow API call points at items 2, 3, or 4; a dozen slow calls point at an N+1 or a chatty frontend.
  3. Check the server logs for slow queries. With the endpoint named, look at your request-duration logs and your database's slow query log. A query whose examined-to-returned ratio is in the tens of thousands is a missing index (item 2). A burst of near-identical queries is an N+1 (item 3). This step is why measurement came first on the checklist.
  4. Check external calls. If the endpoint calls a payment provider, an email service, or any third-party API, their latency is inside your request path until you move it. A provider having a slow day shows up as your app being slow. Tighten timeouts and move anything non-essential to a background job (item 7).
  5. Fix the biggest thing, then re-measure. Resist fixing three things at once — you will not know which one worked. Fix the largest cause, re-run the same measurement from step 1, and write the before-and-after down. If the page is still slow, the next-biggest bar is your next target.

The discipline matters more than the tools. Every step narrows the search space, and stopping to fix after step 2 beats a speculative afternoon of "optimizations" every time.

Performance Regressions Start at Deploys

Performance is not a project you finish; it is a property that decays without maintenance, and deploys are the most common cause of sudden decay. A new feature ships a new query with no index — fine on staging with a thousand rows, slow in production with a million. A dependency update changes an ORM default and quietly turns one query into an N+1. A "small" addition to a response doubles its payload. And even with no code change at all, data growth alone degrades unindexed queries month by month.

The countermeasure is cheap: add a timing check to your post-deploy routine. Keep a list of your five most important endpoints — the pages that carry revenue — and after every deploy, hit each one and compare response times against your baseline. Three minutes by hand, or a small script that requests each endpoint and prints the duration. A jump of 2x or more means investigate now, not next week — and it means rolling back is on the table while you diagnose. Rolling back a slow release is not an admission of failure; serving customers a release you know is slow is.

Caching Basics and the Invalidation Trap

Caching is the fix everyone reaches for and the one that should come last. In plain words: a cache stores the answer to an expensive question so the work is skipped next time. Done well, it is transformative. Done early, it adds a second copy of the truth with its own failure modes — and the classic failure is invalidation: knowing when the stored answer is stale. Cache a customer's dashboard and forget to clear it when their data changes, and you are either showing outdated invoices or, if the cache key is wrong, another customer's data — which is a security incident wearing a performance costume.

So the honest guidance: fix queries before you cache. Most apps that "need caching" actually have an unindexed query, an N+1, or an unpaginated list, and those are cheaper and safer to fix. When you have genuinely earned a cache, start with data that is expensive to compute, rarely changes, and is not per-user — a product catalog, a pricing page, app configuration. Cache per-user and per-tenant data last, key it explicitly by user or tenant ID, give every entry a TTL so stale answers expire even if you forget to clear them, and prefer short TTLs while confidence is low. And if the result can instead be precomputed by a background job — a summary table rebuilt every few minutes — that is often simpler than a cache, because the answer lives in one place: your database.

When to Worry About Scaling at All

Notice what was not on the checklist: read replicas, message buses, Kubernetes, microservices, a Redis cluster. For a small SaaS, the list above is performance work; scaling infrastructure is what you consider after the list is done and the graphs still look wrong. Every item on it is cheaper, faster to implement, and safer to operate than new infrastructure — and each one also raises the ceiling that infrastructure would buy you, because indexes and pagination help just as much at a hundred times the traffic.

The triggers for a real scaling conversation are specific: database CPU is the sustained bottleneck even though every query is indexed and every list is paginated; the background job backlog grows faster than it drains; resource use stays pinned at peak even on a right-sized instance. Until one of those is true, the disciplined move — the one your future team and your monthly bill will thank you for — is to measure, fix the query, right-size the instance, and get back to product work.

Where Deployxa Fits — and Where It Doesn't

Everything above is deliberately platform-agnostic, so it is fair to ask where a deployment platform changes the picture. On Deployxa, four pieces connect directly to this article.

Logs and health status per deployment. Application logs and health checks for each release are visible from the dashboard, so the triage steps that depend on request-duration logs and the post-deploy timing check do not require SSH or a log-piping setup you never got around to building.

Long-lived workloads for background jobs. Deployxa runs long-lived workloads, so the background worker from item 7 can run alongside your web app as a first-class part of the deployment rather than a cron-job-shaped workaround.

Right-sizing without a migration. Item 8 is a sizing decision, and on Deployxa it stays one: adjust the instance resources and redeploy, rather than moving hosting again.

Blue/green as a performance safety net. New releases go to a standby slot and are health-verified before traffic switches, and the prior release stays warm for a short rollback window — so a release that turns out slow-but-not-broken can be rolled back in under a second during that window while you fix the query that caused it.

And the honest limit: application performance is your code's responsibility. No platform can index your database, spot the N+1, or paginate the list — the milliseconds come from your queries and your request paths. A platform gives you the visibility, the rollback safety net, and the sizing knobs; the docs explain how those pieces work, but the checklist above is yours to run.

Your Performance Checklist

The whole article, compressed:

  • I can name my slowest endpoints, with numbers — server timing logged, database slow query log on
  • Columns I filter and sort by are indexed, and EXPLAIN shows index scans on my slow queries
  • Query counts per request are sane — no N+1 on my top list pages
  • Every list endpoint paginates with a server-side cap
  • Images are compressed, bundles are split, static assets have caching headers
  • Database connections are pooled, and instances × pool size stays under the database's limit
  • Email, exports, and reports run as background jobs, not in the request path
  • The instance is sized to real peak load, with CPU headroom at peak
  • My post-deploy routine includes a timing check on my five key endpoints
  • Caching exists only where it has been earned, with explicit keys and TTLs

Ten lines, a few afternoons, no new hires. That is the honest scope of performance work for most small SaaS products.

Run the Triage This Week

Pick your slowest real page — the one a customer has actually complained about, not the one that is easiest to fix — and run the five-step triage on it this week. Time it before you touch anything, fix the single biggest cause the walkthrough surfaces, and time it after. Write both numbers down; they become your baseline and your proof. If the cause turns out to be a missing index or an N+1, you are in good company — that is where most of these stories end, and the fix is usually an afternoon, not a quarter.

Ready to deploy with Deployxa?

Deploy your apps globally with automatic SSL and AI diagnostics.

Start Free Now