nuxt-modules / algolia

🔎 Algolia module for Nuxt
https://algolia.nuxtjs.org/
MIT License
189 stars 35 forks source link

Nuxt 3 Instantsearch SSR #132

Closed kreejzak closed 1 year ago

kreejzak commented 1 year ago

Hello, I just wanted to ask if it is possible to use this module for Nuxt 3 Instantsearch with SSR? I tried using <ais-instant-search-ssr> component, but it throws:

[nuxt] [request error] [unhandled] [500] createServerRootMixin is required when using SSR.                                                             

Also the Algolia's official Instantsearch example for Nuxt with SSR is only for Nuxt 2. So not much to learn there.

Is there any way to use Instantsearch with SSR, or should I use useAlgoliaFacetedSearch inside of useAsyncData for obtaining results and handle filtering manually with factes?

Thanks for your time!

Baroshem commented 1 year ago

Hey,

Thanks for rising this question. Vue Instantsearch does not work with SSR by default. In order to make it a mixin (or something similar for Vue 3) would be needed.

The official docs are using Nuxt 2 as the module is maintained by me and help of contributors, not by Algolia itself. Thats is why the examples there are not up to date but I already contacted Algolia team to add my docs there.

For now, I would recommend you to use useFacetedSearch or regular useAsyncAlgoliaSearch to get the data in SSR while we can think about supporting SSR with Vue Instantsearch in future releases :)

Baroshem commented 1 year ago

For that, you can use https://algolia.nuxtjs.org/getting-started/usage#useasyncalgoliasearch and pass as a third property of the param object the requestOptions filters https://www.algolia.com/doc/api-reference/api-methods/search/#method-param-requestoptions

kreejzak commented 1 year ago

Thank you for pointing me in the right direction! 🙏

Baroshem commented 1 year ago

Glad that I could help!

I will reopen the issue so that I could make some research about implementing the mixin to make Vue Instantsearch work with SSR :)

After all, it should work for SSR too so it is a valid feature request.

Baroshem commented 1 year ago

Hey @haroenv

Long time no see 😃

Have you maybe found a way to implement instantsearch for Vue 3 with SSR? 🙂

Haroenv commented 1 year ago

I tried this recently, but didn't manage to get reference to the component's render as you can do in non-setup style scripts. My attempt is here: https://github.com/algolia/doc-code-samples/pull/430

Baroshem commented 1 year ago

Thanks @Haroenv I will take a look at it in a few days

Baroshem commented 1 year ago

Havent managed to make it work either unfortunately. I think this can be an opportunity for some community contribution to this module as it is not very requested feature for all users but rather a nice to have

Strift commented 1 year ago

Hello there 👋

First, thanks a lot for your module and for sharing code samples. It was useful to look at the code, even though I'm not using Algolia. For the context, I'm using Meilisearch, which is an open source alternative. Meilisearch also uses InstantSearch components to implement the front-end.

I haven't yet come to a solution, but I've got a partially working sample. It might or might not be useful but I figured I would share it as your contributions helped me a lot in my work, so I hope I can return the favor.

Basically, my implementation works for server-side rendering the results, but not the other components that depends on the state (ie. filters, etc.) At least, that's my understanding of the behavior. Feel free to let me know if I'm wrong.

I made a local Nuxt module in one of the projects I'm working on, you can take a look here: https://github.com/meilisearch/ecommerce-demo/blob/main/components/organisms/MeiliSearchProvider.vue

EDIT: The link is now public as intended

Have a good day,

Baroshem commented 1 year ago

Hey @Strift

Thank you for this message! I am glad that you liked the module :)

Also thanks for the research on how to implement this using SSR.

I am affraid that I do not have access to this project.

Would you be interested in contributing to Algolia module and adding such provider for it to make VueInstantsearch work with SSR? :)

Bcavez commented 1 year ago

I tried this recently, but didn't manage to get reference to the component's render as you can do in non-setup style scripts. My attempt is here: algolia/doc-code-samples#430

Have you tried the getCurrentInstance function ?

EDIT: Made it kinda work doing this:

Using the code from your pull request, I removed the search.vue component and put everything in the page. Then I did this modification:

        app.ssrContext.payload.algolia = await instantsearch.findResultsState({
          // todo: what is the component? i don't see how to access the instance we currently are in
          component: getCurrentInstance(),
          // this relies on a patch in createServerRootMixin, as the component would be a child likely
          renderToString,
        }); 

