DEV Community

Mykola Vantukh
Mykola Vantukh

Posted on • Edited on

Laravel Livewire 3 Filter SEO and UI (Part 3)

In this third part of the series, we’ll dive into the role of JavaScript

Even though Livewire 3 already updates content without reloads, this small JavaScript bridge was essential for:

  • Updating the URL in the browser history as filters change
  • Keeping the page title and meta tags accurate for users
  • Ensuring back/forward navigation in the browser re-triggers Livewire updates

The goal: a seamless and consistent UX without full page reloads or a separate SPA framework.


The Purpose for Users

For users, this JavaScript ensures that:

  • When filters are applied, the page title and meta description change immediately in the browser tab.
  • The browser URL updates dynamically, so sharing links always reflects the current filter combination.
  • Clicking back/forward buttons updates the filters and product lists correctly, without any glitches.
  • No flickering or reloads – everything feels native and smooth.

The Minimal JS Bridge

export default class ProductsFiltersLivewire { constructor() { this.isInitialized = false; } init(baseUrl) { if (this.isInitialized) { return; } this.isInitialized = true; this.replaceHistoryState(baseUrl); this.setupEventListeners(); this.setupLivewireHooks(); } replaceHistoryState(baseUrl) { history.replaceState({ url: [baseUrl] }, null, baseUrl); } setupEventListeners() { window.addEventListener('popstate', (event) => { Livewire.dispatch('url-changed-back', event.state.url); }); } setupLivewireHooks() { // Update browser URL as filters or pages change Livewire.on('url-updated', (url) => { history.pushState({ url: url }, null, url); }); Livewire.on('page-changed', (url) => { history.pushState({ url: url }, null, url); }); // Update meta tags and page heading instantly for a polished UX Livewire.on('meta-tags-ready-for-update', (data) => { document.title = data['meta_title']; const robotsMeta = document.querySelector('meta[name="robots"]'); if (robotsMeta) robotsMeta.setAttribute('content', data['robots_content']); const descMeta = document.querySelector('meta[name="description"]'); if (descMeta) descMeta.setAttribute('content', data['meta_description']); const canonicalLink = document.querySelector('link[rel="canonical"]'); if (canonicalLink) canonicalLink.setAttribute('href', data['canonical_tag']); const pageHeading = document.querySelector('.pageHeadingTitle'); if (pageHeading) pageHeading.textContent = data['page_heading_title']; }); // Handle loading states for instant visual feedback Livewire.hook('commit', (interaction) => { this.handleCommit(interaction); }); } handleCommit({ component, commit, respond, succeed, fail }) { this.setLoadingState(component.el, true); succeed(() => { this.setLoadingState(component.el, false); }); fail(() => { this.setLoadingState(component.el, false); }); } setLoadingState(el, loading) { if (loading) { el.classList.add("loading"); } else { el.classList.remove("loading"); } } } 
Enter fullscreen mode Exit fullscreen mode

How It Interacts with Livewire

Livewire emits events like url-updated, meta-tags-ready-for-update, or lifecycle hooks (commit) whenever filters change, new products are loaded, or the page is rehydrated.

This JS bridge listens to those events and:

  • Updates the browser’s URL history
  • Dynamically updates meta tags and titles in the browser DOM
  • Manages loading states visually (e.g., a “loading” CSS class on filter panels or product areas)

Everything is driven by Livewire’s event system – the JS doesn’t do any data-fetching itself. It simply mirrors and finalizes what Livewire already did server-side.


Let’s Connect

If you’re looking for a senior developer to solve complex architecture challenges or lead critical parts of your product — let’s talk.

Connect with me on LinkedIn

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.