Nitro

Getting started

How Nitro works

Each Nitro component runs in two places. This page explains what the compiler makes of your code, what the server sends on the first load, and what happens when you click and when you call the server.

On this page

The compiler

Nitro reads each component's class and view and compiles them to JavaScript. It compiles the methods the browser can reach, and turns the view into a tree of elements with its dynamic parts. During development, this happens on the first request after you change a component. For production, you run php artisan nitro:build to do it ahead of time.

  • PHP's rules still hold. Arrays are values, == compares the way PHP compares, strings are bytes, and functions behave as PHP's do. All of this is checked against PHP itself.
  • The browser only gets what it needs. Nitro never compiles a method the browser can't reach, such as a helper that you only call from mount().
  • Mistakes fail the build. A browser method that queries the database, or a view the browser can't render the same way, causes an error that names the file and line. You find out during development, not in production.

The first render

The server renders the page with Blade, just as Laravel renders any view. The HTML arrives complete, so it appears quickly and search engines and link previews can read it. Along with it comes each component's state, signed by the server.

The browser then takes over the page. It doesn't render the page again. Instead, it attaches to the elements the server sent, so nothing moves or flickers. From then on, the component is live.

What happens on a click

nitro:click="increment" calls the compiled increment() method in the browser. The method changes $count, and only the parts of the view that read $count render again. This works because, when the view was compiled, every dynamic part recorded which properties it reads:

Blade
<div>
    <button nitro:click="increment">+</button>
    <p>Clicked {{ $count }} times</p>      {{-- reads $count --}}
    <p>Hello, {{ $name }}</p>               {{-- reads $name, so a click leaves it alone --}}

    @foreach ($authors as $author)          {{-- reads $authors, so neither one changes it --}}
        <li>{{ $author }}</li>
    @endforeach
</div>
A change runs Then compares
Livewire the whole view, on the server the new HTML against the page
React, Vue the component’s render function the new virtual DOM against the last one
Nitro the expressions that read the property nothing: each one updates its own node

What happens on a server call

A method marked #[Server] runs on the server. Each call is a single request:

  1. The browser sends the signed state

    It sends the component's state exactly as the server signed it, the properties it has changed since, and the method with its arguments.

  2. The server checks everything

    • The state is one the server signed, for this session and this user, and it hasn't been changed.
    • The page's middleware (auth, can, ...) still lets the visitor in.
    • Every changed property is public, isn't #[Locked], has its declared type, and passes its #[Validate] rules.
    • The method is a #[Server] method, and each argument has its parameter's type.
  3. The method runs

    It runs like any other Laravel code, with queries, mail, the session and the container. Models are always read again from the database, never taken from the browser.

  4. The browser renders what changed

    The response holds the new signed state, along with any events, redirects or messages. Only the parts of the view that read a changed property render again.

The browser can send anything

Anyone can call your endpoint without using the page. An unlocked property can arrive with any value of its type. So check whatever a #[Server] method relies on, with #[Validate] rules or $this->validate().

A link with nitro:navigate changes the page without a full reload. Laravel routes the visit as usual, with its middleware, bindings and redirects, and the browser shows the new page in place of the old one. The first visit to any URL always gets the server's complete HTML. These docs work this way.

In production

nitro:build compiles every component, minifies it with esbuild, and publishes versioned files to public/nitro. The web server sends these files without PHP, and browsers cache them for a year. Each page is in a file of its own, which loads the first time someone visits it. The files hold views and browser methods, never data. The state travels with each response.