Skip to content

CpServiceProvider::boot() sets Inertia rootView globally, breaking apps that run Inertia outside the CP #14619

@ragulka

Description

@ragulka

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

  1. 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.

  2. 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;
    });
  3. 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:

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions