Summary
Statamic\Providers\CpServiceProvider::boot() calls Inertia::setRootView('statamic::layout') at application boot:
https://github.com/statamic/cms/blob/v6.0.0/src/Providers/CpServiceProvider.php#L29
This sets the root view globally, regardless of whether the request will ever touch the CP. Apps that run their own Inertia frontend (or Inertia in a non-CP namespace) inherit this default. The app's own HandleInertiaRequests middleware can override it during normal request handling, but the override doesn't always survive into the exception-handling path — Inertia error responses then try to render inside Statamic's CP layout and crash on Statamic\Preferences\Preferences::preferences() because the authenticated user has no Statamic CP context.
Reproduction
-
Laravel app with Statamic installed for one section (e.g. marketing pages) and a custom Inertia app for another (e.g. /app/*) on the same host.
-
Define a Laravel exception handler that renders friendly Inertia error pages, e.g.:
$exceptions->respond(function (Response $response, Throwable $e, Request $request) {
if (str_starts_with($request->path(), 'app')) {
return Inertia::render("Errors/{$response->getStatusCode()}")
->toResponse($request)
->setStatusCode($response->getStatusCode());
}
return $response;
});
-
Trigger any 404 inside /app/* (e.g. route-model binding miss).
Expected
The Inertia error page renders inside the app's root view (app.blade.php).
Actual
Inertia\Response::toResponse() resolves the rootView from the singleton — statamic::layout, set at boot. The error page renders inside Statamic's CP layout, which loads partials/head.blade.php, which calls Statamic\CP\Color::cssVariables() → Statamic\Preferences\Preferences::get('theme') → mergeDottedUserPreferences() → throws:
BadMethodCallException / null member access:
Call to a member function preferences() on null
at Statamic\Preferences\Preferences.php:90
Because the user is authenticated in the host app, not in Statamic.
Workaround
Pin the root view explicitly in the exception handler before rendering:
Inertia::setRootView('app');
return Inertia::render("Errors/{$status}")->toResponse($request);
Works, but feels like reaching past Statamic.
Suggestion
Two options that would remove the surprise:
- Move the
setRootView() call out of boot() and into the CP HandleInertiaRequests middleware (which already extends \Inertia\Middleware with $rootView = 'statamic::layout'). The middleware-level setting only applies on CP routes; the global default stays at Inertia's 'app'. This matches how applications using Inertia normally configure things and removes the global side effect.
- If keeping the global default is intentional, document it in the CP/customization docs with the fix above. The behavior is fully understandable once you know about it, but the failure mode (a crash deep in
Preferences.php from a 404 in unrelated routes) makes it hard to track down.
Related: #14167 describes a similar shape of problem (Statamic Antlers error templates rendered without their middleware-supplied Cascade).
Environment
- Statamic CMS: v6.0.0
- Laravel: v12
- inertiajs/inertia-laravel: v2
- Setup: Laravel + Statamic CMS on same host, custom Inertia frontend at
/app/*, Statamic CP at /cp/*, Statamic frontend on rest of the site.
Summary
Statamic\Providers\CpServiceProvider::boot()callsInertia::setRootView('statamic::layout')at application boot:https://github.com/statamic/cms/blob/v6.0.0/src/Providers/CpServiceProvider.php#L29
This sets the root view globally, regardless of whether the request will ever touch the CP. Apps that run their own Inertia frontend (or Inertia in a non-CP namespace) inherit this default. The app's own
HandleInertiaRequestsmiddleware can override it during normal request handling, but the override doesn't always survive into the exception-handling path — Inertia error responses then try to render inside Statamic's CP layout and crash onStatamic\Preferences\Preferences::preferences()because the authenticated user has no Statamic CP context.Reproduction
Laravel app with Statamic installed for one section (e.g. marketing pages) and a custom Inertia app for another (e.g.
/app/*) on the same host.Define a Laravel exception handler that renders friendly Inertia error pages, e.g.:
Trigger any 404 inside
/app/*(e.g. route-model binding miss).Expected
The Inertia error page renders inside the app's root view (
app.blade.php).Actual
Inertia\Response::toResponse()resolves the rootView from the singleton —statamic::layout, set at boot. The error page renders inside Statamic's CP layout, which loadspartials/head.blade.php, which callsStatamic\CP\Color::cssVariables()→Statamic\Preferences\Preferences::get('theme')→mergeDottedUserPreferences()→ throws:Because the user is authenticated in the host app, not in Statamic.
Workaround
Pin the root view explicitly in the exception handler before rendering:
Works, but feels like reaching past Statamic.
Suggestion
Two options that would remove the surprise:
setRootView()call out ofboot()and into the CPHandleInertiaRequestsmiddleware (which already extends\Inertia\Middlewarewith$rootView = 'statamic::layout'). The middleware-level setting only applies on CP routes; the global default stays at Inertia's'app'. This matches how applications using Inertia normally configure things and removes the global side effect.Preferences.phpfrom a 404 in unrelated routes) makes it hard to track down.Related: #14167 describes a similar shape of problem (Statamic Antlers error templates rendered without their middleware-supplied Cascade).
Environment
/app/*, Statamic CP at/cp/*, Statamic frontend on rest of the site.