Nitro

Pages

Navigation

nitro:navigate moves between pages without a full reload. The first page load is the server's HTML. After that, the browser asks only for the next page's data and renders it in place, keeping the layout.

On this page

To make a link navigate without a reload, add nitro:navigate to it. Laravel handles the visit as usual, including middleware, route bindings and redirects. The page runs mount() and answers with its state and title. The browser then renders the new page in place of the current one, and the layout and its components stay where they are.

Visiting from a link or from code live, in this page View source ↓
or a nitro:navigate link
Blade
<a href="{{ route('posts.index') }}" nitro:navigate>Posts</a>

<a href="{{ route('posts.index') }}" nitro:navigate.keypress>Starts on mouse down</a>
<a href="?tab=comments" nitro:navigate.preserve-scroll>Keeps the scroll position</a>
<a href="{{ route('posts.index') }}" nitro:navigate nitro:current="font-bold">Bold while current</a>
Attribute Does
nitro:navigate Visits the link's page without a reload
.keypress Starts the visit when the mouse button goes down, instead of on the click. Touch and keyboard still use the click
.preserve-scroll Keeps the scroll position. Without it, a visit scrolls to the top, or to the URL's #fragment
.transition Shows this link's visit inside a View Transition: the old page animates into the new one (see View Transitions)
nitro:prefetch Asks for the page before the click, on hover or with .visible when the link is on screen, so the click shows it at once (see Prefetching)
nitro:current="class" Adds the class while the link points at the current page, and sets aria-current="page" . Add .exact to match the same path only

You can also visit a page from code. Call $visit(url) in a handler, or $this->redirect($url, navigate: true) in a method. The back and forward buttons return to where you left each page.

Prefetching

nitro:navigate visits a page without a reload. nitro:prefetch, beside it, asks for the page's data before the click, so the click shows the page at once. A link prefetches only when you add it:

Blade
<a href="/posts" nitro:navigate>Posts</a>                                    {{-- no prefetch --}}
<a href="/posts" nitro:navigate nitro:prefetch>Posts</a>                     {{-- fetch on hover --}}
<a href="/posts/hello-world" nitro:navigate nitro:prefetch.visible>Hello, world</a>   {{-- fetch when on screen --}}
  • On hover (nitro:prefetch). It asks once the pointer has rested on the link for 75ms, so moving across a menu asks for nothing. Focusing the link with the keyboard asks at once.
  • On screen (nitro:prefetch.visible). It asks as soon as the link scrolls into view, once per link. Give it to the link most visitors follow next, not to every row of a long list: each one is a request to your server.
  • Not at all (no nitro:prefetch). The page is asked for on the click, as with any visit.

How long it waits, and how long it keeps the page

A time right after nitro:prefetch is how long the pointer must rest on the link first. .keep is how long the fetched page is kept: after that, the click asks for it again.

Blade
<a href="/archive" nitro:navigate nitro:prefetch.150ms>Archive</a>                     {{-- wait 150ms on hover --}}
<a href="/archive" nitro:navigate nitro:prefetch.keep.60s>Archive</a>                  {{-- keep it 60 seconds --}}
<a href="/archive" nitro:navigate nitro:prefetch.wait.150ms.keep.60s>Archive</a>       {{-- both, spelled out --}}
<a href="/posts/hello-world" nitro:navigate nitro:prefetch.visible.keep.60s>Hello, world</a>   {{-- on screen, kept 60 seconds --}}
  • The wait is 75ms unless you set it. Raise it for a menu people move across; on screen there is no wait, so .visible with one is a build error.
  • The keep is 30 seconds unless you set it. Times are written 150ms or 60s.
  • Which pages. Only a Nitro page the browser renders from its data is prefetched, and only from a Nitro page. A markup-first or client page already shows at once, so it isn't asked for; a plain Blade page is fetched on the click.
  • With nitro:navigate only. nitro:prefetch on a link without it does nothing.

Prefetched pages, and back and forward

A page a link prefetched is shown by its click without another request. Back and forward show the pages you visited in the last five minutes as they were, also without asking again. Both are kept in memory only:

  • Never in local storage or the history entry, so a reload, or the next person at the same computer, starts without them.
  • Any #[Server] call forgets them, so a page is never shown from before your own change.
  • Flash messages are shown once: back and forward don't show them again.

Prefetching asks for a page before anyone clicks, so only prefetch pages that are safe to load: a GET page should never change anything anyway. Anything that does, such as signing out or deleting, belongs in a form or a #[Server] method, not a link.

Links are for other pages

To filter or sort what a page shows, change a property with $set() or nitro:model and let the view render again. That needs no visit, and in the browser it needs no request.

View Transitions

A visit can change the page inside the browser's View Transitions: the browser keeps a picture of the old page, Nitro puts the new one in place, and the old page fades into the new. Turn it on for one link with .transition:

