SlideShare a Scribd company logo
Angular Universal
Be SEO, CDN & user friendly!
Maciej Treder
AKAMAI TECHNOLOGIES
@maciejtreder
@maciejtreder
• Kraków, Poland
• Senior Software Development Engineer in Test 

Akamai Technologies
• Angular passionate
• Open source contributor (founder of @ng-toolkit)
• Articles writer
Outline
• Why SPAs are not SEO?
• Server-side rendering concept
• Working with server-side and browser-side code
• API calls performance
• What’s more? & Deployment
• Prerendering & Summary
ng build
• Ahead of Time compilation
We are ready for —prod!
• Ahead of Time compilation
• Minified
• Tree-shaked
ng build vs. —prod
Common SPA problem
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.html [L]
</IfModule>
.htaccess
Common SPA problem
GET /
GET /anotherPage
index.html
G
ET
/subpage
G
ET
/contact
GET /home
Common SPA problem
GET / GET /anotherPage
Common SPA problem
Outline
• Why SPAs are not SEO?
• Server-side rendering concept
• Working with server-side and browser-side code
• API calls performance
• What’s more? & Deployment
• Prerendering & Summary
Server Side Rendering
GET /
GET /anotherPage
Is it worth?
curl localhost:8080
<!DOCTYPE html><html lang="en"><head>
<meta charset="utf-8">
<title>SomeProject</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.3ff695c00d717f2d2a11.css"><style ng-transition="app-root">
/*# sourceMappingURL=data:application/
json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzcmMvYXBwL2FwcC5jb21wb25lbnQuY3NzIn0= */</
style></head>
<body>
<script type="text/javascript" src="runtime.26209474bfa8dc87a77c.js"></script><script type="text/javascript" src="es2015-
polyfills.c5dd28b362270c767b34.js" nomodule=""></script><script type="text/javascript" src="polyfills.8bbb231b43165d65d357.js"></
script><script type="text/javascript" src="main.8a9128130a3a38dd7ee5.js"></script>
<script id="app-root-state" type="application/json">{}</script></body></html>
<app-root _nghost-sc0="" ng-version="7.2.9"><div _ngcontent-sc0="" style="text-align:center"><h1 _ngcontent-sc0=""> Welcome
to someProject! </h1><img _ngcontent-sc0="" alt="Angular Logo" src="data:image/
svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERD
AwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJ
GIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1
Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogI
Dwvc3ZnPg==" width="300"></div><h2 _ngcontent-sc0="">Here are some links to help you start: </h2><ul _ngcontent-sc0=""><li
_ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="https://angular.io/tutorial" rel="noopener"
target="_blank">Tour of Heroes</a></h2></li><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="https://
angular.io/cli" rel="noopener" target="_blank">CLI Documentation</a></h2></li><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a
_ngcontent-sc0="" href="https://blog.angular.io/" rel="noopener" target="_blank">Angular blog</a></h2></li></ul></app-root>
Is it worth?
—prod vs. universal
Angular Universal
Load HTML Bootstrap
Load HTML Bootstrap
SSR
NO SSR
First meaningful paint
First meaningful paint
How to start?
Official guide
https://angular.io/guide/universal
ng-toolkit
https://github.com/maciejtreder/ng-toolkit
ng add @nguniversal/express-engine —clientProject myProject
CREATE src/main.server.ts (220 bytes)
CREATE src/app/app.server.module.ts (318 bytes)
CREATE src/tsconfig.server.json (219 bytes)
CREATE webpack.server.config.js (1360 bytes)
CREATE server.ts (1500 bytes)
UPDATE package.json (1876 bytes)
UPDATE angular.json (4411 bytes)
UPDATE src/main.ts (432 bytes)
UPDATE src/app/app.module.ts (359 bytes)
Adjust your modules
app.module.tsapp.server.module.ts
@NgModule({
bootstrap: [AppComponent],
imports: [
BrowserModule.withServerTransition({appId: 'my-app'}),
//other imports
],
})
export class AppModule {}
import {NgModule} from '@angular/core';
import {ServerModule} from '@angular/platform-server';
import {ModuleMapLoaderModule} from
‘@nguniversal/module-map-ngfactory-loader';
import {AppModule} from './app.module';
import {AppComponent} from './app.component';
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
Adjust your modules
Official guide
app.module.tsapp.server.module.ts
@NgModule({
declarations: [AppComponent],
imports: [
//common imports
]
})
export class AppModule {}
import {NgModule} from '@angular/core';
import {ServerModule} from '@angular/platform-server';
import {ModuleMapLoaderModule} from
‘@nguniversal/module-map-ngfactory-loader';
import {AppModule} from './app.module';
import {AppComponent} from './app.component';
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule,
//server specific imports
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
app.browser.module.ts
@NgModule({
bootstrap: [AppComponent],
imports: [
AppModule,
BrowserModule.withServerTransition({appId: 'my-app'}),
//browser specific imports
]
})
export class AppModule {}
//browser specific imports
//server specific imports
ng add @ng-toolkit/universal
CREATE local.js (248 bytes)
CREATE server.ts (1546 bytes)
CREATE webpack.server.config.js (1214 bytes)
CREATE src/main.server.ts (249 bytes)
CREATE src/tsconfig.server.json (485 bytes)
CREATE src/app/app.browser.module.ts (395 bytes)
CREATE src/app/app.server.module.ts (788 bytes)
CREATE ng-toolkit.json (95 bytes)
UPDATE package.json (1840 bytes)
UPDATE angular.json (4022 bytes)
UPDATE src/app/app.module.ts (417 bytes)
UPDATE src/main.ts (447 bytes)
And let’s go!
• npm run build:prod
• npm run server
Date: 2018-11-21T13:04:33.302Z
Hash: 1a82cb687d2e22b5d12b
Time: 10752ms
chunk {0} runtime.ec2944dd8b20ec099bf3.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main.09093ffa4ad7f66bc6ff.js (main) 169 kB [initial] [rendered]
chunk {2} polyfills.c6871e56cb80756a5498.js (polyfills) 37.5 kB [initial] [rendered]
chunk {3} styles.3bb2a9d4949b7dc120a9.css (styles) 0 bytes [initial] [rendered]
> my-app@0.0.0 server /Users/mtreder/myApp
> node local.js
Listening on: http://localhost:8080Listening on: http://localhost:8080
Under the hood
export const app = express();
app.use(compression());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main');
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
server.ts
Under the hood
app.get('/*', (req, res) => {
res.render('index', {req, res}, (err, html) => {
if (html) {
res.send(html);
} else {
console.error(err);
res.send(err);
}
});
});
server.ts
Under the hood
app.set('view engine', 'html');
app.set('views', './dist/browser');
app.get('*.*', express.static('./dist/browser', {
maxAge: '1y'
}));
server.ts
Server Side Rendering
GET /
GET /anotherPage
Outline
• Why SPAs are not SEO?
• Server-side rendering concept
• Working with server-side and browser-side code
• API calls performance
• What’s more? & Deployment
• Prerendering & Summary
Browser vs Server• document
• window
• navigator
• file system
• request
Browser vs Server
public ngOnInit(): void {
console.log(window.navigator.language);
}
Listening on: http://localhost:8080
ERROR ReferenceError: window is not defined
at AppComponent.module.exports../src/app/app.component.ts.AppComponent.ngOnInit
(/Users/mtreder/myApp/dist/server.js:118857:21)
at checkAndUpdateDirectiveInline (/Users/mtreder/myApp/dist/server.js:19504:19)
at checkAndUpdateNodeInline (/Users/mtreder/myApp/dist/server.js:20768:20)
at checkAndUpdateNode (/Users/mtreder/myApp/dist/server.js:20730:16)
at prodCheckAndUpdateNode (/Users/mtreder/myApp/dist/server.js:21271:5)
at Object.updateDirectives (/Users/mtreder/myApp/dist/server.js:118833:264)
at Object.updateDirectives (/Users/mtreder/myApp/dist/server.js:21059:72)
at Object.checkAndUpdateView (/Users/mtreder/myApp/dist/server.js:20712:14)
at ViewRef_.module.exports.ViewRef_.detectChanges (/Users/mtreder/myApp/dist/
server.js:19093:22)
at /Users/mtreder/myApp/dist/server.js:15755:63
ERROR ReferenceError: window is not defined
is server? is browser?
import { Component, Inject, PLATFORM_ID, OnInit } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
@Component({
selector: 'home-view',
templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {
constructor( private platformId) {}
public ngOnInit(): void {
if ( ) {
console.log('I am executed in the browser!’);
// window.url can be reached here
}
if (isPlatformServer(this.platformId)) {
console.log('I am executed in the server!’);
// window.url CAN’T be reached here
}
}
}
@Inject(PLATFORM_ID)
isPlatformBrowser(this.platformId)
isPlatformServer(this.platformId)
Wrapper services
• Determine if we are in the browser or server
• Retrieve window or request object
• Create ‘mock’ window from request object if necessary
REQUEST
import { Component, OnInit, Inject, PLATFORM_ID, Optional } from ‘@angular/core’;
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { isPlatformServer } from '@angular/common';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {
constructor(
@Inject(REQUEST) private request: any,
@Inject(PLATFORM_ID) private platformId: any) {}
public ngOnInit(): void {
if (isPlatformServer(this.platformId)) {
console.log(this.request.headers);
}
}
}
import { REQUEST } from '@nguniversal/express-engine/tokens';
@Optional @Inject(REQUEST) private request: any,
console.log(this.request.headers);
REQUEST
Listening on: http://localhost:8080
{ host: 'localhost:8080',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9,ru;q=0.8',
'if-none-match': 'W/"40e-JviTST4QyiABJz2Lg+QxzZtiXv8"' }
'accept-language': 'en-US,en;q=0.9,ru;q=0.8',
Wrapper Service
@Injectable()
export class WindowService {
private _window: Window;
constructor(@Inject(PLATFORM_ID) platformId: any, @Optional @Inject(REQUEST) private request: any ) {
if (isPlatformServer(platformId)) {
this._window = {
navigator: {
language: this.request.headers['accept-language']
},
URL: this.request.headers.host + '' + this.request.url
};
} else {
this._window = window;
}
}
get nativeWindow(): any {
return this._window;
}
}
Wrapper Service
import { Component , OnInit, Inject} from '@angular/core';
import { WINDOW } from '@ng-toolkit/universal';
export class AppComponent implements OnInit {
constructor(@Inject(WINDOW) private window: Window) {}
public ngOnInit(): void {
console.log(window.navigator.language);
}
}
app.component.ts
console.log(this.window.navigator.language);
Wrapper Service
import { NgtUniversalModule } from '@ng-toolkit/universal';
import { NgModule } from '@angular/core';
@NgModule({
imports:[
NgtUniversalModule
]
})
export class AppModule { }
app.module.ts
Server/Browser specific code
@ngx-translate
• i18n module
• multiple ways of usage
{{‘Welcome to' | translate}}
<div [innerHTML]="'HELLO' | translate"></div>
{
"Welcome to": "Ласкаво просимо в"
}
uk.json
Server/Browser specific elements
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http);
}
@NgModule({
imports:[
TranslateModule.forRoot({
loader: {provide: TranslateLoader, useFactory: HttpLoaderFactory,
deps: [httpClient]}
})
]
})
export class AppBrowserModule {}
export function httpLoaderFactory(http: HttpClient): TranslateLoader {
return new TranslateHttpLoader(http);
}
HttpLoaderFactory,
app.browser.module.ts
Server/Browser specific elements
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Observable, Observer } from 'rxjs';
import * as fs from 'fs';
export function universalLoader(): TranslateLoader {
return {
getTranslation: (lang: string) => {
return Observable.create((observer: Observer<any>) => {
observer.next(JSON.parse(fs.readFileSync(`./dist/assets/i18n/${lang}.json`, 'utf8')));
observer.complete();
});
}
} as TranslateLoader;
}
@NgModule({
imports:[
TranslateModule.forRoot({
loader: {provide: TranslateLoader, useFactory: universalLoader}
})
]
})
export class AppServerModule {}
export function universalLoader(): TranslateLoader {
app.server.module.ts
universalLoader }
Server/Browser specific elements
import { Component, OnInit, Inject } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { WINDOW } from '@ng-toolkit/universal';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {
constructor(
@Inject(WINDOW) private window: Window,
private translateService: TranslateService
) {}
public ngOnInit(): void {
this.translateService.use(this.window.navigator.language);
}
}
app.component.ts
this.translateService.use(this.window.navigator.language);
Server/Browser specific elements
https://www.twilio.com/blog/create-search-engine-friendly-internationalized-web-apps-
angular-universal-ngx-translate
Outline
• Why SPAs are not SEO?
• Server-side rendering concept
• Working with server-side and browser-side code
• API calls performance
• What’s more? & Deployment
• Prerendering & Summary
DRY(c)
Don’t repeat your calls
export class AppComponent implements OnInit {
public post: Observable<any>;
constructor(private httpClient: HttpClient) {}
public ngOnInit(): void {
this.post = this.httpClient.get('https://jsonplaceholder.typicode.com/posts/1');
}
}
2
1
3
4
5 6
external.api.com
external.api.com
my-website.com
DRY(c)
Don’t repeat your calls
1
2
3
HttpCacheModule
npm install @nguniversal/common
import { NgtUniversalModule } from '@ng-toolkit/universal';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { TransferHttpCacheModule } from '@nguniversal/common';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports:[
CommonModule,
NgtUniversalModule,
TransferHttpCacheModule,
HttpClientModule
]
})
export class AppModule { }
TransferHttpCacheModule
TransferState
• ServerTransferStateModule (@angular/platform-server)
• BrowserTransferStateModule (@angular/platform-browser)
• get(key, fallbackValue)
• set(key, value)
• has(key)
• remove(key)
HTTP_INTERCEPTOR
• Provided in the AppModule
• Every http request made with HttpClient goes threw it
• Used to transform request or response ie:
• Adding authentication headers
HTTP_INTERCEPTOR
@Injectable()
export class ServerStateInterceptor implements HttpInterceptor {
    constructor(private _transferState: TransferState) {}
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(req).pipe(tap(event => {
            if (event instanceof HttpResponse) {
                this._transferState.set(makeStateKey(req.url), event.body);
            }
        }));
    }
}
HTTP_INTERCEPTOR
@Injectable()
export class BrowserStateInterceptor implements HttpInterceptor {
    constructor(private _transferState: TransferState) { }
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (req.method !== 'GET') {
            return next.handle(req);
        }
        const storedResponse: string = this._transferState.get(makeStateKey(req.url), null);
        if (storedResponse) {
            const response = new HttpResponse({ body: storedResponse, status: 200 });
this._transferState.remove(makeStateKey(req.url));
            return of(response);
        }
        return next.handle(req);
    }
}
HTTP_INTERCEPTOR
import {HTTP_INTERCEPTORS } from '@angular/common/http';
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: BrowserStateInterceptor,
multi: true,
}
]
import {HTTP_INTERCEPTORS } from '@angular/common/http';
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: ServerStateInterceptor,
multi: true,
}
]
Performance
export class RouteResolverService implements Resolve<any> {
constructor(
private httpClient: HttpClient,
@Inject(PLATFORM_ID) private platformId: any
) {}
public resolve(): Observable<any> {
}
}
const watchdog: Observable<number> = timer(500);
if (isPlatformBrowser(this.platformId)) {
return this.httpClient.get<any>('https://jsonplaceholder.typicode.com/posts/1');
}
return Observable.create(subject => {
this.httpClient.get<any>('https://jsonplaceholder.typicode.com/posts/1')
.subscribe(response => {
subject.next(response);
subject.complete();
});
})
.pipe(takeUntil(watchdog))
watchdog.subscribe(() => {
subject.next('timeout');
subject.complete()
})
0.5sec
0.5sec
DRY(c) & Performance
https://www.twilio.com/blog/faster-javascript-web-apps-angular-universal-transferstate-api-watchdog
Outline
• Why SPAs are not SEO?
• Server-side rendering concept
• Working with server-side and browser-side code
• API calls performance
• What’s more? & Deployment
• Prerendering & Summary
It’s not only SSR
const listFiles = (callBack) => {
return fs.readdir('./user_upload', callBack);
};
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP),
{provide: 'LIST_FILES', useValue: listFiles}
]
}));
constructor(
private http: HttpClient,
@Optional() @Inject('LIST_FILES') private listFiles: (callback) => void,
@Inject(PLATFORM_ID) private platformId: any,
private transferState: TransferState
) {
const transferKey: StateKey<string> = makeStateKey<string>('fileList');
if (isPlatformServer(this.platformId)) {
this.listFiles((err, files) => {
this.fileList = files;
this.transferState.set(transferKey, this.fileList);
});
} else {
this.fileList = this.transferState.get<string[]>(transferKey, []);
}
}
server.ts
someService.ts
It’s not only SSR
https://www.twilio.com/blog/transfer-files-data-javascript-applications-angular-node-js
Let’s go Serverless!
• Function as a Service
• Event-driven
• Scalable
• Pay for the up-time
Let’s go Serverless!
https://www.twilio.com/blog/angular-universal-javascript-node-js-aws-lambda
Outline
• Why SPAs are not SEO?
• Server-side rendering concept
• Working with server-side and browser-side code
• API calls performance
• What’s more? & Deployment
• Prerendering & Summary
Prerender
• Generating HTML files at a build time
• Can be hosted from traditional hosting (ie. AWS S3)
• Doesn’t perform dynamic request
• https://github.com/maciejtreder/angular-ssr-prerender
Alternative
Summary
server-side renderingprerenderng build —prod
SEO
Performance
Difficulty
SEO + external calls
Additional back-end logic
@maciejtreder