The issue next was that the finderResultsState function (https://github.com/algolia/instantsearch/blob/master/packages/vue-instantsearch/src/util/createServerRootMixin.js) tries to access the instantsearch instance by doing this.instantsearch which doesn't work. If I modify the file by doing this

created() {
                instance = component.provides.$_ais_ssrInstantSearchInstance;

                instance.start();

Then it works (I still get some hydration mismatch on the widgets, but the search results is good). I just don't know how to make the finderResultsState function work properly.

Strift commented 1 year ago

Hey there,

Sorry for the link to private repository earlier. 😓 It's now public.

I'm using a solution similar to Bcavez's. The search results are SSR'd, but not the rest of the UI (facets, etc.)

I'm guessing this is related to me not using instantsearch.findResultsState().

@Baroshem Unfortunately, I currently do not have the time to contribute. But I will make sure to share what I learn here!

Baroshem commented 1 year ago

Hey @Strift

Sorry for no contact from my side. I was off due to sick leave.

Thanks for that! I am currently working on other modules as this one is quite stable now and does not require that many new features. So, I am looking at this feature as an opportunity for an open source activity for contributors to implement.

Laruxo commented 1 year ago

After a long debugging session I managed to make finderResultsState work. I hope this helps anyone struggling with this.

  onBeforeMount(() => {
    // Use data loaded on the server
    if (data.value) {
      instantsearch.hydrate(data.value.algoliaState)
    }
  })

  const { data } = await useAsyncData('algolia-state', async () => {
    const algoliaState = await instantsearch.findResultsState({
      // IMPORTANT: a component with access to `this.instantsearch` to be used by the createServerRootMixin code
      component: {
        $options: {
          data() {
            return { instantsearch }
          },
          render() {
            h('div')
          },
        },
      },
      renderToString,
    })

    return { algoliaState }
  })
Baroshem commented 1 year ago

Hey @Laruxo

Thanks for this piece of code. Would you be interested in contributing to the module by writing a docs section with this code sample? I am sure that several people could benefit from that :)

Laruxo commented 1 year ago

Hey @Baroshem, honestly, I am not using this module right now 😅 I think we should contribute to vue-instantsearch instead, it really needs to move away from that mixin. I will try to find some time for any of this a bit later. First, I really need to finish my migration to Nuxt 3 🙂

Baroshem commented 1 year ago

@Laruxo

I see but you just provided a way how users can have instantsearch in Nuxt 3 with SSR.

If you don't want to, I can add this code snippet to the docs so the future users could more easily find the answer for that :)

Fixing this in instantsearch is a good idea as well!

Laruxo commented 1 year ago

@Baroshem got inspired this morning, so I created a PR for the docs 🙂

Baroshem commented 1 year ago

Awesome!

I will review it in a few hours :)

Haroenv commented 1 year ago

@Laruxo, the component won't have access to the child widgets though, which is needed to have routing and state synced 🤔

Laruxo commented 1 year ago

@Haroenv you are right, I've just encountered that. Now, I am having some errors when I use initialUiState. Do you have any ideas how I could work around it?

Haroenv commented 1 year ago

The component you're mounting needs to be the component with all widgets mounted too.

Laruxo commented 1 year ago

So I managed to make it work for my project. For that, I needed to render AisRefinementList and AisSortBy components. I don't persist the state to the route for now, so I did not add the router.

@Haroenv do you know other cases for which we would need to add additional components?

  const { data: algoliaState, error } = await useAsyncData('algolia-state', async () => {
    return instantsearch.findResultsState({
      // IMPORTANT: fake component used by the createServerRootMixin code
      component: {
        $options: {
          components: { AisInstantSearchSsr, AisRefinementList, AisSortBy },
          data() {
            return { instantsearch }
          },
          provide: { $_ais_ssrInstantSearchInstance: instantsearch },
          render() {
            return h(AisInstantSearchSsr, null, () => [
              h(AisRefinementList, { attribute: 'metadata.languages.value' }),
              h(AisSortBy, { items: [{ value: indexName, label: '' }] }),
            ])
          },
        },
      },
      renderToString,
    })
  })
Haroenv commented 1 year ago

This would make sense, but if you're going that far, you can take the component, put it in a separate file, and use that to both render the html, as well as the server side rendering

Laruxo commented 1 year ago

I tried that, but my components use Nuxt composables (e.g. useNuxtApp) which causes the SSR to fail (specifically instantsearch.findResultsState). Maybe someone with a better understanding of Vue/Nuxt internals could work around that.

Bcavez commented 1 year ago

Sorry for posting in a closed issue but I cannot seem to make your solution work. Is there something missing in the snippet you provided @Laruxo ?

I'm getting this error:

[nitro] [dev] [unhandledRejection] <ref *1> ReferenceError: XMLHttpRequest is not defined        
    at eval (file:///home/projects/github-vukisz-jvkzve/algoliasearch/dist/algoliasearch-lite.esm.browser:818:39)
    at new Promise (<anonymous>)
    at Object.send (file:///home/projects/github-vukisz-jvkzve/algoliasearch/dist/algoliasearch-lite.esm.browser:817:20)
    at retry (file:///home/projects/github-vukisz-jvkzve/algoliasearch/dist/algoliasearch-lite.esm.browser:419:38)
    at eval (file:///home/projects/github-vukisz-jvkzve/algoliasearch/dist/algoliasearch-lite.esm.browser:432:16) {
  error: [Circular *1]
}

Here is a minimal repro: https://stackblitz.com/edit/github-vukisz-jvkzve?file=pages/search.vue

Laruxo commented 1 year ago

Sorry for the late reply @Bcavez. Your implementation seems correct, so I don't know why it does not work. The only strange thing that I can see from the error is the use of a browser build (algoliasearch-lite.esm.browser). In my case, we use a different build.

Dante3003 commented 1 year ago

@Bcavez have you found any solution to this problem?