Basics
Client-side navigation allows your application to feel like a single-page app while maintaining the server-driven architecture that Laravel developers know and trust. Instead of full page reloads, Hyper fetches new content and updates the page seamlessly, preserving scroll position and providing instant feedback.
What Is Client-Side Navigation?
Traditional web navigation triggers a full page reload every time you click a link. The browser discards the entire page, fetches new HTML from the server, and rebuilds everything from scratch. This works, but it's slow and creates a jarring experience.
Client-side navigation intercepts link clicks, fetches the new content via AJAX, and updates only the parts of the page that changed. The browser's address bar updates, the back button works naturally, but your page never fully reloads.
The data-navigate Attribute (Hyper)
Hyper provides the data-navigate attribute to enable client-side navigation on links and forms. Add it to any container, and Hyper automatically intercepts navigation within that element.
<a href="/dashboard" data-navigate="true">Dashboard</a>When clicked, this link doesn't trigger a full page reload. Instead, Hyper:
- Prevents the default navigation
- Fetches the new content via AJAX with special headers
- Updates the browser's URL
- Patches the DOM with the server's response
The result feels instant and smooth.
Basic Link Navigation
The simplest way to use client-side navigation is on standard links:
<nav data-navigate="true">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>2
3
4
5
Every link inside the nav element now uses client-side navigation. The attribute works on the container, so you don't need to add it to every individual link.
You can also add it directly to specific links:
<a href="/users" data-navigate="true">View Users</a>
<a href="/reports" data-navigate="true">View Reports</a>2
How It Works
When you click a navigated link, here's what happens behind the scenes:
- Click Intercepted: Hyper's event listener catches the click
- Server Request: An AJAX request is sent with these special headers:
HYPER-NAVIGATE: trueHYPER-NAVIGATE-KEY: true(or your custom key)
- Server Response: Your Laravel controller returns content (usually a fragment)
- URL Update: The browser's address bar updates via the History API
- DOM Patch: The new content replaces the target element using DOM morphing
- Signals Sync: Any signal updates from the server apply automatically
The browser's back and forward buttons work naturally because Hyper uses the History API properly.
Navigation Keys
Navigation keys let you target specific parts of your page for updates. This is powerful for complex layouts where different links should update different sections.
<!-- Main navigation updates the full page -->
<a href="/dashboard" data-navigate__key.main="true">Dashboard</a>
<!-- Sidebar navigation updates only the sidebar -->
<a href="/sidebar/recent" data-navigate__key.sidebar="true">Recent Items</a>2
3
4
5
The __key.name modifier sets a navigation key. Your backend controller can check which key was used and respond accordingly:
public function dashboard()
{
$data = ['stats' => $this->getStats()];
// Main navigation: return full page
if (request()->isHyperNavigate('main')) {
return hyper()->fragment('dashboard', 'page', $data);
}
// Sidebar navigation: return only sidebar
if (request()->isHyperNavigate('sidebar')) {
return hyper()->fragment('dashboard', 'sidebar', $data);
}
// Direct visit: return full view
return view('dashboard', $data);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
This pattern lets you create sophisticated layouts where different navigation contexts update different page regions without duplicating controller logic.
Form Navigation
GET forms automatically work with navigation. When a user submits a search form, Hyper intercepts it and navigates to the result:
<form action="/search" method="GET" data-navigate="true">
<input type="text" name="q" placeholder="Search..." />
<button type="submit">Search</button>
</form>2
3
4
Submitting this form triggers navigation to /search?q=your+query, updating the page without a reload. This is perfect for search interfaces, filters, and any GET-based form.
POST, PUT, PATCH, and DELETE forms use Hyper's action system instead (covered in the Actions documentation).
The @navigate Action (Hyper)
While data-navigate works great on links and containers, sometimes you need to trigger navigation from within event handlers or expressions. The @navigate() action gives you programmatic control over navigation from the frontend.
Basic Usage
Trigger navigation from any event:
<button data-on:click="@navigate('/dashboard')">
Go to Dashboard
</button>2
3
When clicked, this navigates to /dashboard just like a data-navigate link would, but you have full control over when and how it happens.
Navigation with Keys
Pass a navigation key as the second parameter:
<button data-on:click="@navigate('/sidebar/recent', 'sidebar')">
Load Recent
</button>2
3
The backend can detect this key and respond with just the sidebar fragment.
JSON Query Navigation
Instead of manually building query strings, pass an object as the first parameter:
<button data-on:click="@navigate({category: 'electronics', page: 1})">
Show Electronics
</button>2
3
This navigates to ?category=electronics&page=1. The object approach is cleaner and handles encoding automatically.
Combining with Signal Values
Use signal values in your navigation:
<div data-signals="{search: '', category: ''}">
<input data-bind="search" placeholder="Search..." />
<select data-bind="category">
<option value="">All</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
<button data-on:click="@navigate({search: $search, category: $category, page: 1}, 'filters')">
Apply Filters
</button>
</div>2
3
4
5
6
7
8
9
10
11
12
13
When clicked, the button navigates with the current signal values. This pattern is perfect for filter forms where you want explicit submission rather than live updates.
Navigation with Options
The third parameter accepts options for parameter merging and history control:
<!-- Merge with existing parameters -->
<button data-on:click="@navigate({sort: 'price'}, 'filters', {merge: true})">
Sort by Price
</button>
<!-- Preserve only specific parameters -->
<button data-on:click="@navigate({page: 1}, 'filters', {only: ['search', 'category']})">
Reset to Page 1
</button>
<!-- Replace history instead of push -->
<button data-on:click="@navigate('/modal', 'modal', {replace: true})">
Open Modal
</button>2
3
4
5
6
7
8
9
10
11
12
13
14
Available options:
merge- Merge with existing query parametersonly- Array of parameters to preserveexcept- Array of parameters to excludereplace- Use replaceState instead of pushState
Clearing Parameters
Pass empty strings to remove parameters:
<button data-on:click="@navigate({search: '', category: '', page: 1})">
Clear All Filters
</button>2
3
Empty values are removed from the URL, keeping it clean.
Real-World Pattern: Search Form
Here's a complete search interface using @navigate:
<div data-signals="{
_search: '{{ request()->search ?? '' }}',
_category: '{{ request()->category ?? '' }}'
}">
<form data-on:submit__prevent="@navigate({search: $_search, category: $_category, page: 1}, 'filters', {merge: true})">
<input
type="search"
data-bind="_search"
placeholder="Search products..."
/>
<select data-bind="_category">
<option value="">All Categories</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
<button type="submit">Search</button>
</form>
<button data-on:click="$_search = ''; $_category = ''; @navigate({search: '', category: '', page: 1}, 'clear')">
Clear
</button>
</div>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
This pattern:
- Uses local signals (
_search,_category) for form state - Navigates on form submission with current filter values
- Resets to page 1 when filters change
- Provides a clear button that resets both signals and URL
The @navigate() action is covered in much more depth in the Advanced Patterns section, including timing modifiers, complex options, and integration with custom JavaScript.
Skipping Navigation
Sometimes you need certain links to bypass client-side navigation. Use data-navigate-skip to opt out:
<div data-navigate="true">
<!-- This link uses navigation -->
<a href="/internal">Internal Page</a>
<!-- This link does not (full page reload) -->
<a href="/logout" data-navigate-skip>Logout</a>
</div>2
3
4
5
6
7
Hyper also automatically skips:
- External links (different origin)
- Download links (
downloadattribute present) - Links explicitly skipped with
data-navigate-skip
Progressive Enhancement
Navigation works as progressive enhancement. If JavaScript fails to load or is disabled, links fall back to standard browser navigation automatically. Your application remains functional.
When building your backend responses, use the web() method to provide fallback content:
public function index()
{
$contacts = Contact::paginate(10);
return hyper()
->fragment('contacts.index', 'list', compact('contacts'))
->web(view('contacts.index', compact('contacts')));
}2
3
4
5
6
7
8
If the request is a Hyper navigation request, the fragment is returned. If it's a regular page load (first visit, JavaScript disabled, direct URL access), the full view is returned.
Common Patterns
Navigation Bar
<nav class="navbar" data-navigate__key.main="true">
<a href="/" class="nav-link">Home</a>
<a href="/products" class="nav-link">Products</a>
<a href="/about" class="nav-link">About</a>
</nav>2
3
4
5
Pagination Links
<div data-navigate__key.pagination="true">
{{ $contacts->links() }}
</div>2
3
Laravel's pagination links automatically work with navigation when wrapped in a navigation-enabled container.
Tab Switching
<div>
<!-- Tab headers -->
<div class="tabs" data-navigate__key.tabs="true">
<a href="/profile/overview">Overview</a>
<a href="/profile/settings">Settings</a>
<a href="/profile/security">Security</a>
</div>
<!-- Tab content updated by server response -->
@fragment('tab-content')
<div id="tab-content">
<!-- Server determines which tab to show -->
</div>
@endfragment
</div>2
3
4
5
6
7
8
9
10
11
12
13
14
15
Related Topics
- Query Parameters - Learn how to preserve filters and search terms during navigation
- Programmatic Navigation - Navigate from backend controllers using
hyper()->navigate() - Advanced Patterns - Combine navigation with modifiers for debouncing, throttling, and more
- Actions - Understand how POST/PUT/PATCH/DELETE operations differ from GET navigation

