Skip to content

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:

php
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-Request header) → Returns the fragment
  • Traditional request (initial page load, direct URL) → Returns the full view
  • Same data, different rendering - Both responses use the same $contacts data

Why Use Dual Responses? ​

Progressive Enhancement ​

Your application works without JavaScript enabled:

php
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:

php
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:

php
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:

php
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:

blade
{{-- 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>
php
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:

php
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:

php
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:

php
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 ​