algolia / instantsearch

⚡️ Libraries for building performant and instant search and recommend experiences with Algolia. Compatible with JavaScript, TypeScript, React and Vue.
https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js/
MIT License
3.66k stars 514 forks source link

SSR fails in next due to router. #5650

Open b3nk3 opened 1 year ago

b3nk3 commented 1 year ago

🐛 Current behavior

Server-side rendering with React InstantSearch Hooks works fine when using useRouter in /pages up until next version 13.0.2

However anything above this seems to break the functionality.

🔍 Steps to reproduce

  1. Open working sandbox: link and observe it working (/) character displayed from router.

  2. Open second sandbox (link below) with latest next version (13.4.4) and observe the complaint about router.

Live reproduction

https://codesandbox.io/p/sandbox/next13-4-4-wk6lj1

💭 Expected behavior

SSR works on latest next same as it does on 13.0.2

Package version

6.44.0

Operating system

macOS/Linux

Browser

all

Code of Conduct

avremel commented 1 year ago

This error seems to be triggered by getServerState which is called "outside" the app and NextJS is getting stricter about that.

A possible workaround:

  const router = serverState ? useRouter() : {};

Having router undefined when getServerState is called is OK. getServerState is only interested in mounting Algolia widgets to retrieve the UI state before SSR. To quote the docs:

As an optimization you can pass a minimal version of the application that only uses the hooks of the widgets used in the search, with the same options.

@dhayab addresses it here https://github.com/algolia/instantsearch/issues/5475#issuecomment-1425897322

Haroenv commented 1 year ago

Yes, unfortunately there's no way for us to provide a fake router (Next doesn't export the Context), otherwise that would also be a workaround.

JanStevens commented 1 year ago

I solved this by just doing it manually and build up the serverState. This solves a whole bunch of issues with hooks that depend on next router or any other provider. It seems odd that this solution is not documented as its way easier then dealing with a "minimal version of your application" (good luck keeping that in sync).

import {
  InstantSearch,
  InstantSearchServerState,
  InstantSearchSSRProvider,
} from 'react-instantsearch-hooks-web';

const SearchPage = ({ serverState }) => {
  return (
     <InstantSearchSSRProvider {...serverState}>
       <InstantSearch indexName={indexName} searchClient={searchClient}>
          {/* Whatever you want here */}
       </InstantSearch>
     </InstantSearchSSRProvider>
  );
};

export default SearchPage;

export const getStaticProps = async () => {
  // index here is an instance of SearchIndex retrieved from `searchClient.initIndex`
  const searchResults = await index.search('', {
    hitsPerPage: 10,
    // Ensure you also fetch the facets and filters you need
    facets: ['city', 'experience.title'],
    filters: `startDatetime >= ${todayTimeStamp}`,
    maxValuesPerFacet: 10,
    page: 0,
  });

  // We mimic the initialResults so everything can be populated SSR
  const serverState: InstantSearchServerState = {
    initialResults: {
      [index.indexName]: {
        state: {
          facets: [],
          index: index.indexName,
          // Important to have the following 2 properties so useRefinementList items are populated correctly 
          disjunctiveFacets: ['city', 'experience.title'],
          disjunctiveFacetsRefinements: { city: [], 'experience.title': [] },
          // Again repeating the same data
          hitsPerPage: 10,
          maxValuesPerFacet: 10,
        },
        results: [searchResults],
      },
    },
  };

  return {
    props: {
      serverState,
    },
    revalidate: Duration.FIVE_MIN,
  };
};

The above is a lot easier to work with since its framework agnostic and contains less magic. IMO Algolia could wrap this in a little helper function which returns the correctly typed server state.

b3nk3 commented 6 months ago

Hey @JanStevens - We've gone over to the App Router side and doing similar things using the JS SDK over their React one.

RodrigoTomeES commented 5 months ago

We are having the same issue with the latest version of the library