Blade
{{-- This link's visit, inside a View Transition. --}}
<a href="{{ route('posts.show', $post) }}" nitro:navigate.transition>{{ $post->title }}</a>

{{-- With nitro:prefetch: asked for in view, shown with a transition. --}}
<a href="{{ route('posts.show', $post) }}" nitro:navigate.transition nitro:prefetch.visible>{{ $post->title }}</a>

Or for every visit, in config/nitro.php:

PHP
// config/nitro.php: every visit, inside a View Transition
'view_transitions' => true,
  • Off by default. Without view_transitions or .transition, a visit changes the page at once, as it always has.
  • Every kind of visit. With view_transitions on, links, $visit(), redirect(navigate: true) and back and forward all transition. .transition is for that link's visits only.
  • Where it doesn't run. In a browser without View Transitions, or in a tab that is hidden, the page changes at once. Nothing else is different, so it is safe to turn on.
  • The visit doesn't wait for it. The new page is in place and its components start as the animation begins. While it runs (a cross-fade takes a quarter of a second), the browser's animation is on top of the page, so keep custom ones short.

The default is a cross-fade of the whole page. Your CSS decides anything else: the ::view-transition-old(root) and ::view-transition-new(root) pseudo-elements for the page, and view-transition-name for an element that should move from its place on the old page to its place on the new one:

CSS
/* The whole page: slower, sliding in from the right. */
::view-transition-old(root) { animation: 200ms ease-out both fade-out; }
::view-transition-new(root) { animation: 250ms ease-out both slide-in; }

@keyframes fade-out { to { opacity: 0; } }
@keyframes slide-in { from { opacity: 0; transform: translateX(24px); } }

/* One element moving between pages: the same name on both. */
.post-cover { view-transition-name: post-cover; }

/* Visitors who asked for less motion: no animation. */
@media (prefers-reduced-motion: reduce) {
    ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none; }
}

One element per name

A view-transition-name must be on one element at a time. On a list page, where every row would have it, give it only to the row that was clicked, or the browser skips the transition.

Visiting any page

A link can lead anywhere in your application. When the target is a Nitro page in the same layout, the browser renders it from its data. Anything else is fetched as HTML and replaces the document, still without a reload. That includes a plain Blade view, a page in another layout, or a view returned by a controller. When this happens:

  • New stylesheets load before the page shows, so it never appears unstyled.
  • Its new scripts run, and its components and islands start.
  • An error page or a download loads normally.

Every visit sends the version of your build. If a new build has been deployed since the page loaded, the server answers with a 409, and the browser loads the page in full with the new code.

Choosing how a visit shows a page

Each page chooses what a visit does with #[Navigate]. First, ask whether the page needs the server at all. If it doesn't, it can be a client page. If it does, decide whether it should appear after its data arrives (data, the default) or before (markup).

Mode The browser Suits
data (default) Asks the server for the page's data, then renders the complete page. This takes one round trip, and nothing shows half-filled Pages that mean nothing without their data, and pages behind auth or can
markup Renders the page at once with its properties' defaults, then fills it in when the data arrives Dashboards, lists and forms with placeholders
client Renders the page at once and doesn't ask the server at all Help, about pages, legal text, docs

The first page load is always the server's HTML. The mode only decides what later visits do.

Name the mode as a string, or by its case of Nitro\NavigationMode, which your editor completes and checks: #[Navigate(NavigationMode::Markup)]. Pages without #[Navigate] follow nitro.navigation, which may be data or markup. A client page has to say so itself, because it must need nothing from the server.

Showing the markup first

Set the default in config/nitro.php, and choose for a single page with #[Navigate]:

PHP
// config/nitro.php
'navigation' => 'data',   // or 'markup'
PHP
use Nitro\Attributes\Navigate;

#[Navigate('markup')]
class Dashboard extends Component

During a markup-first visit, every property counts as deferred until the data arrives. Use @deferred and @placeholder to choose what to show in the meantime:

Blade
<h1>Dashboard</h1>

@deferred('stats')
    <x-stats :stats="$stats" />
@placeholder
    <div class="skeleton"></div>
@enddeferred
  • The view must render with the properties' defaults. Guard data that isn't there yet with @deferred or ??. For example, don't read $post['title'] from an empty array.
  • Anything the visitor types or toggles before the data arrives is kept. A #[Server] call made in the meantime waits for the data.
  • Components created by the server (those with mount()) appear when the data arrives, and the title is set at that point.
  • A page in another layout, a redirect or an error is handled the same way as in a data-first visit.

Client pages

A page that needs nothing from the server can skip the server entirely on a visit:

PHP
#[Title('Help')]
#[Navigate('client')]
class Help extends Component

The browser renders a client page from its compiled view as soon as the link is clicked, without a request. Its title and head tags are worked out when you build the app. Its state and browser methods work as they do on any page, and the back and forward buttons render it again without a request. A reload or a first visit still gets the server's HTML.

A client page can't use anything that only the server can do. The build refuses a client page that has:

  • mount(), #[Server] methods or #[Url] properties.
  • Route parameters, or middleware beyond the web group (such as auth, can or verified), because only the server can check them.

For those pages, use #[Navigate('markup')] instead. It also renders at once, and then asks the server for the rest.

Showing a progress bar

During a visit, a bar runs along the top of the window, as it does on these docs. To configure it, publish its configuration with php artisan vendor:publish --tag=nprogress-config:

Option
show Shows the bar
server_calls Shows it during #[Server] calls too
color , height , position , z_index Its look
glow , glow_width , spinner , spinner_size The glow at its tip, and a spinner
delay How long to wait before it appears, in milliseconds
minimum_duration How long it runs at least once shown, in milliseconds. A fast visit is then seen to complete rather than flash
minimum , maximum , trickle , trickle_speed , speed , easing , fade_speed Its motion

The bar's markup is #nprogress .bar, so existing NProgress styles apply to it.

Using your own bar

To use another bar, such as NProgress itself, set show to false and drive it from the events Nitro sends: nitro:navigating when a visit starts and nitro:navigated when the new page shows.

JavaScript
// resources/js/app.js
import NProgress from 'nprogress';

window.addEventListener('nitro:navigating', () => NProgress.start());
window.addEventListener('nitro:navigated', () => NProgress.done());

Next steps