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.
Adding links
<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:
<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.
<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
.visiblewith one is a build error. - The keep is 30 seconds unless you set it. Times are written
150msor60s. - 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:navigateonly.nitro:prefetchon 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:
{{-- 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:
// config/nitro.php: every visit, inside a View Transition
'view_transitions' => true,
- Off by default. Without
view_transitionsor.transition, a visit changes the page at once, as it always has. - Every kind of visit. With
view_transitionson, links,$visit(),redirect(navigate: true)and back and forward all transition..transitionis 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:
/* 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]:
// config/nitro.php
'navigation' => 'data', // or 'markup'
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:
<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
@deferredor??. 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:
#[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
webgroup (such asauth,canorverified), 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.
// resources/js/app.js
import NProgress from 'nprogress';
window.addEventListener('nitro:navigating', () => NProgress.start());
window.addEventListener('nitro:navigated', () => NProgress.done());