Skip to content
Muhammet Şafak
tr
Asked by: Ayşe Answered:

Should I enable Model::preventLazyLoading only locally or in production too?


Question

I'm building an API with Laravel 11 and I want to catch N+1 queries before they reach a release. `Model::preventLazyLoading()` looks like the right tool, but I can't decide exactly where to enable it. My worry is this: if I turn it on in production, I'm scared it will throw a fatal error (500) at a real user on some code path my tests missed. Should I only enable it locally, or is there a safe way to run it in production too?

Answer

Short answer: run it fully strict (throw) in local and CI; keep it on in production, but wire it to a handler that logs instead of throwing. Your worry is valid — flipping raw throwing on cold in prod turns a latent bug into an instant outage.

The trap here is treating this as a binary “local or prod” switch. The version that actually pays off is the same setting behaving differently per environment — loud where a developer can fix it, quiet where a real user would otherwise eat a 500.

  1. Be clear about what it does. Model::preventLazyLoading() throws a LazyLoadingViolationException on any lazy-load attempt. Great in development; catastrophic if it returns a 500 to a real user on a path your tests never exercised.
  2. Gate it by environment. In AppServiceProvider::boot(), call Model::preventLazyLoading(! $this->app->isProduction()). That makes it strict everywhere except production, where throwing stays off.
  3. In production, log via a handler instead of throwing. Use Model::handleLazyLoadingViolationUsing(...) to report the violation to Sentry/logs with the model + relation name, without crashing the request. You still see the N+1 in production telemetry, but you don’t punish the user.
  4. It’s one of three strict settings. It’s bundled with preventSilentlyDiscardingAttributes and preventAccessingMissingAttributes under Model::shouldBeStrict(). shouldBeStrict() turns all three on at once; enable them deliberately, not blindly. The other two are lower-risk in production, but preventLazyLoading is the one that can throw on a real user request, so it earns the separate handler treatment.
  5. It only catches lazy loads, not every N+1. A query running inside a loop that isn’t a relation lazy-load won’t trip this mechanism. So you still need Telescope/Clockwork or query-count assertions in your tests.
  6. Roll it out gradually. Keep production in log-only mode for a few weeks before switching to throwing; promote to throw only after the telemetry is clean, if at all.
// app/Providers/AppServiceProvider.php
public function boot(): void
{
    Model::preventLazyLoading(! $this->app->isProduction());

    Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
        report(new \RuntimeException(
            "Lazy loading detected: ".$model::class."::".$relation
        ));
        // no throw — the request continues its normal flow
    });
}

Bottom line: personally I’d go fully strict in local + CI, and in production run a log-only handler for the first weeks. Even once telemetry is clean, leaving production on log-only is enough for most teams: you see the N+1s there without hitting anyone with a 500. The real rule: never flip strict throwing on cold in production — measure first, then tighten.

Related Reading

Tags: #laravel#eloquent#performance
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

More Questions

All questions

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind