vue-instantsearch
vue-instantsearch copied to clipboard
Nuxt + SSR + Routing - Pushes route twice
Bug 🐞
What is the current behavior?
I am trying to implement the nuxt + ssr + routing examples from the docs: https://www.algolia.com/doc/guides/building-search-ui/going-further/routing-urls/vue/#combining-with-vue-router
Issue 1: Route is pushed twice so you have to click back twice to get back
I recoreded a gif so you can reproduce the steps yourself:

This sandbox (serene-leftpad-jp7gb) contains basically just code from the docs https://codesandbox.io/s/serene-leftpad-jp7gb
What is the expected behavior?
Route only pushed once
Does this happen only in specific situations?
No
What is the proposed solution?
I don't know
What is the version you are using?
3.2.0
Always try the latest one before opening an issue.
Quick fix for this problem, just check if the query truly changed. Not pretty but gets the job done.
function nuxtRouter(vueRouter) {
return {
read() {
[...]
},
write(routeState) {
if (this.createURL(routeState) !== this.createURL(this.read())) {
vueRouter.push({
query: {
...routeState,
},
})
}
},
createURL(routeState) {
return vueRouter.resolve({
query: routeState,
}).href
},
[...]
}
}
export { nuxtRouter }
Additionally to that I have another issue which I can't replicate in a sandbox but it is real. If someone has an idea what could go wrong here please let me know:
Scenario:
- User visits: example.com/search?brand=ABC
- The brand ABC is selected accordingly through SSR - all good so far
- User selects additional refinement filters e.g. category=dog
- Clicks on a product
- Goes back to search via browser back button
What is expected?
State: brand=ABC&category=dog
What happens?
The original SSR state is shown meaning: brand=ABC
No matter what I select (even unselecting everything) the original SSR state is shown.
If I visit the search page through example.com/ -> [navigate to] -> example.com/search. Everything works as expected.
After 2 days of debugging I found this "solution" -> nuke the algoliaState after it was used in beforeMount:
this.$nuxt.context.nuxtState.algoliaState = undefined
window.__NUXT__.algoliaState = undefined
thanks for passing this workaround, it's indeed correct, and so is the workaround to your second problem. I'll update the documentation to include these points!
Heya, had to test this to see if it was a nuxt problem or a vue storefront problem: made a minimal repo: https://github.com/ed42311/algolia-nuxt-routes
To clarify: it seems that that the problem I'm seeing is with VSF, but I thought this might be useful
The documentation has not been updated yet, but a fully correct router that works with back button in vue should be:
function nuxtRouter(vueRouter) {
let writeTimer = null;
let removeAfterEachListener = null;
let popStateListener = null;
return {
read() {
return vueRouter.currentRoute.query;
},
write(routeState) {
// Only push a new entry if the URL changed (avoid duplicated entries in the history)
if (this.createURL(routeState) === this.createURL(this.read())) {
return;
}
if (writeTimer) clearTimeout(writeTimer);
writeTimer = setTimeout(
() =>
vueRouter.push({
query: routeState,
}),
400
);
},
createURL(routeState) {
return vueRouter.resolve({
query: routeState,
}).href;
},
onUpdate(cb) {
removeAfterEachListener = vueRouter.afterEach((to, from) => {
console.log('haaah', to.query)
cb(to.query);
});
if (typeof window === "undefined") return;
popStateListener = () => {
cb(this.read());
};
window.addEventListener("popstate", this._onPopState);
},
dispose() {
if (writeTimer) clearTimeout(writeTimer);
if (removeAfterEachListener) removeAfterEachListener();
if (typeof window === "undefined") return;
window.removeEventListener("popstate", popStateListener);
},
};
}
@Haroenv this solution lead to a infinit route change on my project.. so every 400ms i see the url without any params that it shows them and removes them again..
this code seems to work for my project at least:
import qs from 'qs'
function nuxtRouter(vueRouter) {
const routeWritten = null
let popStateListener = null
return {
read() {
return qs.parse(vueRouter.currentRoute.query)
},
write(routeState) {
// Only push a new entry if the URL changed (avoid duplicated entries in the history)
if (this.createURL(routeState) === this.createURL(this.read())) {
return
}
if (!routeWritten) {
setTimeout(
() =>
vueRouter.push({
query: routeState
}),
400
)
}
},
createURL(routeState) {
return vueRouter.resolve({
query: routeState
}).href
},
onUpdate(cb) {
if (typeof window === 'undefined') return
popStateListener = () => {
cb(this.read())
}
window.addEventListener('popstate', this._onPopState)
},
dispose() {
if (typeof window === 'undefined') return
window.removeEventListener('popstate', popStateListener)
}
}
}
just found a small bug..
read() {
return vueRouter.currentRoute.query
},
is not eqal:
read() {
return qs.parse(vueRouter.currentRoute.query)
},
so if i use qs.parse in read() it also works on client side navigation with query params.
using qs is best done in the vue router configuration itself, and not in the router here: https://www.algolia.com/doc/guides/building-search-ui/going-further/routing-urls/vue/#combining-with-vue-router
yes.. but in my case it was the only way i could fix it as the result of read() was on SSR a different one as on the client side. but as suggested i also use it in my nuxt config as described.
found this awhile back: https://code.luasoftware.com/tutorials/algolia/setup-algolia-instantsearch-on-nuxt-with-query-url/
seems to resolve the loading error, probably because of the callback timing?
also lets you clean up the url
something else i discoverd this morning: if you keep some views alive in nuxt the solution is not working. You need to exclude the component than includes the Algolia Components from the keep-alive prop.
I'll close this now, as likely most context is no longer valid. The InstantSearch.js history router has since added some code to ensure you don't write multiple times to the same url. If you have a custom router, I recommend taking inspiration from there and checking before writing