More Related Content

What's hot (20)

PDF
Python Ireland Nov 2009 Talk - Appengine
Python Ireland
 
PPTX
Angular 5
Bartłomiej Narożnik
 
PPTX
React Native: Introduction
InnerFood
 
PPTX
React native introduction
InnerFood
 
PDF
RESTful services and OAUTH protocol in IoT
Yakov Fain
 
PDF
AngularJS - TechTalk 3/2/2014
Spyros Ioakeimidis
 
PDF
Introduction to React Native Workshop
Ignacio Martín
 
PDF
Technozaure - Angular2
Demey Emmanuel
 
PDF
ChtiJUG - Introduction à Angular2
Demey Emmanuel
 
PPTX
Cloud Endpoints _Polymer_ Material design by Martin Görner
European Innovation Academy
 
PDF
Angular 4 for Java Developers
Yakov Fain
 
PDF
React Native - Workshop
Fellipe Chagas
 
PDF
AEM Best Practices for Component Development
Gabriel Walt
 
PDF
How to React Native
Dmitry Ulyanov
 
PDF
Lessons from a year of building apps with React Native
Ryan Boland
 
PDF
Angular genericforms2
Eliran Eliassy
 
PPTX
Creating a Plug-In Architecture
ondrejbalas
 
PPTX
Rapid prototyping and easy testing with ember cli mirage
Krzysztof Bialek
 
