Concepts / Building Search UI / Server-side rendering
Aug. 06, 2019

Server-side Rendering

Overview

When using server-side rendering, instead of having your browser download a minimal HTML page that JavaScript will fill, the initial content is generated on the server. Then, the browser will download a page with the HTML content already in place. Usually server-side rendering is considered to improve SEO and performances.

React InstantSearch is compatible with server-side rendering. We provide an API that can be used with any server-side rendering solution.

Native

We provide an API called createInstantSearch, available at react-instantsearch-dom/server.

When called, createInstantSearch returns:

  • InstantSearch: a component that accepts a resultsState prop containing the Algolia results retrieved by the function findResultsState.
  • findResultsState: a function to retrieve a resultsState object. The function takes two arguments:
    • a component that contains the InstantSearch widget and all the widgets that have to be mounted on the page.
    • a searchState object for computing the resultsState. Use it to pass a your initial state such as { searchState: { query: 'chair' } }. You’ll typically do this when dealing with URL sync and pulling the initial search query from the URL. Make sure App passes the initial searchState prop on to the InstantSearch component too.

We split this guide into three parts:

  • App.js is the server and browser shared main React component from your application
  • server.js is a simple Node http server, it’s the main server entry
  • browser.js is the main browser entry that ultimately gets compiled to bundle.js

App.js

App.js is usually the main entry point of your React application, it exports an component.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import algoliasearch from 'algoliasearch/lite';
import React, { Component } from 'react';
import { createInstantSearch } from 'react-instantsearch-dom/server';
import { SearchBox, Hits } from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'appId',
  'apiKey'
);

// Now we create a dedicated `InstantSearch` component
const { InstantSearch, findResultsState } = createInstantSearch();

class App extends Component {
  render() {
    return (
      <InstantSearch
        indexName="indexName"
        searchClient={searchClient}
        searchState={this.props.searchState}
        resultsState={this.props.resultsState}
      >
        <SearchBox />
        <Hits />
      </InstantSearch>
    );
  }
}

export { App, findResultsState };

Steps:

  • Use createInstantSearch() to get a findResultsState function and a dedicated <InstantSearch> component (instead of importing the one under react-instantsearch-dom)
  • Export <App> (to be used by browser and server code) and findResultsState (to be used by server code)

Notes:

  • Keep a reference to your dedicated InstantSearch component, do not re-create it at each render loop of your App component (This will fail: ... render() { return createInstantSearch(); })
  • If you want to use multiple <InstantSearch> components, then you need to create dedicated <InstantSearch> components for each of them.
  • If you are syncing the searchState to the url for proper routing, pass a searchState to the InstantSearch component.

server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React from 'react';
import { createServer } from 'http';
import { renderToString } from 'react-dom/server';
import { App, findResultsState } from './App';

const server = createServer(async (req, res) => {
  const searchState = { query: "chair" };
  const resultsState = await findResultsState(App, { searchState });
  const initialState = { searchState, resultsState };
  const html = renderToString(<App {...initialState} />);

  res.send(
    `
<!doctype html>
<html>
  <body>
    <h1>Awesome server-side rendered search</h1>
    <did id="root">${html}</div>
    <script>window.__APP_INITIAL_STATE__ = ${JSON.stringify(appInitialState)}</script>
    <script src="bundle.js"></script> <!-- this is the build of browser.js -->
  </body>
</html>
`
  );
});

server.listen(8080);

Notes:

  • You have to transpile (with Babel for example) your server-side code to be able to use JSX and import statements.
  • __APP_INITIAL_STATE__ will be used so that React ensures what was sent by the server matches what the browser expects (checksum).

browser.js

This is the last part that does the plumbing between server-side rendering and the start of the application on the browser.

1
2
3
4
5
6
7
8
import React from 'react';
import { render } from 'react-dom';
import { App } from './App';

render(
  <App {...window.__APP_INITIAL_STATE__} />,
  document.querySelector('#root')
);

Notes:

  • A request will still be sent to Algolia when React mounts your <App> in the browser.

👌 That’s it! You know the basics of doing a custom server-side implementation.

Express + ReactDOMServer

Express is a minimal and flexible Node.js web application framework. It’s widely adopted in the Node.js ecosystem. An example of React InstantSearch and Express is available here.

Next.js

Next.js is a framework for React specifically wrapping and hiding some of the more complicated parts of SSR. An example of React InstantSearch and Next.js is available here.

The createInstantSearch pattern

It might be a bit confusing why we cannot use directly the <InstantSearch> component you were previously using, this part details a bit why we took this approach.

React InstantSearch is a declarative API that programmatically builds an Algolia query. Based on every widget used, and their own options, we compute a set of parameters that should be sent to Algolia.

While doing browser rendering, we need to first render a Component tree alone (to compute every parameter) to then re-render it again with results. Unfortunately the React server-side rendering feature does not allow for such a pattern.

The only solution is to provide an API to get a set of results to then be passed down to an <InstantSearch> component as a prop.

As this can be confusing, you might have better ideas on how we could have implemented this; if so, reach out on our GitHub or on our discourse forum.

Did you find this page helpful?