algolia / react-instantsearch

⚡️ Lightning-fast search for React and React Native applications, by Algolia.
https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/react/
MIT License
1.97k stars 386 forks source link

SSR - Do we need to render additional elements? #3681

Closed ayuhito closed 1 year ago

ayuhito commented 1 year ago

🐛 Description

Contrary to the docs, my Remix + Algolia SSR implementation uses the following code:

export const loader: LoaderFunction = async ({ request }) => {
  const serverUrl = request.url;

  const serverState = await getServerState(
      <InstantSearch
        searchClient={searchClient}
        indexName="prod_FONTS"
        routing={{
          router: history({
            getLocation() {
              if (typeof window === 'undefined') {
                return new URL(serverUrl);
              }

              return window.location;
            },
          }),
        }}
      />,
    { renderToString }
  );

  return json({
    serverState,
    serverUrl,
  });
};

export default function Index() {
  const { serverState, serverUrl } = useLoaderData();
  const { classes } = useStyles();

  return (
    <ContextProvider>
      <InstantSearchSSRProvider {...serverState}>
        <InstantSearch
          searchClient={searchClient}
          indexName="prod_FONTS"
          routing={{
            router: history({
              getLocation() {
                if (typeof window === 'undefined') {
                  return new URL(serverUrl);
                }

                return window.location;
              },
            }),
          }}
        >
          <Box className={classes.background}>
            <Box className={classes.container}>
              <Filters />
            </Box>
          </Box>
          <Box className={classes.container}>
            <InfiniteHits />
          </Box>
        </InstantSearch>
      </InstantSearchSSRProvider>
    </ContextProvider>
  );
}

The main difference being I don't render any additional elements/widgets in the Instantsearch SSR pass. When I followed the SSR docs, rendering the additional elements added tens of milliseconds to the response time which seemed unnecessary. Removing the components reduced response times and still works.

No SSR pass: ~15-25ms (to be expected)
SSR Pass with Mantine UI components (Emotion): ~80-90ms
SSR pass with no extra components: ~55-65ms

If we're only pulling state from the components, is it necessary to re-render the components in the Instantsearch SSR pass?

Haroenv commented 1 year ago

getServerState needs to have the same InstantSearch components in the same state as the regular render, otherwise the network request won't be the same between the server and the client. You can indeed render a more minimal application, but it does need all the widgets rendered.

ayuhito commented 1 year ago

I compared the serverState between the two variants (no widgets and widgets) and the only differences I found were related to the Highlight feature (state.highlightPreTag, state.highlightPostTag, results.hits[record]._highlightResult) being missing in the Instantsearch SSR pass. I've now disabled it fully using the useConfigure hook since I don't use it.

I'm using a lot of default settings that seem to work for this and not cause issues. Rather than rendering extra widgets because it is quite heavy to render a lot of components, especially with some slower CSS-in-JS solutions, could it not be possible to use a hook like useConfigure to adjust some settings instead to make the same network request for getServerState?

Haroenv commented 1 year ago

Yes, you can use useConfigure, or in this case probably useHits with the same options as your app everything will work as expected.

ayuhito commented 1 year ago

Awesome! That might be a nice optimisation to mention in the docs as it speeds things up a lot, but if not feel free to close this issue! Thank you!

Haroenv commented 1 year ago

Thanks for the suggestion, I've made a PR to the docs to mention this possibility. This issue will auto-close when that resolves :)

ayuhito commented 1 year ago

@Haroenv, this actually might lead to some issues, possibly related to the InfiniteHitsCache?

I ran into this bug where on initial load, the first page of hits will be server rendered and load fine. However, scrolling down and activating showMore() will reset the page and only load the 2nd page of hits. After loading the 2nd page, infinite hits works perfectly fine. But the first page of hits is completely lost.

Reverting this 'optimization' does fix the bug, so maybe something could be done to resolve this since I do think it's a way to improve SSR performance.

Haroenv commented 1 year ago

Do you have a reproduction in a sandbox somewhere? There should be a way to use the same cache instance across instances, but the cache gets cleared by default if the parameters don't match the ones for which the cache was written, so maybe the order or exact widgets is different

ayuhito commented 1 year ago

I'll see if I can get a reproduction for you next week.

It might be a little hard to reproduce because I noticed it after I made some big changes to my hit components.