PPTX
Build single page applications using AngularJS on AEM
AdobeMarketingCloud
 
PDF
Boost your angular app with web workers
Enrique Oriol Bermúdez
 
Python Ireland Nov 2009 Talk - Appengine
Python Ireland
 
React Native: Introduction
InnerFood
 
React native introduction
InnerFood
 
RESTful services and OAUTH protocol in IoT
Yakov Fain
 
AngularJS - TechTalk 3/2/2014
Spyros Ioakeimidis
 
Introduction to React Native Workshop
Ignacio Martín
 
Technozaure - Angular2
Demey Emmanuel
 
ChtiJUG - Introduction à Angular2
Demey Emmanuel
 
Cloud Endpoints _Polymer_ Material design by Martin Görner
European Innovation Academy
 
Angular 4 for Java Developers
Yakov Fain
 
React Native - Workshop
Fellipe Chagas
 
AEM Best Practices for Component Development
Gabriel Walt
 
How to React Native
Dmitry Ulyanov
 
Lessons from a year of building apps with React Native
Ryan Boland
 
Angular genericforms2
Eliran Eliassy
 
Creating a Plug-In Architecture
ondrejbalas
 
Rapid prototyping and easy testing with ember cli mirage
Krzysztof Bialek
 
Build single page applications using AngularJS on AEM
AdobeMarketingCloud
 
