Searchbox | SSR Support

Searchbox | SSR Support

Build SSR enabled search experiences with @appbaseio/react-searchbox

May 4, 2024Β·

4 min read

SSR is a much talked about concept nowadays in the Web Development space. And it should be considering all the benefits it offers.

In this blog, we talk about how we are supporting SSR, requiring minimal config from the user in our component library @appbaseio/react-searchbox to help build SEO friendly and performance optimized search apps.

Introduction to SSR

Server-side rendering (SSR), is the ability of an application to contribute by displaying the web page on the server instead of rendering it in the browser. The Server-side sends a fully rendered page to the client; the client’s JavaScript bundle takes over and allows the SPA framework to operate.

The below graphics should convey the conceptual foundation of SSR and CSR.

image.png

image.png

This one should be more relatable :) Text

Benefits of SSR -

  • The initial page of the website load is faster as there are fewer codes to render.
  • Good for minimal and static sites.
  • Search engines can crawl the site for better SEO.

Proof of concept πŸ’ͺ

The claim SSR is better than CSR in terms of performance is proof backed. πŸ’ͺ

We are attaching links to SSR and CSR versions of an application with concrete performance differences in terms of how they performed when tested with the Lighthouse tool.

--- attach examples here ---

The Library support for SSR

@appbase/react-searchbox supports SSR with minimal user input.

  • Client-side

    The user needs to provide just two props to the <SearchBase /> component.

    contextCollector: used by our library util method to compute the initial state at the server side. (injected automatically).

    initialState: the initial state of the app that is calculated at the server-side for hydration at client side.

const App = (props) => (
  <SearchBase
    index="good-books-ds"
    credentials="a03a1cb71321:75b6603d-9456-4a5a-af6b-a487b309eb61"
    url="https://appbase-demo-ansible-abxiydt-arc.searchbase.io"
    // below props are coming from the server
    contextCollector={props.contextCollector}
    initialState={props.initialState}
  >
    <SearchComponent
      id="result-component"
      highlight
      dataField="original_title"
      size={10}
    >
      {({ results, loading, size, setValue, setFrom }) => {
        return (
          <div className="result-list-container">
            {loading ? (
              <div>Loading Results ...</div>
            ) : (
              <div>
                {!results.data.length ? (
                  <div>No results found</div>
                ) : (
                  <p>
                    {results.numberOfResults} results found in {results.time}ms
                  </p>
                )}
                {results.data.map((item) => (
                  <div
                    className="book-header"
                    dangerouslySetInnerHTML={{
                      __html: item.original_title
                    }}
                  />
                ))}
              </div>
            )}
          </div>
        );
      }}
    </SearchComponent>
  </SearchBase>
);

export default App;
  • Server-Side

On the server-side code, the user imports a util method getServerResults()(..., ...) to compute the initial state of the App and passes this initial state back to the client-side.

getServerResults()(App, pageURL): the first param of the function receives the App component ref and the second param[optional] receives the URL string or query param object(should be parsed) to respect the URL query string.

Assuming Next.js used for SSR here.

import {  getServerResults } from '@appbaseio/react-searchbox';

// getServerSideProps method is run on server-side by Next.js
export async function getServerSideProps(context) {

  // calculating the initial state on server  
  let initialState = await getServerResults()(App, context.resolvedUrl);
  return {
    props: { initialState } // will be passed to the App component as props
  };
}

πŸŽ‰ That's it πŸ₯³

The Underlying Architecture πŸ‘·πŸ› πŸ“‹

Let's understand the underlying architecture and how this support is made possible.

The basic idea of SSR support for @appbaseio/react-searchbox is to perform any necessary API calls to the search client and compute the initial state of App, then redyhrate the client side with the initialState computed on the server-side.

To build the initial state of the app at server-side, the getServerResults()(App) renders the app tree on server side using ReactDOMServer.renderToStaticMarkup() method and injects the contextCollector.

The contextCollector is a method that collects the context of the App being rendered.

Based on the collected context, the relevant API calls to the search client are made and the initial state of the client-side app is computed.

This initialState is returned to the user.

// server-side code

let initialState = await getServerResults()(App, context.resolvedUrl);

// user passes this initialState to the client and injects it to <SearchBase /> as a prop

The user then has to just pass this initialState to the client-side and inject it in the <SearchBase /> component.

And that's it. Period.

Why we chose this architecture approach? 🧐

Our focus while integrating support for SSR in our library has been to minimize efforts from the user. As you might notice, with just two props, to the <SearchBase /> i.e., contextCollector and initialState you have a search application giving you a performant search experience.

Notice how we handle all the API calls ourselves behind the scenes and also the configuration is as less as possible. πŸ˜‰

In a nutshell, we bake everythingπŸ‘©β€πŸ³. Leaving the consumption 🀀 to the user.

Action Time 🦸 πŸ‘Š πŸ€› πŸ₯·

Here's a fully working example made using Next.js.

Check out another example we made using Express.js here.

Summary

Let's summarize what we have covered in this blog.