internachi/blade-alpine-instantsearch
Blade-Alpine InstantSearch integrates Algolia InstantSearch into Laravel using Blade components and Alpine.js directives. Build reactive search UIs—hits, facets, pagination, and more—while staying in a familiar Blade workflow.
This package wraps Algolia's InstantSearch.js in Blade components on top of Alpine.js — so you get a search UI without writing a Vue/React SPA, but you keep the full InstantSearch widget vocabulary.
Install and publish.
composer require internachi/blade-alpine-instantsearch
php artisan vendor:publish --tag="blade-alpine-instantsearch-config"
Check the README for whether assets are published separately (--tag=...-assets) or pulled from a CDN — the two paths have different cache-busting implications (see Gotchas).
Credentials. Put your search-only API key and app ID in config/services.php under an algolia key, sourced from .env. Never let the admin key reach the browser bundle.
First render. Drop the search root component into a Blade view, set the index name, and add two children — a search box and a hits list. This is the smallest thing that proves the plumbing (Algolia app ID, index name, asset loading, Alpine boot) all works end-to-end before you start styling.
Where to look first. The package's Blade component namespace (usually resources/views/components after publish, or vendor/.../resources/views if not publishable) is the real documentation — each component maps to a single InstantSearch widget, and the prop names mirror InstantSearch's widgetParams. Read one component's Blade file and you can infer the rest.
First real use case: a /search page with searchbox → stats → hits → pagination. Get that working, then layer on refinement lists and URL state.
One root, many children. Treat the search root component as a provider: it owns the Algolia client and index, and every child widget inside it joins that context. Don't nest two roots inside the same DOM subtree, and don't try to share widgets across roots.
URL state is now first-class (0.8.0). Prior to 0.8.0 the query-string mapping was effectively fixed; 0.8.0 lets you customize URL params. The practical pattern:
q, page, brand, category) and make them match what you actually want to read server-side, in analytics, or in marketing links.brand) and InstantSearch's default dotted form (refinementList.category) only for facets you'll never hand-write. Human-readable URLs are shareable URLs.Compose, don't fork. Build custom widgets as an Alpine component that reads the InstantSearch helper (instantsearch instance exposed on the Alpine scope) and calls helper.setQueryParameter(...) / helper.search() with a debounce. This survives package upgrades far better than copying a vendor Blade file into your app.
Server-side fallback. The hits are client-rendered; crawlers mostly aren't. If search result pages need to rank, render a small server-side list of top results (a direct Algolia PHP client call in the controller, cached) behind the interactive widget, and let Alpine replace it on load.
Multiple search instances. Search-within-search (e.g. a docs sidebar and a main results pane) works if each root has a distinct index name and distinct URL param namespaces — otherwise the two instances will fight over q in the query string.
Scoped indexes for multi-tenant. Rather than one index with a tenant facet filter, prefer passing a tenant-suffixed index name into the root component from the controller. It keeps the URL params clean and avoids leaking cross-tenant facet counts.
0.8.0 is a pre-1.0 minor bump. Under semver, 0.x minor releases are allowed to break. bencarr's URL-params change is additive in intent, but if you had existing deep links with default param names, verify they still resolve after upgrading. Check any tests or marketing links that hardcode ?q= or ?page=.
Alpine double-boot. If you're using Livewire wire:navigate, Turbo, or Inertia, the search root will be re-mounted without re-initializing InstantSearch, and you'll get stale results or silent no-ops. Either exclude the search page from SPA navigation, or hook into the navigation lifecycle to tear down and rebuild the search instance.
Asset loading order. Alpine must be defined before the package's directives register. If you load the package's JS from a <script> in @stack('scripts') but Alpine comes from Vite with defer, you get intermittent "widget did nothing" failures that only reproduce on cold cache. Confirm the loading strategy and pin it.
Config caching. php artisan config:cache will freeze your Algolia credentials at deploy time. If you rotate the search key, you must re-cache. Also: never resolve env() inside a package component — you'll get null in production with a cached config.
Search-only key, and restrict it. A search-only key is public by definition, but it should still be scoped with restrictIndices and, where applicable, secured API keys with a tenant filter. If you're doing per-user filtering (e.g. "only my org's documents"), you need a secured API key generated server-side per session — a client-side facet filter is not a security boundary.
URL params and sensitive filters. Anything in the query string is in browser history, referrer headers, and server logs. Don't map private filters into URL params. This is the most common way this feature gets misused.
Hit template escaping. Hits render via {!! !!} in many of these wrappers so you can put HTML in the template. Since the content comes from your Algolia index (which may contain user-generated text), either use {{{ }}}-style escaping or sanitize the indexed attributes at index time. Highlighted attributes in particular can carry markup.
Extension point: custom widgets. Most of the friction in this package shows up when you need a widget it doesn't ship (geo search, range slider with custom formatting, a "no results" fallback with query suggestions). The durable pattern is an Alpine component using the exposed helper — reach for that before patching vendor files, and keep a note of which helper methods you depend on, since those are the most likely thing to shift in a 0.x upgrade.
How can I help you explore Laravel packages today?