Server-Side Rendering with 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 { liteClient as 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 { liteClient as 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.