SSR fails in next due to router.
b3nk3 opened this issue Β· comments
π 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
-
Open working sandbox: link and observe it working (/) character displayed from router.
-
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
- I agree to follow this project's Code of Conduct
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 #5475 (comment)
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.
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.
Hey @JanStevens - We've gone over to the App Router side and doing similar things using the JS SDK over their React one.
We are having the same issue with the latest version of the library