Boost your angular app with web workers
Enrique Oriol Bermúdez
 

Similar to Maciej Treder "Server-side rendering with Angular—be faster and more SEO, CDN and user-friendly!" (20)

PDF
Maciej Treder ''Angular Universal - a medicine for the Angular + SEO/CDN issu...
OdessaJS Conf
 
PDF
Angular for Java Enterprise Developers: Oracle Code One 2018
Loiane Groner
 
PPTX
I Know It Was MEAN, But I Cut the Cord to LAMP Anyway
All Things Open
 
PPTX
I Know It Was MEAN, But I Cut the Cord to LAMP Anyway
POSSCON
 
PPT
Scripting Oracle Develop 2007
Tugdual Grall
 
PDF
React loadable
George Bukhanov
 
PDF
Heroku pop-behind-the-sense
Ben Lin
 
PDF
using Mithril.js + postgREST to build and consume API's
Antônio Roberto Silva
 
PDF
A Gentle Introduction to Angular Schematics - Angular SF 2019
Matt Raible
 
PPTX
Reactive application using meteor
Sapna Upreti
 
PDF
Front End Development for Back End Developers - UberConf 2017
Matt Raible
 
PDF
Front End Development for Back End Developers - vJUG24 2017
Matt Raible
 
PDF
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
Igor Bronovskyy
 
PPTX
ASP .Net Core SPA Templates
Eamonn Boyle
 
PPTX
Building Web Apps with Express
Aaron Stannard
 
PDF
Use Angular Schematics to Simplify Your Life - Develop Denver 2019
Matt Raible
 
PPTX
Javascript first-class citizenery
toddbr
 
PDF
Front End Development for Back End Java Developers - Jfokus 2020
Matt Raible
 
PDF
Node azure
Emanuele DelBono
 
PPTX
AngularJS training - Day 1 - Basics: Why, What and basic features of AngularJS
murtazahaveliwala
 
Maciej Treder ''Angular Universal - a medicine for the Angular + SEO/CDN issu...
OdessaJS Conf
 
Angular for Java Enterprise Developers: Oracle Code One 2018
Loiane Groner
 
I Know It Was MEAN, But I Cut the Cord to LAMP Anyway
All Things Open
 
I Know It Was MEAN, But I Cut the Cord to LAMP Anyway
POSSCON
 
Scripting Oracle Develop 2007
Tugdual Grall
 
React loadable
George Bukhanov
 
Heroku pop-behind-the-sense
Ben Lin
 
using Mithril.js + postgREST to build and consume API's
Antônio Roberto Silva
 
A Gentle Introduction to Angular Schematics - Angular SF 2019
Matt Raible
 
Reactive application using meteor
Sapna Upreti
 
Front End Development for Back End Developers - UberConf 2017
Matt Raible
 
Front End Development for Back End Developers - vJUG24 2017
Matt Raible
 
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
Igor Bronovskyy
 
ASP .Net Core SPA Templates
Eamonn Boyle
 
Building Web Apps with Express
Aaron Stannard
 
Use Angular Schematics to Simplify Your Life - Develop Denver 2019
Matt Raible
 
Javascript first-class citizenery
toddbr
 
Front End Development for Back End Java Developers - Jfokus 2020
Matt Raible
 
Node azure
Emanuele DelBono
 
AngularJS training - Day 1 - Basics: Why, What and basic features of AngularJS
murtazahaveliwala
 
Ad

More from Fwdays (20)

PPTX
"Як ми переписали Сільпо на Angular", Євген Русаков
Fwdays
 
PDF
"AI Transformation: Directions and Challenges", Pavlo Shaternik
Fwdays
 
PDF
"Validation and Observability of AI Agents", Oleksandr Denisyuk
Fwdays
 
