Guides / Building Search UI / Going further

Server-side rendering with Angular InstantSearch

Angular InstantSearch is deprecated. Please use InstantSearch.js instead. For more information, see Migrating from Angular InstantSearch.

This is an advanced guide. If you’ve never used Angular InstantSearch, you should follow the getting-started first.

You can find the result of this guide on the Angular InstantSearch repository.

Angular InstantSearch is compatible with server-side rendering. This guide illustrates how to implement it with @angular/universal.

How server-side rendering works

Server-side rendering uses the TransferState modules from @angular/platform-browser and @angular/platform-server. This module caches the first request your server sends to Algolia to avoid re-triggering it when the Angular app starts on the client.

Set up an empty server-side rendering app

First, you need to generate a clean Angular app and enable server-side rendering.

1
2
ng new server-side-rendering
cd server-side-rendering && ng add @nguniversal/express-engine

Run the following command to ensure everything works. You can also inspect the source code of the page to check if the app was indeed rendered on the server, not just on the client.

1
2
3
npm build:ssr && npm serve:ssr
# or
yarn build:ssr && yarn serve:ssr

Install Angular InstantSearch

The goal is to have a working client-side implementation of Angular InstantSearch.

Install angular-instantsearch and its peer dependencies

1
npm install angular-instantsearch@4 instantsearch.js@4 algoliasearch@4

Import NgAisModule into the main app module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 // server-side-rendering/src/app/app.module.ts
 import { NgModule } from '@angular/core';
 import { BrowserModule } from '@angular/platform-browser';
+import { NgAisModule } from "angular-instantsearch";

 import { AppRoutingModule } from './app-routing.module';
 import { AppComponent } from './app.component';


 @NgModule({
   declarations: [
     AppComponent
   ],
   imports: [
     BrowserModule.withServerTransition({ appId: 'serverApp' }),
     AppRoutingModule,
+    NgAisModule.forRoot(),
   ],
   providers: [],
   bootstrap: [AppComponent]
 })
 export class AppModule { }

Create and expose the configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 import { Component } from '@angular/core';
+import algoliasearch from 'algoliasearch/lite';
+import { InstantSearchOptions } from "instantsearch.js/es/types";


 @Component({
   selector: 'app-root',
   templateUrl: './app.component.html',
   styleUrls: ['./app.component.css']
 })
 export class AppComponent {
   title = 'server-side-rendering';
+  config: InstantSearchOptions;

+  constructor() {
+    this.config = {
+      searchClient: algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey')
+      indexName: 'YourIndexName',
+    };
+  }
 }

Add some minimal markup and styles

1
2
3
4
5
6
<!-- in server-side-rendering/src/app/app.component.html -->
<router-outlet></router-outlet>
<ais-instantsearch [config]="config" >
  <ais-search-box></ais-search-box>
  <ais-hits></ais-hits>
</ais-instantsearch>
1
2
/* server-side-rendering/src/styles.css */
@import '~angular-instantsearch/bundles/instantsearch-theme-algolia.css';

Enable server-side rendering

Import TransferState and HTTPClient modules

These modules help you cache HTTP requests that the server sends to Algolia. This avoids duplicate requests during client-side hydration.

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
 // server-side-rendering/src/app/app.module.ts
 import { NgModule } from '@angular/core';
-import { BrowserModule } from '@angular/platform-browser';
+import { BrowserModule, BrowserTransferStateModule } from "@angular/platform-browser";
+import { HttpClientModule } from '@angular/common/http';
 import { NgAisModule } from "angular-instantsearch";
 import { AppRoutingModule } from './app-routing.module';
 import { AppComponent } from './app.component';


 @NgModule({
   declarations: [
     AppComponent
   ],
   imports: [
     BrowserModule.withServerTransition({ appId: 'serverApp' }),
+    BrowserTransferStateModule,
+    HttpClientModule,
     AppRoutingModule,
     NgAisModule.forRoot(),
   ],
   providers: [],
   bootstrap: [AppComponent]
 })
 export class AppModule { }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 // in server-side-rendering/src/app/app.server.module.ts
 import { NgModule } from '@angular/core';
-import { ServerModule } from '@angular/platform-server';
+import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';

 import { AppModule } from './app.module';
 import { AppComponent } from './app.component';
 @NgModule({
   imports: [
     AppModule,
     ServerModule,
+    ServerTransferStateModule,
   ],
   bootstrap: [AppComponent],
 })
 export class AppServerModule {}

Create a server-side rendering capable search client

The createSSRSearchClient function wraps the Algolia API client. It enables caching and state transfer from server to client.

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
31
32
33
34
35
 // server-side-rendering/src/app/app.component.ts
 import { Component } from '@angular/core';
 import { InstantSearchConfig } from "instantsearch.js/es/types";
+import { TransferState, makeStateKey } from '@angular/platform-browser';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
-import algoliasearch from 'algoliasearch/lite';
+import { createSSRSearchClient } from 'angular-instantsearch';

 @Component({
   selector: 'app-root',
   templateUrl: './app.component.html',
   styleUrls: ['./app.component.css'],
 })
 export class AppComponent {
   title = 'server-side-rendering';
   config: any;

   constructor(
+    private httpClient: HttpClient,
+    private transferState: TransferState,
   ) {
     this.config = {
-      searchClient: algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey')
+      searchClient: createSSRSearchClient({
+        appId: 'YourApplicationID',
+        apiKey: 'YourSearchOnlyAPIKey',
+        makeStateKey,
+        HttpHeaders,
+        transferState: this.transferState,
+        httpClient: this.httpClient,
+      }),
       indexName: 'YourIndexName'
     };
   }
 }

Enable InstantSearch routing

If you want your server to perform a search based on the URL, you need to extend the history router so it’s aware of the server-side rendering context.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// server-side-rendering/src/app/app.component.ts
-import { Component } from '@angular/core';
+import { Component, Inject, Optional } from '@angular/core';
import { TransferState, makeStateKey } from '@angular/platform-browser';
import { createSSRSearchClient } from 'angular-instantsearch';
import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { history } from 'instantsearch.js/es/lib/routers';
+import { REQUEST } from '@nguniversal/express-engine/tokens';
+import type { Request } from 'express';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = 'server-side-rendering';
  config: any;

  constructor(
    private httpClient: HttpClient,
    private transferState: TransferState,
+   @Optional() @Inject(REQUEST) protected request: Request
  ) {
    this.config = {
      searchClient: createSSRSearchClient({
        makeStateKey,
        HttpHeaders,
        appId: 'YourApplicationID',
        apiKey: 'YourSearchOnlyAPIKey',
        transferState: this.transferState,
        httpClient: this.httpClient,
      }),
      indexName: 'YourIndexName',
+     routing: history({
+       getLocation: () => {
+         if (this.request) {
+           const req = this.request;
+           const protocol =
+             (req.headers.referer && req.headers.referer.split('://')[0]) ||
+             'https';
+           const url = `${protocol}://${req.headers.host}${req.url}`;
+           return (new URL(url) as unknown) as Location;
+         }
+         return window.location;
+       },
+     }),
    };
  }
}

This lets the router read the URL from the server request via this.request and use it to perform a search based on the URL. On the client side, it will use window.location as usual.

You’re now ready to build and test server-side rendering.

1
npm run build:ssr && npm run serve:ssr

Open http://localhost:4000 in your browser.

If the app runs without errors, it means server-side rendering is working. You can now add more Angular InstantSearch widgets on your search page component and run:

You have now fully universal Angular InstantSearch application running on your server and browser! If you want to run the application directly we provide a complete example that you can find on the angular-instantsearch GitHub repository.

Did you find this page helpful?