question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

With Nuxt + ssr + routing, bug with the browser previous/next buttons ?

See original GitHub issue

Hi the team,

I am trying to implement vue-instantsearch with nuxt + ssr + routing. SSR is OK.

If I refine brand attributes with “Apple” (firstly), and secondly “Samsung”, and click on the browser back button (Chrome & Safari, Mac), the url is updated but not the app. (Apple and Samsung are always checked). If I hard reload the current page, Microsoft disappears.

What I Want : when I click on the browser back button, I want to have the previousState in my app (only “Apple” checked).

What I did :

  1. Create new and empty nuxt project
npx create-nuxt-app algolia-nuxt-example
cd algolia-nuxt-example
npm install vue-instantsearch algoliasearch
  1. Add search.vue in my app and follow Instructions in your doc (mix ssr nuxt implementation and nuxt routing /pages/search.vue
<template>
  <ais-instant-search-ssr>
    <ais-refinement-list attribute="brand" />
    <ais-hits>
      <template slot="item" slot-scope="{ item }">
        <p>
          <ais-highlight attribute="name" :hit="item" />
        </p>
        <p>
          <ais-highlight attribute="brand" :hit="item" />
        </p>
      </template>
    </ais-hits>
  </ais-instant-search-ssr>
</template>

<script>
import { AisInstantSearchSsr, AisRefinementList, AisHits, AisHighlight, createServerRootMixin } from 'vue-instantsearch';
import algoliasearch from 'algoliasearch/lite';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

function nuxtRouter(vueRouter) {
  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;
      }
      vueRouter.push({
        query: routeState,
      });
    },
    createURL(routeState) {
      return vueRouter.resolve({
        query: routeState,
      }).href;
    },
    onUpdate(cb) {
      if (typeof window === 'undefined') return;

      this._onPopState = event => {
        const routeState = event.state;
        // On initial load, the state is read from the URL without
        // update. Therefore, the state object isn't present. In this
        // case, we fallback and read the URL.
        if (!routeState) {
          cb(this.read());
        } else {
          cb(routeState);
        }
      };
      window.addEventListener('popstate', this._onPopState);
    },
    dispose() {
      if (typeof window === 'undefined') return;

      window.removeEventListener('popstate', this._onPopState);
    },
  };
}

export default {
  data() {
    // Create it in `data` to access the Vue Router
    const mixin = createServerRootMixin({
      searchClient,
      indexName: 'instant_search',
      routing: {
        router: nuxtRouter(this.$router),
      },
    });
    return {
      ...mixin.data(),
    };
  },
  provide() {
    return {
      // Provide the InstantSearch instance for SSR
      $_ais_ssrInstantSearchInstance: this.instantsearch,
    };
  },
  serverPrefetch() {
    return this.instantsearch.findResultsState(this).then(algoliaState => {
      this.$ssrContext.nuxt.algoliaState = algoliaState;
    });
  },
  beforeMount() {
    const results =
      (this.$nuxt.context && this.$nuxt.context.nuxtState.algoliaState) ||
      window.__NUXT__.algoliaState;

    this.instantsearch.hydrate(results);

    // Remove the SSR state so it can't be applied again by mistake
    delete this.$nuxt.context.nuxtState.algoliaState;
    delete window.__NUXT__.algoliaState;
  },
  components: {
    AisInstantSearchSsr,
    AisRefinementList,
    AisHits,
    AisHighlight
  },
};
</script>

nuxt.config.js

router: {
    parseQuery(queryString) {
      return require('qs').parse(queryString);
    },
    stringifyQuery(object) {
      var queryString = require('qs').stringify(object);
      return queryString ? '?' + queryString : '';
    },
  },

  build: {
    transpile: ['vue-instantsearch', 'instantsearch.js/es']
  }

Am I making an implementation error ? Thanks if you can help me, that’s make me crazy!

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:14 (3 by maintainers)

github_iconTop GitHub Comments

4reactions
elkicocommented, Apr 29, 2021

We found a solution.

First, the problem is some kind of race-condition, i guess. The vue-instantsearch callback inside the custom-routers onUpdate (event when doing history back) looks like this: instantSearchInstance.setUiState(stateMapping.routeToState(route));

Problem is that the UiState got changed but the search is not triggered again.

How i solved it: Adding a watcher $route.query that triggers the search manually.

Adding this watcher would trigger the search every time the route changes ==> also every time a refinement get’s applied and put into the URL. That would not be bad because vue-instantsearch caches the requests and does not fire them again.

Anyways, to avoid the possibility to trigger request twice, i added a dummy query-parameter when History-Back is used to only trigger the search manually when History-Back was done.

Custom-Router

onUpdate(callBack) {
      if (typeof window === 'undefined') {
        return
      }

      this._onPopState = () => {
        // this is a workaround because of a race-condition: When the UiState get's updated via the callback
        // the search is not triggered again.
        const query = { ...this.read(), hb: Date.now() }
        vueRouter.replace({
          query
        })

        // This should be enough, if the race-condition would not be there
        callBack(query)
      }

      window.addEventListener('popstate', this._onPopState)
    },

    /* remove any listeners */
    dispose() {
      if (typeof window === 'undefined') {
        return
      }

      window.removeEventListener('popstate', this._onPopState)
    }

Watcher

watch: {
    '$route.query': {
      handler: async function(newValue, oldValue) {
        if (newValue.hb) {
          const query = { ...newValue }
          delete query.hb
          await this.$router.replace({ query })
          this.state.instantSearchInstance.helper.search()
        }
      }
    }
  }

For now this is the solution we will go with.

1reaction
yoannk-devcommented, Apr 19, 2021

Hi @tkrugg,

I just updated the codesandbox with my initial code, which is the mix between this documentation (SSR Nuxt) and this one (Nuxt + Routing).

Read more comments on GitHub >

github_iconTop Results From Across the Web

With Nuxt + ssr + routing, bug with the browser ... - Bountysource
Hi the team,. I am trying to implement vue-instantsearch with nuxt + ssr + routing. SSR is OK. If I refine brand attributes...
Read more >
SSR-Routing: Custom-/Vue-Router: History Back functionality ...
Hi, i enabled the routing functionality for NuxtJS like described in this ... + ssr + routing, bug with the browser previous/next buttons...
Read more >
Server Side Rendering - Nuxt
Server-side rendering (SSR), is the ability of an application to contribute by displaying the web-page on the server instead of rendering it in...
Read more >
Intercepting back button on mobile in Vue/Nuxt/Vuetify apps
Remember, we have SSR and we want to serve correct HTML immediately - before we get to the browser and can question its...
Read more >
useHooks - Easy to understand React Hook recipes
We bring you easy to understand React Hook code recipes so you can learn how React hooks work and feel more comfortable writing...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found