Server Side Rendering enables us to pre-render the results on the server enabling better SEO for the app, and faster delivery of relevant results on an initial render to the users.

Reactivesearch internally runs on a redux store. With Server Side Rendering, you can handle the intial render when a user (or search engine crawler) first requests your app. To achieve the relevant results on an initial render, we need to pre-populate the redux store of ReactiveSearch.

ReactiveSearch offers SSR via initReactivesearch() method which takes three params:

  • an array of all components (with their set of props) we wish to render at the server side
  • url params
  • base component (reactivebase) props

Usage

This is a three-steps process:

First, import initReactivesearch:

import initReactivesearch from '@appbaseio/reactivesearch/lib/server';

Then, evaluate the initial state:

const initialState = await initReactivesearch(...);

and finally, pass the computed initial state to ReactiveBase component.

<ReactiveBase {...props} initialState={initialState}>
    ...
</ReactiveBase>

Example

We will build a simple booksearch app with next.js as an example to get started with:

Note that you can also use react-dom/server to implement SSR. We are using next.js here for simplicity.

Pre-requisites

Set up next.js - Refer docs here

Installation

Use the package manager of your choice to install reactivesearch:

yarn add @appbaseio/reactivesearch

Since reactivesearch internally uses emotion-js for styling, we will also need to install emotion-server:

yarn add emotion-server

We will also utilise babel-plugin-direct-import and babel-plugin-emotion primarily to generate an optimised build for our app. So make sure that you install:

yarn add -D babel-cli babel-core babel-loader babel-plugin-direct-import babel-plugin-emotion babel-plugin-transform-class-properties babel-plugin-transform-object-rest-spread babel-preset-env babel-preset-next babel-preset-react

Setup

Create .babelrc with the following configuration to generate an optimised build for your react app:

{
    "presets": ["next/babel"],
    "plugins": [
        "emotion",
        "transform-class-properties",
        "transform-object-rest-spread",
        [
            "direct-import",
            [
              "@appbaseio/reactivesearch",
              {
                "name": "@appbaseio/reactivesearch",
                "indexFile": "@appbaseio/reactivesearch/lib/index.es.js"
              }
            ]
        ]
    ]
}

Create an index.js file in the pages directory:

import initReactivesearch from '@appbaseio/reactivesearch/lib/server';

and we will also import the other relevant component from the reactivesearch library:

import { ReactiveBase, DataSearch, SelectedFilters, ReactiveList } from '@appbaseio/reactivesearch';

Set the props for all the components we are going to use:

const settings = {
    app: 'good-books-ds',
    credentials: 'nY6NNTZZ6:27b76b9f-18ea-456c-bc5e-3a5263ebc63d',
};

const dataSearchProps = {
    dataField: ['original_title', 'original_title.search'],
    categoryField: 'authors.raw',
    componentId: 'BookSensor',
    defaultSelected: 'Harry',
};

const reactiveListProps = {
    componentId: 'SearchResult',
    dataField: 'original_title.raw',
    className: 'result-list-container',
    from: 0,
    size: 5,
    renderItem: data => <BookCard key={data._id} data={data} />,
    react: {
        and: ['BookSensor'],
    },
};

Next step is to evaluate the initial state in the getInitialProps lifecycle method:

export default class Main extends Component {
    static async getInitialProps() {
        return {
            store: await initReactivesearch(
                [
                    {
                        ...dataSearchProps,
                        source: DataSearch,
                    },
                    {
                        ...reactiveListProps,
                        source: ReactiveList,
                    },
                ],
                null,
                settings,
            ),
        };
    }

    render() {
        return (
            <ReactiveBase {...settings} initialState={this.props.store}>
                <div className="row">
                    <div className="col">
                        <DataSearch {...dataSearchProps} />
                    </div>

                    <div className="col">
                        <SelectedFilters />
                        <ReactiveList {...reactiveListProps} />
                    </div>
                </div>
            </ReactiveBase>
        );
    }
}

Since ReactiveSearch runs on emotion-js internally, we will need to extract and inject styles properly by creating a _document.js:

import React from 'react';
import Document, { Head, Main, NextScript } from 'next/document';
import { extractCritical } from 'emotion-server';

export default class MyDocument extends Document {
    static getInitialProps({ renderPage }) {
        // for emotion-js
        const page = renderPage();
        const styles = extractCritical(page.html);
        return { ...page, ...styles };
    }

    constructor(props) {
        // for emotion-js
        super(props);
        const { __NEXT_DATA__, ids } = props;
        if (ids) {
            __NEXT_DATA__.ids = ids;
        }
    }

    render() {
        return (
            <html lang="en">
                <Head>
                    <link rel="stylesheet" href="/_next/static/style.css" />
                    <meta charSet="utf-8" />
                    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
                    {/* for emotion-js */}
                    <style dangerouslySetInnerHTML={{ __html: this.props.css }} />
                </Head>
                <body>
                    <Main />
                    <NextScript />
                </body>
            </html>
        );
    }
}

Finally, you can now run the dev server and catch the SSR in action.

Using with react-dom

You can also use ReactiveSearch with react-dom/server. Check out the example app for a detailed setup.

The concept remains the same, after gettting a request, we'll use initReactiveSearch to compute the results and populate ReactiveSearch's redux store. We'll use renderToString from react-dom/server and renderStylesToString from emotion-server to generate a html paint for our app. For example:

const html = renderStylesToString(
    renderToString(
        <App
            store={store}
            settings={settings}
            singleRangeProps={singleRangeProps}
            reactiveListProps={reactiveListProps}
        />,
    ),
);

We'll send this markup along with the computed store object so that it can be pre-loaded in client side while hydrating the app.

Example apps

We've covered all the existing components as an example app here: