← Back to blog

Scaling Laravel SaaS Without Scaling Your Infrastructure Bill

1 May 2026 · 6 min read

There's a conversation I have surprisingly often with founders. It usually starts with something like: "Our app is getting slow, I think we need to upgrade the server." Sometimes that's true. More often, it isn't.

After 25 years of building web applications — and the last several deeply focused on Laravel SaaS products — I've learned that scaling infrastructure is the expensive, lazy answer. Scaling intelligently at the application layer is where the real wins are. And honestly, it's more satisfying too.

Let me walk you through how I actually approach this when a SaaS product starts groaning under load.

Start With Measurement, Not Assumptions

Before I change a single line of code, I reach for Laravel Telescope or Laravel Pulse to understand what's actually happening. I can't tell you how many times I've been convinced a bottleneck was in one place, only to find it somewhere completely different.

The usual suspects I look for:

  • N+1 queries — still the most common culprit I find in growing codebases
  • Slow or missing database indexes — especially on columns used in where, orderBy, or relationship lookups
  • Unoptimised eager loading — loading entire related models when you only need a couple of columns
  • Synchronous work that should be queued — sending emails, generating reports, calling third-party APIs inline

If you're not measuring, you're guessing. And guessing is expensive.

Eager Loading Is Not Enough — Be Selective

Most Laravel developers know to use with() to avoid N+1 queries. That's good. But I often see codebases that eagerly load entire relationships when they only need a fraction of the data.

Instead of this:

$users = User::with('subscription')->get();

Consider this:

$users = User::with('subscription:id,user_id,status,plan')->get();

Constraining eager loads to only the columns you actually need can make a meaningful difference, especially when your related models are wide tables with lots of columns. It sounds minor, but across thousands of requests it adds up.

Database Indexes Are Your Best Friend

I review indexes on every project I take on, and I almost always find at least one missing one. Laravel's query log and tools like Debugbar make it straightforward to spot slow queries.

A simple rule I follow: any column you filter or sort by regularly should have an index. Foreign keys in particular are often missed in early-stage apps built fast.

$table->index('status');
$table->index(['user_id', 'created_at']); // composite for common query patterns

Composite indexes are especially powerful when you have queries that consistently filter by two or more columns together.

Queue Everything That Doesn't Need to Be Synchronous

This one genuinely transforms the perceived performance of a SaaS app. If a user action triggers an email, a webhook, a PDF generation, or anything that touches a third-party service — queue it.

With Laravel's job batching and the reliability improvements in recent versions, there's really no excuse to be doing that work inline anymore. Your users don't care that the welcome email was sent in 0.2 seconds — they care that the page loaded fast and confirmed their action succeeded.

I use Laravel Horizon on most of my SaaS projects to manage and monitor queues. It's one of those tools that gives you instant visibility into what's running, what's failing, and where the backlog is sitting.

HTTP Caching and Response Caching

For SaaS dashboards and data-heavy pages, I lean heavily on a combination of server-side caching and thoughtful cache invalidation. The spatie/laravel-responsecache package has saved me countless cycles on public-facing pages.

For authenticated, user-specific data, I use Laravel's built-in cache with tagged cache entries so I can invalidate precisely what's changed rather than nuking everything:

Cache::tags(['user', "user-{$userId}"])->remember('dashboard-stats', 300, function () {
    return $this->buildDashboardStats();
});

The key discipline here is invalidating caches at the right time — when data changes, not on a timer. It takes a bit more thought upfront, but it means your users always see accurate data and your database breathes easier.

Livewire-Specific Wins

Since most of my SaaS frontends are built with Livewire v3, I've developed a few specific habits here:

Use wire:model.live sparingly. Real-time validation on every keystroke is great for UX, but it hammers your server with requests. I switch to wire:model.blur or wire:model.lazy unless there's a genuine reason for instant feedback.

Leverage lazy loading components. I wrote a whole post on this, but the short version is: if a Livewire component below the fold has expensive data loading, wire:init or lazy loading lets the page render fast and defers the heavy lifting.

Avoid heavy computations in render(). Anything expensive should be cached at the component level using Livewire's #[Computed] attribute, so it doesn't recalculate on every re-render.

When You Actually Do Need More Infrastructure

I'm not saying infrastructure upgrades are never the answer — sometimes they genuinely are. But I'd always exhaust application-level optimisations first. They're cheaper, they make your codebase better, and the improvements often compound over time.

When I do recommend scaling infrastructure, it's usually:

  • Horizontal scaling for queue workers during peak load periods
  • Read replicas for reporting-heavy workloads
  • A CDN in front of static assets (this one should be day one, honestly)

But even then, a well-optimised application on a modest server will outperform a bloated one on expensive hardware.

The Mindset Shift

The biggest thing I try to instil in the founders I work with is this: performance isn't a feature you add later. It's a habit you build into development from the start. Measurement, thoughtful querying, caching where it makes sense, and deferring work that doesn't need to happen immediately.

Getting these habits right early means you can focus on building features rather than firefighting slowness — and it means your infrastructure costs stay proportional to your actual growth, not your technical debt.

If your Laravel SaaS is starting to feel sluggish, have a proper look under the bonnet before reaching for a bigger server. Chances are the answer is already in your codebase.