Excellent question and you’re absolutely right to want to keep firstOrFail(), because it’s idiomatic and clean in Laravel. The “hack” using first() works but gives up the elegant exception handling built into the framework.
Let’s solve this properly, and also in a global way so that every theme’s custom 404.blade.php is automatically used for missing models, missing pages, etc.
When firstOrFail() (or any findOrFail(), ModelNotFoundException, etc.) fails, Laravel throws a ModelNotFoundException. You can catch that globally in your app/Exceptions/Handler.php.
So instead of manually redirecting to /404, let’s intercept that exception and render the correct 404 view based on the currently active theme.
Step 1: Edit app/Exceptions/Handler.php Open app/Exceptions/Handler.php and add a custom handler for ModelNotFoundException (and optionally for NotFoundHttpException too).
use Illuminate\Database\Eloquent\ModelNotFoundException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use App\Models\Settings;
public function render($request, Throwable $exception) { // Handle model not found if ($exception instanceof ModelNotFoundException || $exception instanceof NotFoundHttpException) {
// Get the active theme
$settings = Settings::first();
$theme_directory = $settings->theme_directory ?? 'default';
// Build the path to the custom 404 view
$theme_404_view = 'themes.' . $theme_directory . '.templates.404';
// Check if that view exists
if (view()->exists($theme_404_view)) {
return response()->view($theme_404_view, [
'theme_directory' => $theme_directory,
'site_name' => $settings->site_name ?? config('app.name'),
], 404);
}
// Fallback to Laravel's default 404 page
return response()->view('errors.404', [], 404);
}
return parent::render($request, $exception);
}
Step 2: Keep your controller clean Now you can safely write:
$article = Article::visible()->where('slug', $slug)->firstOrFail();
return view( 'themes/' . $this->theme_directory . '/templates/article', array_merge($this->data, [ 'article' => $article ]) );
If the article doesn’t exist, the ModelNotFoundException is thrown, automatically caught by your global handler, and the correct theme-specific 404.blade.php is rendered.
No redirect('/404') needed.
Step 3 (Optional): Make it even more flexible If you want to handle all 404s (including missing routes, pages, etc.), also register your theme 404 logic in a fallback route in routes/web.php:
Route::fallback(function () { $settings = App\Models\Settings::first(); $theme_directory = $settings->theme_directory ?? 'default'; $theme_404_view = 'themes.' . $theme_directory . '.templates.404';
if (view()->exists($theme_404_view)) {
return response()->view($theme_404_view, [], 404);
}
return response()->view('errors.404', [], 404);
});
This ensures even unhandled routes will show your theme’s 404.
@muhalifanhar Thanks for the answer, I will test it. But please notice that there is some code displayed as text. Please fix that. Thanks!
Sign in to participate in this thread!
The Laravel portal for problem solving, knowledge sharing and community building.