Nitro

Features

Handling errors

When a component fails, in production, it shows a fallback in its place and the rest of the page works on. The error goes to Laravel's exception handler. In development, you see the error itself.

On this page

A page is made of many components, and one of them failing shouldn't take the others down. A bank feed that is down, a view with a bug, a #[Server] call that throws: in production, Nitro shows that component's fallback, reports the error as Laravel reports any other, and leaves every other component working. You don't have to do anything for this. A component without a fallback of its own shows a short message with a link that loads the page again.

A component's own fallback

Give a component a fallback() method to choose what shows in its place. It runs on the server, and receives the exception:

app/Nitro/Components/BankBalance.php Server
<?php

namespace App\Nitro\Components;

use App\Support\BankFeed;
use Illuminate\Contracts\View\View;
use Nitro\Attributes\Locked;
use Nitro\Component;
use Throwable;

class BankBalance extends Component
{
    #[Locked]
    public int $balanceCents = 0;

    /** The bank's API: when it is down, this throws. */
    public function mount(BankFeed $feed): void
    {
        $this->balanceCents = $feed->today()['balance_cents'];
    }

    /** Shown in the component's place when it fails. */
    public function fallback(Throwable $exception): View
    {
        return view('partials.bank-unavailable');
    }
}
  • Optional. Without fallback(), the component gets your app's fallback view, or Nitro's message.
  • Its parameter is optional too. Take Throwable $exception when the fallback depends on what went wrong; leave it out when it doesn't.
  • It shows the visitor something safe. Never put the exception's message in the fallback: it can hold paths, queries or keys. The message goes to your logs.
  • If it throws, that error is reported too, and the app's fallback view or Nitro's message shows instead.

fallback() can return a view, an Htmlable, or text:

PHP Server
public function fallback(): string
{
    return 'The balance is not available right now.';   // Text: escaped.
}

public function fallback(): View
{
    return view('partials.bank-unavailable');          // A view: rendered.
}

public function fallback(): HtmlString
{
    return new HtmlString('<p class="muted">Unavailable</p>');   // Htmlable: as it is.
}

Your app's fallback

For the components without fallback(), name a view in config/nitro.php. Without one, Nitro shows this, which you can style with .nitro-fallback:

HTML
<p class="nitro-fallback">This part of the page could not load. <a href="">Try again</a></p>
PHP
// config/nitro.php
'fallbacks' => [
    'enabled' => null,                     // null: on when APP_DEBUG is off.
    'view' => 'partials.unavailable',      // For components without fallback().
],

Where a failure is caught

When it fails What shows
Its mount() or its view, on the server: the first page, a visit, a child a #[Server] call creates Its fallback() , else the app's The fallback is in the page's HTML in the component's place. The page and its other components render as usual
A #[Server] method throws Its fallback() , else the app's The call answers 500 with the fallback, and the browser shows it in the component's place
Its view, rendering in the browser The app's ( fallback() runs on the server, so it isn't asked) The browser shows it in the component's place, and reports the error in the console

A component showing its fallback is finished: it doesn't render again or take calls. The others carry on. Nitro's message, and a link in your own fallback with href="", load the page again, which starts the component afresh.

What isn't a fallback

  • The page itself. A page whose own mount() or view throws has nothing around it to keep working, so Laravel answers with its error page, as it does for a controller. Fallbacks are for the components inside a page or a plain Blade view.
  • Laravel's answers. abort(403), a failed Gate::authorize(), findOrFail() finding nothing, a guest where a user is needed, a redirect thrown with HttpResponseException, and every other exception Laravel's handler turns into a response, are answers, not failures. The visitor gets them as Laravel sends them, from a component in a page as from a controller, with fallbacks on or off.
  • Validation. A rule that fails puts its messages in $errors, as always.
  • An error exception() handles. A #[Server] method's error goes to the component's exception() hook first. When the hook handles it, the call answers normally, with the state as the hook left it. See Lifecycle.

exception() or fallback()?

Use exception() when the component can carry on, such as "The CRM isn't answering, try again in a minute" beside a form that keeps what was typed. Use fallback() when it can't, such as a widget whose data never loaded.

In development

Fallbacks are off while APP_DEBUG is on, so you see the error itself: Laravel's error page for a failure on the server, and the error in the browser's console for a call or a render in the browser. A component whose call failed keeps its last state.

To try your fallbacks locally, set fallbacks.enabled to true in config/nitro.php, and back to null after. false turns them off everywhere.

Reporting

Every failure that becomes a fallback goes to Laravel's exception handler, so your logs, Sentry, Flare or Nightwatch see it as they see any other error, with its stack trace. In the browser, Nitro dispatches nitro:error on the window for each failure there, with the component and the message:

JavaScript
window.addEventListener('nitro:error', (event) => {
    const { component, message, status } = event.detail;
    Sentry.captureMessage(message, { extra: { component: component?.name, status } });
});

Next steps