PPTX
"Autonomy of LLM Agents: Current State and Future Prospects", Oles` Petriv
Fwdays
 
PDF
"Beyond English: Navigating the Challenges of Building a Ukrainian-language R...
Fwdays
 
PPTX
"Co-Authoring with a Machine: What I Learned from Writing a Book on Generativ...
Fwdays
 
PPTX
"Human-AI Collaboration Models for Better Decisions, Faster Workflows, and Cr...
Fwdays
 
PDF
"AI is already here. What will happen to your team (and your role) tomorrow?"...
Fwdays
 
PPTX
"Is it worth investing in AI in 2025?", Alexander Sharko
Fwdays
 
PDF
''Taming Explosive Growth: Building Resilience in a Hyper-Scaled Financial Pl...
Fwdays
 
PDF
"Scaling in space and time with Temporal", Andriy Lupa.pdf
Fwdays
 
PDF
"Database isolation: how we deal with hundreds of direct connections to the d...
Fwdays
 
PDF
"Scaling in space and time with Temporal", Andriy Lupa .pdf
Fwdays
 
PPTX
"Provisioning via DOT-Chain: from catering to drone marketplaces", Volodymyr ...
Fwdays
 
PPTX
" Observability with Elasticsearch: Best Practices for High-Load Platform", A...
Fwdays
 
PPTX
"How to survive Black Friday: preparing e-commerce for a peak season", Yurii ...
Fwdays
 
PPTX
"Istio Ambient Mesh in production: our way from Sidecar to Sidecar-less",Hlib...
Fwdays
 
PPTX
" How to survive with 1 billion vectors and not sell a kidney: our low-cost c...
Fwdays
 
PPTX
"Confidential AI: zero trust concept", Hennadiy Karpov
Fwdays
 
PPTX
"Choosing Tensor Accelerators for Specific Tasks: Compute vs Memory Bound Mod...
Fwdays
 
"Як ми переписали Сільпо на Angular", Євген Русаков
Fwdays
 
"AI Transformation: Directions and Challenges", Pavlo Shaternik
Fwdays
 
"Validation and Observability of AI Agents", Oleksandr Denisyuk
Fwdays
 
"Autonomy of LLM Agents: Current State and Future Prospects", Oles` Petriv
Fwdays
 
"Beyond English: Navigating the Challenges of Building a Ukrainian-language R...
Fwdays
 
"Co-Authoring with a Machine: What I Learned from Writing a Book on Generativ...
Fwdays
 
"Human-AI Collaboration Models for Better Decisions, Faster Workflows, and Cr...
Fwdays
 
"AI is already here. What will happen to your team (and your role) tomorrow?"...
Fwdays
 
"Is it worth investing in AI in 2025?", Alexander Sharko
Fwdays
 
''Taming Explosive Growth: Building Resilience in a Hyper-Scaled Financial Pl...
Fwdays
 
"Scaling in space and time with Temporal", Andriy Lupa.pdf
Fwdays
 
"Database isolation: how we deal with hundreds of direct connections to the d...
Fwdays
 
"Scaling in space and time with Temporal", Andriy Lupa .pdf
Fwdays
 
"Provisioning via DOT-Chain: from catering to drone marketplaces", Volodymyr ...
Fwdays
 
" Observability with Elasticsearch: Best Practices for High-Load Platform", A...
Fwdays
 
"How to survive Black Friday: preparing e-commerce for a peak season", Yurii ...
Fwdays
 
"Istio Ambient Mesh in production: our way from Sidecar to Sidecar-less",Hlib...
Fwdays
 
" How to survive with 1 billion vectors and not sell a kidney: our low-cost c...
Fwdays
 
"Confidential AI: zero trust concept", Hennadiy Karpov
Fwdays
 
"Choosing Tensor Accelerators for Specific Tasks: Compute vs Memory Bound Mod...
Fwdays
 
Ad

Recently uploaded (20)

PPTX
WooCommerce Workshop: Bring Your Laptop
Laura Hartwig
 
PDF
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
PPTX
From Sci-Fi to Reality: Exploring AI Evolution
Svetlana Meissner
 
PPTX
Webinar: Introduction to LF Energy EVerest
DanBrown980551
 
PDF
POV_ Why Enterprises Need to Find Value in ZERO.pdf
darshakparmar
 
PDF
Transforming Utility Networks: Large-scale Data Migrations with FME
Safe Software
 
PPTX
AUTOMATION AND ROBOTICS IN PHARMA INDUSTRY.pptx
sameeraaabegumm
 
DOCX
Cryptography Quiz: test your knowledge of this important security concept.
Rajni Bhardwaj Grover
 
PDF
How Startups Are Growing Faster with App Developers in Australia.pdf
India App Developer
 
PPTX
COMPARISON OF RASTER ANALYSIS TOOLS OF QGIS AND ARCGIS
Sharanya Sarkar
 
PDF
Mastering Financial Management in Direct Selling
Epixel MLM Software
 
PDF
IoT-Powered Industrial Transformation – Smart Manufacturing to Connected Heal...
Rejig Digital
 
PPTX
OpenID AuthZEN - Analyst Briefing July 2025
David Brossard
 
PDF
Jak MŚP w Europie Środkowo-Wschodniej odnajdują się w świecie AI
dominikamizerska1
 
PPTX
Q2 FY26 Tableau User Group Leader Quarterly Call
lward7
 
PDF
Empower Inclusion Through Accessible Java Applications
Ana-Maria Mihalceanu
 
PDF
Transcript: New from BookNet Canada for 2025: BNC BiblioShare - Tech Forum 2025
BookNet Canada
 
PDF
Go Concurrency Real-World Patterns, Pitfalls, and Playground Battles.pdf
Emily Achieng
 
PDF
[Newgen] NewgenONE Marvin Brochure 1.pdf
darshakparmar
 
PDF
CIFDAQ Market Insights for July 7th 2025
CIFDAQ
 
WooCommerce Workshop: Bring Your Laptop
Laura Hartwig
 
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
From Sci-Fi to Reality: Exploring AI Evolution
Svetlana Meissner
 
Webinar: Introduction to LF Energy EVerest
DanBrown980551
 
POV_ Why Enterprises Need to Find Value in ZERO.pdf
darshakparmar
 
Transforming Utility Networks: Large-scale Data Migrations with FME
Safe Software
 
AUTOMATION AND ROBOTICS IN PHARMA INDUSTRY.pptx
sameeraaabegumm
 
Cryptography Quiz: test your knowledge of this important security concept.
Rajni Bhardwaj Grover
 
How Startups Are Growing Faster with App Developers in Australia.pdf
India App Developer
 
COMPARISON OF RASTER ANALYSIS TOOLS OF QGIS AND ARCGIS
Sharanya Sarkar
 
Mastering Financial Management in Direct Selling
Epixel MLM Software
 
IoT-Powered Industrial Transformation – Smart Manufacturing to Connected Heal...
Rejig Digital
 
OpenID AuthZEN - Analyst Briefing July 2025
David Brossard
 
Jak MŚP w Europie Środkowo-Wschodniej odnajdują się w świecie AI
dominikamizerska1
 
Q2 FY26 Tableau User Group Leader Quarterly Call
lward7
 
