Before you reach for Octane, Redis or a bigger server, take one real journey through your slow Laravel customer portal and measure where the wait time actually accumulates. Split it across browser and network, Laravel code, database, cache, queues, and external services. Fix the largest evidenced bottleneck first. Eager loading, suitable indexes, targeted caching, and background jobs solve different problems. Octane only belongs on the shortlist when measurements show that request bootstrap or throughput is the bottleneck and the application has been audited for long-lived workers.
- Compare each change under the same conditions and monitor errors, queue delay, and resource saturation as well as latency.
- Treat rollback, cache invalidation, and worker reloads as part of the release.
Measure a real journey, not the framework
“The portal is slow” is not a diagnosis. A customer may mean sign-in, an operations user may mean search, management may mean a report, and support may mean an intermittent timeout. Those journeys can have entirely different bottlenecks inside the same Laravel application.
Define a testable journey first, for example: “An authenticated customer opens their case list, filters by status, and loads the detail view.” Record data volume, permission profile, browser, network conditions, and application version. Then measure latency as a distribution rather than an average: median, p95 and p99 answer different questions. Add error rate, database time, query count and shape, external service wait time, queue delay, and the responsiveness visible in the browser.
Important: A quick test against an empty database says little about a real customer journey. Use representative, privacy-safe data volumes and reproducible conditions. Production data must not be copied into a test environment without proper controls.
Laravel provides official starting points. DB::listen invokes a listener for every executed SQL query, DB::whenQueryingForLongerThan fires when cumulative query time within one request exceeds a threshold, and a scheduled db:monitor run dispatches a DatabaseBusy event when the permitted open-connection count is exceeded, which a separate listener has to turn into a notification.
Laravel Pulse surfaces slow requests and queries, queue throughput and exceptions; Telescope captures individual requests in detail and must be protected outside local environments through the viewTelescope gate, because query bindings may contain sensitive values; production capture needs filtering, access controls, and bounded retention.
Check the database first: query shape, data volume, and indexes
In data-heavy portals the largest lever often lies in fewer or better database operations rather than a faster PHP worker. Ask three questions first:
- Does a list load the same relationships separately for every item?
- Does the application retrieve more columns or rows than the current step needs?
- Can the database execute filtering, ordering, and joins through a suitable access path?
An N+1 problem occurs when an initial query loads a collection and more queries then run for each item. Eloquent eager loading can load the required relationships in a bounded number of queries, but it is not a reason to preload every possible relationship: excessive eager loading increases data volume, memory use, and object work.
In development and test environments, preventLazyLoading exposes unexpected lazy loading, and the current Laravel documentation marks automatic eager loading as beta. For a critical portal path an explicit loading decision is easier to control.
An index helps only when it matches the actual filtering, ordering, and joins; one added on instinct increases write work and storage without improving the problematic query.
MySQL and PostgreSQL document EXPLAIN and EXPLAIN ANALYZE. Both actually execute the supported statement under EXPLAIN ANALYZE, so for writes that check belongs in a controlled environment; PostgreSQL explicitly demonstrates wrapping a data-modifying statement in a transaction and rolling it back. The key question is not whether an index exists, but whether the database chooses a suitable plan for the relevant data distribution.
Pagination is a product choice with a technical effect: paginate also retrieves a total result count, simplePaginate avoids that count query, and cursor pagination can be more efficient for large, suitably indexed and uniquely ordered data sets but provides no page numbers. If users genuinely need “page 37 of 812,” the count has business value.
Cache only with an owner, validity rule, and invalidation path
Cache suits expensive results that are read more often than they change and may be reused for a defined period. Laravel provides Cache::remember; Cache::flexible implements stale-while-revalidate, returning an older result during a second time window while recalculation is deferred until after the response. Whether that is acceptable depends on the domain: a nearly current report may tolerate stale data, a recently changed permission may not. Every cache therefore needs four explicit answers:
- Which business and authorization dimensions belong in the key, such as tenant, user, language, or relevant filters?
- How stale may the result become in business terms?
- Which event makes it invalid, and which code path owns invalidation?
- What happens with a cold cache or an unavailable cache backend?
Security boundary: A cache key with insufficient scope can mix data between tenants or permission levels. Scope and authorization belong in the design and its tests, not in a later optimization pass.
Queues and external services need a time budget
An interactive request should contain only the work required for the user's next visible decision. Report generation and broad synchronizations are candidates for background jobs, which shortens the request and increases operational responsibility. A production-ready job needs a unique business identity, bounded retries, timeouts, failure handling, and observability. A retry must not create a duplicate booking, message or state transition in an external system, and for database changes it must be clear whether the job may run only after a successful commit.
A scheduled queue:monitor run dispatches a QueueBusy event when the job count of a monitored queue exceeds the configured threshold; a separate listener turns that into a notification. For the user experience, though, queue length alone matters less than time to the business result. Define a queue-delay budget per job class and watch for old, repeatedly failing, and permanently running jobs.
Laravel's HTTP Client supports explicit connection and response timeouts as well as controlled retries. Do not rely on defaults in a business-critical journey. Decide per dependency how long the user may wait, which failures are safe to retry and whether a fallback is correct in business terms.
A retry around a safe read is different from a write: if a timeout leaves it unclear whether the remote system performed an operation, the process needs an idempotency key. For cross-system workflows, our page on automation and integration explains how time budgets, idempotency, monitoring, and operational ownership fit together.
Frontend and deployment
A short server response does not guarantee a fast portal. Large JSON responses, blocking JavaScript, incorrectly sized media or too much first-render work delay visible interaction. If several components fetch the same data again after the first render, the bottleneck may sit in frontend data architecture rather than the Laravel controller.
Core Web Vitals are field metrics: a Lighthouse score is not evidence that a Laravel backend is fast or that real users pass them. DUNA's documented Lighthouse method shows how to qualify a dated lab result.
Laravel recommends php artisan optimize for production deployments to cache configuration, events, routes and views. After config:cache the environment file is not loaded during requests or Artisan commands, so env() belongs only in configuration files; a wrong assumption here turns an apparent optimization into a production failure.
If PHP OPcache timestamp validation is disabled, the release procedure must explicitly invalidate or reset OPcache, or restart the web server, before new code takes effect. Long-running processes must match the new version: Laravel 13 documents php artisan reload, which terminates queue, Reverb, and Octane processes so that a correctly configured process monitor restarts them with the new code.
A new migration, a changed cache format, or jobs already dispatched can make the old version incompatible. Decide before release which states both versions can read and which change will be enabled only after a successful observation period.
When Octane fits and when it does not
Laravel Octane starts the application once and keeps it in memory for subsequent requests; the currently documented server options include FrankenPHP, Open Swoole, Swoole and RoadRunner. That can reduce bootstrap work, but it repairs no inefficient SQL query, no slow third party and no oversized browser payload. The long-lived process model also changes assumptions, and Laravel's documentation explicitly warns about stale request or container state and possible memory leaks.
Put Octane on the shortlist only when profiling shows that, after database, API and application improvements, a relevant share remains in bootstrap or worker processing. Before adoption: load tests, a review of stateful components, worker restart limits, monitoring and a tested route back.
From signal to lever
| Observation | Required evidence | First lever | Main risk | Verification |
|---|---|---|---|---|
| High server time without a database or API share | Request profile, logs, and traces for the concrete journey | Remove unnecessary work in Laravel code | Refactoring without an evidenced share of total time | Compare server time before and after the change |
| Many similar relationship queries | Query trace shows N+1 in the relevant journey | Targeted eager loading or a different query shape | Excess data and higher memory use | Compare query count, data volume, and latency |
| Slow filtering or ordering | Execution plan and representative data volume | Design query and suitable index together | Extra write cost or an unsuitable plan with another distribution | Check plan, runtime, and write effect |
| Expensive stable read | Repeated computation with a clear validity period | Targeted cache | Stale or incorrectly scoped data | Test hit rate, invalidation, permissions, and failure |
| Request waits for side work | Profile shows separable non-interactive work | Idempotent background job | Duplicate effects, queue delay, or hidden failures | Check end-to-end time, failures, retries, and duplicates |
| Variable external API | Dependency dominates a trace or timeout | Time budget, bounded retry, cache, or decoupling | Retry storm or a fallback that is invalid for the business process | Test failure scenarios and idempotency |
| Server fast, browser slow | Network and main-thread profile | Improve payload, JavaScript, and rendering path | Functional regression or displaced work | Lab test plus field data after release |
| Bootstrap dominates after other fixes | Profile and load test show a reproducible share | Octane pilot | State leak, memory growth, operational overhead | Load, isolation, memory, and rollback tests |
| Regression only after releases, or stale workers | Release markers in metrics and logs, process version | Deterministic build and controlled reload | Old code or old configuration stays active | Check process version, configuration and route cache |
Every intervention needs a testable hypothesis. “We will add Redis” is not one. “The permission overview repeats the same expensive aggregation per tenant, so a tenant-scoped cache with event-driven invalidation should reduce database time for this journey” is. A faster request that returns stale permissions or creates duplicate records is not an improvement.
Named proof, with a clear boundary: the published entsorgo case study demonstrates Laravel delivery and a step-by-step rollout without interrupting daily operations. It is not a published performance optimization case and proves no response-time, query, cache, throughput or saving result.
Frequently asked questions about Laravel performance
Why is my Laravel customer portal slow?
The cause may sit in database queries, Laravel code, cache, queues, external APIs, network or frontend work. Measure one concrete journey and split its wait time across those layers, then fix the largest evidenced bottleneck.
Does Laravel Octane make every portal faster?
No. Octane moves the limit at request bootstrap and throughput. It repairs no inefficient query, no slow external API and no heavy browser payload, so it only pays off once the profile puts the bottleneck exactly there and the application has been audited for long-lived workers.
How do I prevent N+1 queries in Laravel?
Capture the queries for the affected journey and eagerly load exactly the Eloquent relationships it needs, then compare query count, data volume, memory use and response time. Preloading every relationship creates unnecessary work of its own.