How to manipulate browser url in Inertia.js?
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
How to Manipulate Browser URL in Inertia.js with IntersectionObserver
As developers building Single Page Applications (SPAs) with frameworks like Inertia.js, we often need fine-grained control over the browser's URL, especially when implementing complex visual interactions like scroll-based navigation or state synchronization. The scenario you’ve described—using an IntersectionObserver to update the URL hash—is a classic pattern for creating smooth, anchor-based navigation. However, as you noted, integrating this directly with Inertia’s router methods can feel awkward if you are only manipulating the URL without triggering a full component re-render caused by a standard route change.
This post will walk you through how to effectively manipulate the browser URL in an Inertia/Vue 3 environment using DOM manipulation, which offers more direct control for these specific visual effects.
The Challenge: Inertia vs. Direct URL Manipulation
Inertia.js is powerful because it bridges the gap between your Laravel backend and a modern frontend framework (like Vue). It primarily manages navigation via its internal router, which hooks into the browser's History API. When you use router.push(), Inertia handles updating the history stack and rendering the correct component.
The challenge arises when you want to update the URL—specifically the hash (#section-id)—based purely on client-side scrolling events, without forcing a full Inertia route transition. Since the IntersectionObserver logic runs entirely in the browser context, directly manipulating the window.location.hash is the most efficient and direct way to achieve this visual effect.
The Solution: Leveraging window.location.hash
Instead of relying on potentially complex Inertia routing methods for simple anchor updates, we can leverage vanilla JavaScript to listen for scroll events and update the URL hash directly. This keeps the performance snappy and delegates the visual state management to the browser itself.
Here is how you adapt your existing IntersectionObserver logic for a Vue/Inertia setup:
Step 1: Ensure Your HTML Structure is Ready
Make sure your target sections have unique IDs that correspond to the desired URL fragments.
<!-- Example HTML structure -->
<section id="about">...</section>
<section id="services">...</section>
<section id="contact">...</section>
Step 2: Implement the Observer Logic in Vue/JavaScript
In your Vue component setup, you will define the observer and hook its callback function directly to update the browser history. This code runs purely on the client side, independent of Inertia’s routing cycle.
import { onMounted, onUnmounted } from 'vue';
export default {
setup() {
const router = window.inertiaRouter || window.router; // Check for global router availability if needed
let observer;
onMounted(() => {
const sections = document.querySelectorAll('section');
const options = {
threshold: 0.5 // Trigger when 50% of the element is visible
};
observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Directly manipulate the URL hash
const targetId = entry.target.id;
window.location.hash = `#${targetId}`;
// Optional: If you still want Inertia to react, you can call router.visit() here,
// but usually direct hash manipulation is sufficient for scroll effects.
}
});
}, options);
sections.forEach(section => {
observer.observe(section);
});
});
onUnmounted(() => {
if (observer) {
observer.disconnect();
}
});
return {};
}
}
Best Practices for Inertia Integration
When working within a Laravel/Inertia ecosystem, remember that backend logic should remain separate from purely visual client-side effects. While you are manipulating the URL hash on the frontend, ensure that your primary data fetching and state management still rely on Inertia's conventions. For complex state synchronization between the scroll position and server-side data, consider using Inertia's ability to send state via props or use dedicated Vuex/Pinia stores, which often provides a cleaner architecture than trying to force route logic onto purely visual events.
For robust application architecture, understanding the separation between server-side routing (Inertia) and client-side presentation manipulation is key. This pattern ensures that your application remains maintainable, regardless of how complex the user interface becomes—a principle central to building scalable applications, much like the robust design principles found in Laravel projects.
Conclusion
Manipulating the browser URL in Inertia for scroll-based effects is best achieved by utilizing native DOM APIs like window.location.hash. By decoupling this visual navigation from the core Inertia routing mechanism, you gain direct, high-performance control over the user experience while maintaining a clean separation of concerns. This approach allows your Vue and Inertia components to focus on rendering data fetched from the Laravel backend, while client-side scripts handle the dynamic presentation layer elegantly.