In a Livewire app, what happens when a user clicks a link marked wire:navigate, and what does @persist keep across those page visits?
answer
- fetch the page, swap the body
- head scripts run once
- livewire:navigated replaces DOMContentLoaded
- @persist reuses the DOM element
- persisted markup outside components
basics
~20 swire:navigate intercepts the click, fetches the new page in the background and swaps in its body, title and URL without a full reload; @persist('name') keeps a matching element alive across those swaps instead of replacing it.
solid answer
~40 sOn a `wire:navigate` click Livewire prevents the browser's normal navigation, requests the next page in the background (starting on mouse-down, or on hover with `.hover`), shows a progress bar if it takes over 150 ms, then replaces the URL, `<title>` and `<body>` with the new page's. The JavaScript and CSS in `<head>` are not reloaded, so the page feels like a single-page app. Consequences: `DOMContentLoaded` fires only once, so per-page setup moves to the `livewire:navigated` event; `<body>` scripts rerun unless marked `data-navigate-once`; and a changed asset marked `data-navigate-track` forces a full reload. `@persist('player') ... @endpersist` makes Livewire move the existing element to the new page instead of replacing it, keeping its DOM and Alpine state. Persisted elements belong outside Livewire components, typically in the layout.
go deeper
Recall that wire:navigate swaps the body without a full reload and @persist keeps an element across those swaps.
Explain the fetch-and-swap sequence, head versus body scripts, livewire:navigated, and where persisted elements must live.
Plan for stale assets after deploys, duplicated document listeners and third-party scripts that assume full page loads.
Decide whether SPA-style navigation is worth the script-lifecycle discipline it demands across a team's front-end code.
## What a wire:navigate click does A normal link click makes the browser throw the page away and load a new document: it re-parses the HTML, re-runs every script and re-applies every stylesheet. `wire:navigate` on an `<a>` replaces that with Livewire's own navigation: 1. Livewire intercepts the click and prevents the browser's navigation. 2. It requests the target page over HTTP; the server renders it like any other page. 3. A progress bar appears at the top if the request takes longer than **150 ms** (configurable under `navigate` in `config/livewire.php`: `show_progress_bar`, `progress_bar_color`). 4. When the HTML arrives, Livewire swaps in the new **URL**, `<title>` and `<body>`, and initialises the Livewire components it contains. The server still renders the full page; what is saved is the browser's re-download and re-evaluation of assets. Livewire starts fetching on **mouse-down**, before the click completes, and `wire:navigate.hover` starts after 60 ms of hover or focus, at the price of fetching pages the user may never open. A component can navigate the same way with `$this->redirect('/reports', navigate: true)`. ## Script and asset consequences Because the browser never loads a new document, several habits break: - **`DOMContentLoaded` fires once.** Code that initialises a chart library per page must listen for `livewire:navigated`, which also fires on the first load. - **`<head>` scripts run once.** A script present on both pages is not re-run; a new one on the next page is loaded and run before the swap. - **`<body>` scripts re-run** on every visit unless marked `data-navigate-once`. - **Stale assets after a deploy.** A `<head>` asset marked `data-navigate-track` whose query string changes triggers a full page reload; Livewire adds this attribute to the tags `@vite` renders automatically. - **Listeners on `document` survive** navigations and can pile up; register them once or with `{ once: true }`. ## @persist Some elements should survive a navigation untouched: an audio player that is playing, an open chat widget, a sales-dashboard sidebar with its scroll position. Wrapping them in `@persist('name') ... @endpersist` tells Livewire: when the next page also contains a persisted element with this name, **move the existing DOM element** into the new page instead of using the fresh one. Its DOM state, event listeners and Alpine data are preserved. | Rule | Why | |---|---| | Place it outside Livewire components, usually in the layout | component markup is re-initialised from the new page | | Use the same name on both pages | the name is the match key | | Highlight active links with `data-current` or `wire:current` | server-side `@if (request()->is(...))` is frozen inside a persisted element | | Add `wire:navigate:scroll` to a scrollable persisted element | keeps its own scroll position | `@persist` only works with `wire:navigate`; a full page load recreates everything. ## What does not persist Livewire component **state** on the old page is not carried over to a newly visited page: on a forward navigation the components start from what the server rendered for that page. A dashboard filter the user set on one page is lost on navigation unless it lives in the URL, the session or the database. ## Common mistakes 1. Initialising a chart in `DOMContentLoaded` and seeing a blank chart after the first navigation. 2. Wrapping a Livewire component's inner markup in `@persist` and expecting its server state to survive. 3. Forgetting that hover prefetch doubles requests for links users only pass over. ## Events worth knowing Livewire fires three browser events on every navigation, including back and forward presses and programmatic `Livewire.navigate()` calls: - `livewire:navigate` when a navigation starts; it can be cancelled with `preventDefault()`; - `livewire:navigating` just before the new HTML is swapped in, a place to adjust the outgoing page; - `livewire:navigated` as the final step, and also on the initial page load. Per-page JavaScript, such as mounting a chart library on the sales report page, belongs in a `livewire:navigated` listener that checks the target element exists, because the listener itself persists for the whole session.
- A chart library initialised in DOMContentLoaded renders only on the first page after enabling wire:navigate. Why, and what is the fix?With `wire:navigate` the browser never loads a new document, so `DOMContentLoaded` fires once, on the first visit. Listen for `livewire:navigated` instead; it fires after every navigation and on the initial load.
- After a deploy, users navigating with wire:navigate keep running old JavaScript. How does Livewire handle that?Assets in `<head>` marked `data-navigate-track` are compared on each navigation; if the query-string version changes, Livewire does a full page reload. Livewire adds the attribute to the tags `@vite` renders, so versioned builds trigger the reload.
wire:navigate is like a theatre changing scenery between acts without sending the audience home: the stage set (the body) is replaced, the audience and lighting rig (head scripts and styles) stay, and a prop marked @persist is carried from one set to the next.
saying these in an interview costs you the question
- Believes wire:navigate renders the next page in the browser without a server request.
- Expects DOMContentLoaded to fire on every wire:navigate visit.
- Puts @persist inside a Livewire component to keep its server state.
- Thinks @persist also works on normal full page loads.
- Uses request()->is() inside a persisted nav to highlight the active link.