Empower Inclusion Through Accessible Java Applications
Ana-Maria Mihalceanu
 
Transcript: New from BookNet Canada for 2025: BNC BiblioShare - Tech Forum 2025
BookNet Canada
 
Go Concurrency Real-World Patterns, Pitfalls, and Playground Battles.pdf
Emily Achieng
 
[Newgen] NewgenONE Marvin Brochure 1.pdf
darshakparmar
 
CIFDAQ Market Insights for July 7th 2025
CIFDAQ
 

Maciej Treder "Server-side rendering with Angular—be faster and more SEO, CDN and user-friendly!"

  • 1. Angular Universal Be SEO, CDN & user friendly! Maciej Treder AKAMAI TECHNOLOGIES
  • 3. @maciejtreder • Kraków, Poland • Senior Software Development Engineer in Test 
 Akamai Technologies • Angular passionate • Open source contributor (founder of @ng-toolkit) • Articles writer
  • 4. Outline • Why SPAs are not SEO? • Server-side rendering concept • Working with server-side and browser-side code • API calls performance • What’s more? & Deployment • Prerendering & Summary
  • 5. ng build • Ahead of Time compilation
  • 6. We are ready for —prod! • Ahead of Time compilation • Minified • Tree-shaked
  • 7. ng build vs. —prod
  • 8. Common SPA problem <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.html [L] </IfModule> .htaccess
  • 9. Common SPA problem GET / GET /anotherPage index.html G ET /subpage G ET /contact GET /home
  • 10. Common SPA problem GET / GET /anotherPage
  • 12. Outline • Why SPAs are not SEO? • Server-side rendering concept • Working with server-side and browser-side code • API calls performance • What’s more? & Deployment • Prerendering & Summary
  • 13. Server Side Rendering GET / GET /anotherPage
  • 14. Is it worth? curl localhost:8080 <!DOCTYPE html><html lang="en"><head> <meta charset="utf-8"> <title>SomeProject</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="stylesheet" href="styles.3ff695c00d717f2d2a11.css"><style ng-transition="app-root"> /*# sourceMappingURL=data:application/ json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzcmMvYXBwL2FwcC5jb21wb25lbnQuY3NzIn0= */</ style></head> <body> <script type="text/javascript" src="runtime.26209474bfa8dc87a77c.js"></script><script type="text/javascript" src="es2015- polyfills.c5dd28b362270c767b34.js" nomodule=""></script><script type="text/javascript" src="polyfills.8bbb231b43165d65d357.js"></ script><script type="text/javascript" src="main.8a9128130a3a38dd7ee5.js"></script> <script id="app-root-state" type="application/json">{}</script></body></html> <app-root _nghost-sc0="" ng-version="7.2.9"><div _ngcontent-sc0="" style="text-align:center"><h1 _ngcontent-sc0=""> Welcome to someProject! </h1><img _ngcontent-sc0="" alt="Angular Logo" src="data:image/ svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERD AwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJ GIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1 Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogI Dwvc3ZnPg==" width="300"></div><h2 _ngcontent-sc0="">Here are some links to help you start: </h2><ul _ngcontent-sc0=""><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="https://angular.io/tutorial" rel="noopener" target="_blank">Tour of Heroes</a></h2></li><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="https:// angular.io/cli" rel="noopener" target="_blank">CLI Documentation</a></h2></li><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="https://blog.angular.io/" rel="noopener" target="_blank">Angular blog</a></h2></li></ul></app-root>
  • 17. Angular Universal Load HTML Bootstrap Load HTML Bootstrap SSR NO SSR First meaningful paint First meaningful paint
  • 18. How to start? Official guide https://angular.io/guide/universal ng-toolkit https://github.com/maciejtreder/ng-toolkit
  • 19. ng add @nguniversal/express-engine —clientProject myProject CREATE src/main.server.ts (220 bytes) CREATE src/app/app.server.module.ts (318 bytes) CREATE src/tsconfig.server.json (219 bytes) CREATE webpack.server.config.js (1360 bytes) CREATE server.ts (1500 bytes) UPDATE package.json (1876 bytes) UPDATE angular.json (4411 bytes) UPDATE src/main.ts (432 bytes) UPDATE src/app/app.module.ts (359 bytes)
  • 20. Adjust your modules app.module.tsapp.server.module.ts @NgModule({ bootstrap: [AppComponent], imports: [ BrowserModule.withServerTransition({appId: 'my-app'}), //other imports ], }) export class AppModule {} import {NgModule} from '@angular/core'; import {ServerModule} from '@angular/platform-server'; import {ModuleMapLoaderModule} from ‘@nguniversal/module-map-ngfactory-loader'; import {AppModule} from './app.module'; import {AppComponent} from './app.component'; @NgModule({ imports: [ AppModule, ServerModule, ModuleMapLoaderModule ], bootstrap: [AppComponent], }) export class AppServerModule {}
  • 21. Adjust your modules Official guide app.module.tsapp.server.module.ts @NgModule({ declarations: [AppComponent], imports: [ //common imports ] }) export class AppModule {} import {NgModule} from '@angular/core'; import {ServerModule} from '@angular/platform-server'; import {ModuleMapLoaderModule} from ‘@nguniversal/module-map-ngfactory-loader'; import {AppModule} from './app.module'; import {AppComponent} from './app.component'; @NgModule({ imports: [ AppModule, ServerModule, ModuleMapLoaderModule, //server specific imports ], bootstrap: [AppComponent], }) export class AppServerModule {} app.browser.module.ts @NgModule({ bootstrap: [AppComponent], imports: [ AppModule, BrowserModule.withServerTransition({appId: 'my-app'}), //browser specific imports ] }) export class AppModule {} //browser specific imports //server specific imports
  • 22. ng add @ng-toolkit/universal CREATE local.js (248 bytes) CREATE server.ts (1546 bytes) CREATE webpack.server.config.js (1214 bytes) CREATE src/main.server.ts (249 bytes) CREATE src/tsconfig.server.json (485 bytes) CREATE src/app/app.browser.module.ts (395 bytes) CREATE src/app/app.server.module.ts (788 bytes) CREATE ng-toolkit.json (95 bytes) UPDATE package.json (1840 bytes) UPDATE angular.json (4022 bytes) UPDATE src/app/app.module.ts (417 bytes) UPDATE src/main.ts (447 bytes)
  • 23. And let’s go! • npm run build:prod • npm run server Date: 2018-11-21T13:04:33.302Z Hash: 1a82cb687d2e22b5d12b Time: 10752ms chunk {0} runtime.ec2944dd8b20ec099bf3.js (runtime) 1.41 kB [entry] [rendered] chunk {1} main.09093ffa4ad7f66bc6ff.js (main) 169 kB [initial] [rendered] chunk {2} polyfills.c6871e56cb80756a5498.js (polyfills) 37.5 kB [initial] [rendered] chunk {3} styles.3bb2a9d4949b7dc120a9.css (styles) 0 bytes [initial] [rendered] > [email protected] server /Users/mtreder/myApp > node local.js Listening on: http://localhost:8080Listening on: http://localhost:8080
  • 24. Under the hood export const app = express(); app.use(compression()); app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main'); app.engine('html', ngExpressEngine({ bootstrap: AppServerModuleNgFactory, providers: [ provideModuleMap(LAZY_MODULE_MAP) ] })); server.ts
  • 25. Under the hood app.get('/*', (req, res) => { res.render('index', {req, res}, (err, html) => { if (html) { res.send(html); } else { console.error(err); res.send(err); } }); }); server.ts
  • 26. Under the hood app.set('view engine', 'html'); app.set('views', './dist/browser'); app.get('*.*', express.static('./dist/browser', { maxAge: '1y' })); server.ts
  • 27. Server Side Rendering GET / GET /anotherPage
  • 28. Outline • Why SPAs are not SEO? • Server-side rendering concept • Working with server-side and browser-side code • API calls performance • What’s more? & Deployment • Prerendering & Summary
  • 29. Browser vs Server• document • window • navigator • file system • request
  • 30. Browser vs Server public ngOnInit(): void { console.log(window.navigator.language); } Listening on: http://localhost:8080 ERROR ReferenceError: window is not defined at AppComponent.module.exports../src/app/app.component.ts.AppComponent.ngOnInit (/Users/mtreder/myApp/dist/server.js:118857:21) at checkAndUpdateDirectiveInline (/Users/mtreder/myApp/dist/server.js:19504:19) at checkAndUpdateNodeInline (/Users/mtreder/myApp/dist/server.js:20768:20) at checkAndUpdateNode (/Users/mtreder/myApp/dist/server.js:20730:16) at prodCheckAndUpdateNode (/Users/mtreder/myApp/dist/server.js:21271:5) at Object.updateDirectives (/Users/mtreder/myApp/dist/server.js:118833:264) at Object.updateDirectives (/Users/mtreder/myApp/dist/server.js:21059:72) at Object.checkAndUpdateView (/Users/mtreder/myApp/dist/server.js:20712:14) at ViewRef_.module.exports.ViewRef_.detectChanges (/Users/mtreder/myApp/dist/ server.js:19093:22) at /Users/mtreder/myApp/dist/server.js:15755:63 ERROR ReferenceError: window is not defined
  • 31. is server? is browser? import { Component, Inject, PLATFORM_ID, OnInit } from '@angular/core'; import { isPlatformBrowser, isPlatformServer } from '@angular/common'; @Component({ selector: 'home-view', templateUrl: './home.component.html' }) export class HomeComponent implements OnInit { constructor( private platformId) {} public ngOnInit(): void { if ( ) { console.log('I am executed in the browser!’); // window.url can be reached here } if (isPlatformServer(this.platformId)) { console.log('I am executed in the server!’); // window.url CAN’T be reached here } } } @Inject(PLATFORM_ID) isPlatformBrowser(this.platformId) isPlatformServer(this.platformId)
  • 32. Wrapper services • Determine if we are in the browser or server • Retrieve window or request object • Create ‘mock’ window from request object if necessary
  • 33. REQUEST import { Component, OnInit, Inject, PLATFORM_ID, Optional } from ‘@angular/core’; import { REQUEST } from '@nguniversal/express-engine/tokens'; import { isPlatformServer } from '@angular/common'; @Component({ selector: 'app-root', templateUrl: './app.component.html', }) export class AppComponent implements OnInit { constructor( @Inject(REQUEST) private request: any, @Inject(PLATFORM_ID) private platformId: any) {} public ngOnInit(): void { if (isPlatformServer(this.platformId)) { console.log(this.request.headers); } } } import { REQUEST } from '@nguniversal/express-engine/tokens'; @Optional @Inject(REQUEST) private request: any, console.log(this.request.headers);
  • 34. REQUEST Listening on: http://localhost:8080 { host: 'localhost:8080', connection: 'keep-alive', 'cache-control': 'max-age=0', 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36', accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'en-US,en;q=0.9,ru;q=0.8', 'if-none-match': 'W/"40e-JviTST4QyiABJz2Lg+QxzZtiXv8"' } 'accept-language': 'en-US,en;q=0.9,ru;q=0.8',
  • 35. Wrapper Service @Injectable() export class WindowService { private _window: Window; constructor(@Inject(PLATFORM_ID) platformId: any, @Optional @Inject(REQUEST) private request: any ) { if (isPlatformServer(platformId)) { this._window = { navigator: { language: this.request.headers['accept-language'] }, URL: this.request.headers.host + '' + this.request.url }; } else { this._window = window; } } get nativeWindow(): any { return this._window; } }
  • 36. Wrapper Service import { Component , OnInit, Inject} from '@angular/core'; import { WINDOW } from '@ng-toolkit/universal'; export class AppComponent implements OnInit { constructor(@Inject(WINDOW) private window: Window) {} public ngOnInit(): void { console.log(window.navigator.language); } } app.component.ts console.log(this.window.navigator.language);
  • 37. Wrapper Service import { NgtUniversalModule } from '@ng-toolkit/universal'; import { NgModule } from '@angular/core'; @NgModule({ imports:[ NgtUniversalModule ] }) export class AppModule { } app.module.ts
  • 38. Server/Browser specific code @ngx-translate • i18n module • multiple ways of usage {{‘Welcome to' | translate}} <div [innerHTML]="'HELLO' | translate"></div> { "Welcome to": "Ласкаво просимо в" } uk.json
  • 39. Server/Browser specific elements import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http); } @NgModule({ imports:[ TranslateModule.forRoot({ loader: {provide: TranslateLoader, useFactory: HttpLoaderFactory, deps: [httpClient]} }) ] }) export class AppBrowserModule {} export function httpLoaderFactory(http: HttpClient): TranslateLoader { return new TranslateHttpLoader(http); } HttpLoaderFactory, app.browser.module.ts
  • 40. Server/Browser specific elements import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { Observable, Observer } from 'rxjs'; import * as fs from 'fs'; export function universalLoader(): TranslateLoader { return { getTranslation: (lang: string) => { return Observable.create((observer: Observer<any>) => { observer.next(JSON.parse(fs.readFileSync(`./dist/assets/i18n/${lang}.json`, 'utf8'))); observer.complete(); }); } } as TranslateLoader; } @NgModule({ imports:[ TranslateModule.forRoot({ loader: {provide: TranslateLoader, useFactory: universalLoader} }) ] }) export class AppServerModule {} export function universalLoader(): TranslateLoader { app.server.module.ts universalLoader }
  • 41. Server/Browser specific elements import { Component, OnInit, Inject } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { WINDOW } from '@ng-toolkit/universal'; @Component({ selector: 'app-root', templateUrl: './app.component.html', }) export class AppComponent implements OnInit { constructor( @Inject(WINDOW) private window: Window, private translateService: TranslateService ) {} public ngOnInit(): void { this.translateService.use(this.window.navigator.language); } } app.component.ts this.translateService.use(this.window.navigator.language);
  • 43. Outline • Why SPAs are not SEO? • Server-side rendering concept • Working with server-side and browser-side code • API calls performance • What’s more? & Deployment • Prerendering & Summary
  • 44. DRY(c) Don’t repeat your calls export class AppComponent implements OnInit { public post: Observable<any>; constructor(private httpClient: HttpClient) {} public ngOnInit(): void { this.post = this.httpClient.get('https://jsonplaceholder.typicode.com/posts/1'); } }
  • 47. HttpCacheModule npm install @nguniversal/common import { NgtUniversalModule } from '@ng-toolkit/universal'; import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { TransferHttpCacheModule } from '@nguniversal/common'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports:[ CommonModule, NgtUniversalModule, TransferHttpCacheModule, HttpClientModule ] }) export class AppModule { } TransferHttpCacheModule
  • 48. TransferState • ServerTransferStateModule (@angular/platform-server) • BrowserTransferStateModule (@angular/platform-browser) • get(key, fallbackValue) • set(key, value) • has(key) • remove(key)
  • 49. HTTP_INTERCEPTOR • Provided in the AppModule • Every http request made with HttpClient goes threw it • Used to transform request or response ie: • Adding authentication headers
  • 50. HTTP_INTERCEPTOR @Injectable() export class ServerStateInterceptor implements HttpInterceptor {     constructor(private _transferState: TransferState) {}     intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {         return next.handle(req).pipe(tap(event => {             if (event instanceof HttpResponse) {                 this._transferState.set(makeStateKey(req.url), event.body);             }         }));     } }
  • 51. HTTP_INTERCEPTOR @Injectable() export class BrowserStateInterceptor implements HttpInterceptor {     constructor(private _transferState: TransferState) { }     intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {         if (req.method !== 'GET') {             return next.handle(req);         }         const storedResponse: string = this._transferState.get(makeStateKey(req.url), null);         if (storedResponse) {             const response = new HttpResponse({ body: storedResponse, status: 200 }); this._transferState.remove(makeStateKey(req.url));             return of(response);         }         return next.handle(req);     } }
  • 52. HTTP_INTERCEPTOR import {HTTP_INTERCEPTORS } from '@angular/common/http'; providers: [ { provide: HTTP_INTERCEPTORS, useClass: BrowserStateInterceptor, multi: true, } ] import {HTTP_INTERCEPTORS } from '@angular/common/http'; providers: [ { provide: HTTP_INTERCEPTORS, useClass: ServerStateInterceptor, multi: true, } ]
  • 53. Performance export class RouteResolverService implements Resolve<any> { constructor( private httpClient: HttpClient, @Inject(PLATFORM_ID) private platformId: any ) {} public resolve(): Observable<any> { } } const watchdog: Observable<number> = timer(500); if (isPlatformBrowser(this.platformId)) { return this.httpClient.get<any>('https://jsonplaceholder.typicode.com/posts/1'); } return Observable.create(subject => { this.httpClient.get<any>('https://jsonplaceholder.typicode.com/posts/1') .subscribe(response => { subject.next(response); subject.complete(); }); }) .pipe(takeUntil(watchdog)) watchdog.subscribe(() => { subject.next('timeout'); subject.complete() })
  • 57. Outline • Why SPAs are not SEO? • Server-side rendering concept • Working with server-side and browser-side code • API calls performance • What’s more? & Deployment • Prerendering & Summary
  • 58. It’s not only SSR const listFiles = (callBack) => { return fs.readdir('./user_upload', callBack); }; app.engine('html', ngExpressEngine({ bootstrap: AppServerModuleNgFactory, providers: [ provideModuleMap(LAZY_MODULE_MAP), {provide: 'LIST_FILES', useValue: listFiles} ] })); constructor( private http: HttpClient, @Optional() @Inject('LIST_FILES') private listFiles: (callback) => void, @Inject(PLATFORM_ID) private platformId: any, private transferState: TransferState ) { const transferKey: StateKey<string> = makeStateKey<string>('fileList'); if (isPlatformServer(this.platformId)) { this.listFiles((err, files) => { this.fileList = files; this.transferState.set(transferKey, this.fileList); }); } else { this.fileList = this.transferState.get<string[]>(transferKey, []); } } server.ts someService.ts
  • 59. It’s not only SSR https://www.twilio.com/blog/transfer-files-data-javascript-applications-angular-node-js
  • 60. Let’s go Serverless! • Function as a Service • Event-driven • Scalable • Pay for the up-time
  • 62. Outline • Why SPAs are not SEO? • Server-side rendering concept • Working with server-side and browser-side code • API calls performance • What’s more? & Deployment • Prerendering & Summary
  • 63. Prerender • Generating HTML files at a build time • Can be hosted from traditional hosting (ie. AWS S3) • Doesn’t perform dynamic request • https://github.com/maciejtreder/angular-ssr-prerender Alternative
  • 64. Summary server-side renderingprerenderng build —prod SEO Performance Difficulty SEO + external calls Additional back-end logic