Dual Responses
Dual responses allow your controllers to serve both Hyper requests and traditional HTTP requests from a single method. This progressive enhancement approach ensures your application works with or without JavaScript, providing a better user experience and improved accessibility.
The web() Method
The web() method sets a fallback response for non-Hyper requests:
public function index()
{
$contacts = Contact::latest()->paginate(10);
return hyper()
->fragment('contacts.index', 'contact-list', compact('contacts'))
->web(view('contacts.index', compact('contacts')));
}How it works:
- Hyper request (has
Datastar-Requestheader) → Returns the fragment - Traditional request (initial page load, direct URL) → Returns the full view
- Same data, different rendering - Both responses use the same
$contactsdata
Why Use Dual Responses?
Progressive Enhancement
Your application works without JavaScript enabled:
public function search()
{
$query = request('q');
$results = Product::search($query)->paginate(20);
return hyper()
->fragment('search.results', 'results-list', compact('results', 'query'))
->web(view('search.results', compact('results', 'query')));
}- With JavaScript: Fast fragment updates, no page reload
- Without JavaScript: Traditional page navigation still works
- Same codebase: No duplicate controller logic
SEO Benefits
Search engines can crawl your full pages:
public function show($slug)
{
$post = Post::where('slug', $slug)->firstOrFail();
return hyper()
->fragment('posts.show', 'post-content', compact('post'))
->web(view('posts.show', compact('post')));
}Initial page loads serve complete HTML with proper meta tags, titles, and structured data that search engines can index.
Graceful Degradation
If the Hyper JavaScript fails to load or errors occur, traditional navigation continues working:
public function filter()
{
$category = request('category');
$products = Product::where('category_id', $category)->paginate(20);
return hyper()
->fragment('products.index', 'product-grid', compact('products'))
->web(view('products.index', compact('products')));
}Users on slow connections or with JavaScript disabled still get a functional application.
Implementation Patterns
Basic Pattern
The most common dual response pattern:
public function index()
{
$data = $this->getData();
return hyper()
->fragment('view.name', 'fragment-name', $data)
->web(view('view.name', $data));
}Both responses use identical data but render differently based on the request type.
With Fragments in Views
Structure your Blade views to support both modes:
{{-- resources/views/products/index.blade.php --}}
<!DOCTYPE html>
<html>
<head>
<title>Products</title>
@hyper
</head>
<body>
<div class="container">
<h1>Products</h1>
@fragment('product-grid')
<div id="product-grid">
<div class="grid">
@foreach($products as $product)
<x-product-card :product="$product" />
@endforeach
</div>
<div class="pagination">
{{ $products->links() }}
</div>
</div>
@endfragment
</div>
</body>
</html>public function index()
{
$products = Product::paginate(20);
return hyper()
->fragment('products.index', 'product-grid', compact('products'))
->web(view('products.index', compact('products')));
}- Hyper request: Only the fragment content is rendered
- Web request: Complete page with header, footer, and layout
- Single template: No code duplication
Multiple Fragments
Update multiple sections for Hyper, full page otherwise:
public function dashboard()
{
$stats = $this->getStats();
$activities = $this->getActivities();
$notifications = $this->getNotifications();
return hyper()
->fragments([
['view' => 'dashboard', 'fragment' => 'stats', 'data' => compact('stats')],
['view' => 'dashboard', 'fragment' => 'activities', 'data' => compact('activities')]
])
->web(view('dashboard', compact('stats', 'activities', 'notifications')));
}Handling Different Data Needs
Sometimes Hyper and web requests need different data:
public function dashboard()
{
// Data needed by both
$stats = $this->getStats();
if (request()->isHyper()) {
// Hyper: Just update stats
return hyper()->fragment('dashboard', 'stats', compact('stats'));
}
// Web: Need full page data
$activities = $this->getActivities();
$notifications = $this->getNotifications();
$user = auth()->user();
return view('dashboard', compact('stats', 'activities', 'notifications', 'user'));
}Or use conditional loading:
public function show($id)
{
$product = Product::findOrFail($id);
$response = hyper()->fragment('products.show', 'product-details', compact('product'));
// Only load related products for full page views
if (!request()->isHyper()) {
$related = Product::related($product)->take(6)->get();
$response->web(view('products.show', compact('product', 'related')));
}
return $response;
}Learn More
- Response Builder - Building Hyper responses
- Fragments - Working with fragments
- Conditional Logic - Detecting request types
- Request Macros - Request helper methods

