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.
- Be clear about what it does.
Model::preventLazyLoading()throws aLazyLoadingViolationExceptionon any lazy-load attempt. Great in development; catastrophic if it returns a 500 to a real user on a path your tests never exercised. - Gate it by environment. In
AppServiceProvider::boot(), callModel::preventLazyLoading(! $this->app->isProduction()). That makes it strict everywhere except production, where throwing stays off. - 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. - It’s one of three strict settings. It’s bundled with
preventSilentlyDiscardingAttributesandpreventAccessingMissingAttributesunderModel::shouldBeStrict().shouldBeStrict()turns all three on at once; enable them deliberately, not blindly. The other two are lower-risk in production, butpreventLazyLoadingis the one that can throw on a real user request, so it earns the separate handler treatment. - 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.
- 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
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.