A Laravel application that feels quick in development can slow down badly once real users, real data and real integrations arrive. In our experience the cause is almost always the same: too much work happens while the user waits. Queues, caching and scheduled tasks fix that, if they are used with care. This article covers the habits we rely on in production.
Move slow work out of the request
A web request should do the minimum needed to respond. Anything that talks to another service, generates a file or sends many messages can usually run in the background. Good candidates include:
- Sending emails, SMS and WhatsApp messages.
- Generating PDFs, invoices and exports.
- Calling payment, shipping or tax APIs where the user does not need the answer immediately.
- Resizing images and processing uploads.
- Syncing data to search or reporting tables.
Laravel's queue system dispatches a job, stores it in the configured backend, and lets a worker process run it. The user gets a fast response and the work completes moments later.
Write jobs that are safe to retry
Jobs fail. Networks drop, third-party APIs time out, and workers restart during deployments. Assume every job may run more than once, and design for it.
Make jobs idempotent
Running a job twice should not charge a customer twice or send the same message twice. Check whether the work has already been done before doing it, store a unique reference for external calls, and use database constraints as a final safety net.
Set timeouts, tries and backoff
Give each job a sensible time limit, a limited number of attempts and an increasing delay between retries. A job that retries forever against a broken service only adds load.
public int $tries = 3;
public int $timeout = 60;
public function backoff(): array
{
return [30, 120, 600];
}
Pass identifiers, not whole models
Jobs should carry the minimum data they need. Laravel serialises Eloquent models by identifier and reloads them when the job runs, which keeps payloads small and data fresh. For long delays, check inside the job that the record still exists and is still in the expected state.
Separate queues by importance
Do not put password reset emails behind a ten-thousand-message marketing campaign. Use separate queues for urgent, default and bulk work, and run workers that favour the urgent queue. A process manager such as Supervisor keeps workers running, and a graceful restart after each deployment makes sure they load the new code.
Cache what is read often and changes rarely
Caching is the second lever. Settings, navigation, public pages, expensive report queries and permissions lookups are classic candidates. Use Cache::remember() with a clear key and a sensible lifetime.
Plan invalidation first
The hard part is not storing a value, it is knowing when it is wrong. Clear or update the cache in the same code path that changes the data, or use short lifetimes for data where slight staleness is acceptable. Use cache tags or well-named key prefixes so you can clear a group of values together.
Never cache per-user data under a shared key
In multi-tenant and multi-user systems, include the tenant or user identifier in the key. A missing prefix is one of the easiest ways to show one customer another customer's data.
Use a backend suited to the job
File and database caches are fine for small sites. For busier applications, Redis offers fast reads, atomic locks and a good queue backend. Our Narmar Express Shipping Platform uses Redis alongside Laravel and Inertia for this kind of workload.
Use the scheduler for recurring work
Laravel's scheduler replaces a pile of cron entries with one command that runs every minute and decides what is due. Use it for report generation, cleanup, reminders and syncing. Prevent overlapping runs of long tasks, and run heavy scheduled jobs on a single server when you scale horizontally.
Watch what you cannot see
Background work fails silently unless you look for it. After launch, set up:
- Alerts for failed jobs, using the failed jobs table and a notification to your team.
- Queue length and wait-time monitoring, so growing backlogs are noticed early.
- Logging with request or job identifiers, so a problem can be traced end to end.
- A regular review of slow queries, since caching cannot rescue a missing database index.
Do not skip the basics
Before adding more infrastructure, fix the obvious. Eager load relationships to avoid repeated queries, add indexes for the columns you filter and sort by, and paginate large lists. These changes often deliver more than a new cache layer.
If your Laravel application is slowing down as it grows, our engineers can review it and propose targeted fixes. See our services or get in touch with the Narmar team.



