Back to Blog

Livewire Navigate

Julian Beaujardin
Julian Beaujardin July 16th, 2024

Many modern web applications are built as "single page applications" (SPAs). In these applications, each page rendered by the application no longer requires a full browser page reload, avoiding the overhead of re-downloading JavaScript and CSS assets on every request.

The alternative to a single page application is a multi-page application. In these applications, every time a user clicks a link, an entirely new HTML page is requested and rendered in the browser.

While most PHP applications have traditionally been multi-page applications, Livewire offers a single page application experience via a simple attribute you can add to links in your application: wire:navigate.

Basic usage

Let's explore an example of using wire:navigate. Below is a typical Laravel routes file (routes/web.php) with three Livewire components defined as routes:

use App\Livewire\Dashboard;
use App\Livewire\ShowPosts;
use App\Livewire\ShowUsers;

Route::get('/', Dashboard::class);

Route::get('/posts', ShowPosts::class);

Route::get('/users', ShowUsers::class);

By adding wire:navigate to each link in a navigation menu on each page, Livewire will prevent the standard handling of the link click and replace it with its own, faster version:

<nav>
    <a href="/" wire:navigate>Dashboard</a>
    <a href="/posts" wire:navigate>Posts</a>
    <a href="/users" wire:navigate>Users</a>
</nav>

Below is a breakdown of what happens when a wire:navigate link is clicked:

  • User clicks a link
  • Livewire prevents the browser from visiting the new page
  • Instead, Livewire requests the page in the background and shows a loading bar at the top of the page
  • When the HTML for the new page has been received, Livewire replaces the current page's URL, <title> tag and <body> contents with the elements from the new page

This technique results in much faster page load times — often twice as fast — and makes the application "feel" like a JavaScript powered single page application.

Redirects

When one of your Livewire components redirects users to another URL within your application, you can also instruct Livewire to use its wire:navigate functionality to load the new page. To accomplish this, provide the navigate argument to the redirect() method:

Now, instead of a full page request being used to redirect the user to the new URL, Livewire will replace the contents and URL of the current page with the new one.

By default, Livewire includes a gentle strategy to prefetch pages before a user clicks on a link:

  • A user presses down on their mouse button
  • Livewire starts requesting the page
  • They lift up on the mouse button to complete the click
  • Livewire finishes the request and navigates to the new page

Surprisingly, the time between a user pressing down and lifting up on the mouse button is often enough time to load half or even an entire page from the server.

If you want an even more aggressive approach to prefetching, you may use the .hover modifier on a link:

<a href="/posts" wire:navigate.hover>Posts</a>

The .hover modifier will instruct Livewire to prefetch the page after a user has hovered over the link for 60 milliseconds.

Prefetching on hover increases server usage

Because not all users will click a link they hover over, adding .hover will request pages that may not be needed, though Livewire attempts to mitigate some of this overhead by waiting 60 milliseconds before prefetching the page.

Persisting elements across page visits

Sometimes, there are parts of a user interface that you need to persist between page loads, such as audio or video players. For example, in a podcasting application, a user may want to keep listening to an episode as they browse other pages.

You can achieve this in Livewire with the @persist directive.

By wrapping an element with @persist and providing it with a name, when a new page is requested using wire:navigate, Livewire will look for an element on the new page that has a matching @persist. Instead of replacing the element like normal, Livewire will use the existing DOM element from the previous page in the new page, preserving any state within the element.

Here is an example of an <audio> player element being persisted across pages using @persist:

@persist('player')
    <audio src="{{ $episode->file }}" controls></audio>
@endpersist

If the above HTML appears on both pages — the current page, and the next one — the original element will be re-used on the new page. In the case of an audio player, the audio playback won't be interrupted when navigating from one page to another.

Please be aware that the persisted element must be placed outside your Livewire components. A common practice is to position the persisted element in your main layout, such as resources/views/components/layouts/app.blade.php.

Preserving scroll position

By default, Livewire will preserve the scroll position of a page when navigating back and forth between pages. However, sometimes you may want to preserve the scroll position of an individual element you are persisting between page loads.

To do this, you must add wire:scroll to the element containing a scrollbar like so:

@persist('scrollbar')
<div class="overflow-y-scroll" wire:scroll>
    <!-- ... -->
</div>
@endpersist

Using with analytics software

When navigating pages using wire:navigate in your app, any <script> tags in the <head> only evaluate when the page is initially loaded.

This creates a problem for analytics software such as Fathom Analytics. These tools rely on a <script> snippet being evaluated on every single page change, not just the first.

Tools like Google Analytics are smart enough to handle this automatically, however, when using Fathom Analytics, you must add data-spa="auto" to your script tag to ensure each page visit is tracked properly:

<head>
    <!-- ... -->

    <!-- Fathom Analytics -->
    @if (! config('app.debug'))
        <script src="https://cdn.usefathom.com/script.js" data-site="ABCDEFG" data-spa="auto" defer></script>
    @endif
</head>

Script evaluation

When navigating to a new page using wire:navigate, it feels like the browser has changed pages; however, from the browser's perspective, you are technically still on the original page.

Because of this, styles and scripts are executed normally on the first page, but on subsequent pages, you may have to tweak the way you normally write JavaScript.