63
Summer. Sea. JavaScript. Angular Universal A medicine for the SEO/CDN issues Maciej Treder @maciejtreder

Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

  • Upload
    others

  • View
    7

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Summer. Sea. JavaScript.

Angular Universal A medicine for the SEO/CDN issues

Maciej Treder

@maciejtreder

Page 2: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Outline

• SPA pitfall

• Server-side rendering

• Server vs. Browser

• API optimization

• Deployment

• Prerendering & Summary

SPA pitfall SSR Server vs. Browser APIs Deploy Summary

Page 3: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

SPA pitfall

ng build

• Ahead of Time compilation

Server vs.

Page 4: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

—prod flag

• Ahead of Time compilation

• Minified

• Tree-shaked

SPA pitfallServer vs.

Page 5: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

ng build vs. —prod

SPA pitfallServer vs.

Page 6: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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

SPA pitfallServer vs.

Page 7: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

SPA Problem

GET /

GET /anotherPage

index.html

GET /s

ubpa

ge

GET /contact

GET /home

SPA pitfallServer vs.

Page 8: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

SPA Problem

GET / GET /anotherPage

SPA pitfallServer vs.

Page 9: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

SPA Problem

SPA pitfallServer vs.

Page 10: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Server Side Rendering

SSRServer vs.

Page 11: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Server Side Rendering

GET /GET /anotherPage

SSRServer vs.

Page 12: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==" 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>

SSRServer vs.

Page 13: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

SSRServer vs.

Page 14: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

—prod vs. universal

SSRServer vs.

Page 15: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

—prod vs. universal

Load HTML Bootstrap

Load HTML Bootstrap

SSR

NO SSR First meaningful paint

First meaningful paint

SSRServer vs.

Page 16: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

How to start?

Official guide

https://angular.io/guide/universal

ng-toolkit

https://github.com/maciejtreder/ng-toolkit

SSRServer vs.

Page 17: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

ng add @nguniversal/express-engine

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)

SSRServer vs.

Page 18: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Adjust your modulesapp.module.ts app.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 {}

SSRServer vs.

Page 19: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Adjust your modules

Official guide

app.module.ts app.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

SSRServer vs.

Page 20: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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)

SSRServer vs.

Page 21: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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:8080

SSRServer vs.

Page 22: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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

SSRServer vs.

Page 23: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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

SSRServer vs.

Page 24: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Under the hood

app.set('view engine', 'html'); app.set('views', './dist/browser');

app.get('*.*', express.static('./dist/browser', { maxAge: '1y' }));

server.ts

SSRServer vs.

Page 25: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Server Side Rendering

GET /GET /anotherPage

SSRServer vs.

Page 26: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

ng-toolkit

SSRServer vs.

Page 27: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Browser vs. Server

• document

• window

• navigator

• file system

• request

Server vs. Browser

Page 28: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Browser vs. Serverpublic 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

Server vs. Browser

Page 29: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

server? 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 } } }

Server vs. Browser

Page 30: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Wrapper Service

• Determine if we are in the browser or server

• Retrieve window or request object

• Create ‘mock’ window based on request object if necessary

Server vs. Browser

Page 31: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

REQUESTimport { 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);

Server vs. Browser

Page 32: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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',

Server vs. Browser

Page 33: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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 window(): any { return this._window; } }

Server vs. Browser

Page 34: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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);

Server vs. Browser

Page 35: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

@ng-toolkit/universal

import { NgtUniversalModule } from '@ng-toolkit/universal'; import { NgModule } from '@angular/core';

@NgModule({ imports:[ NgtUniversalModule ] }) export class AppModule { }

app.module.ts

Server vs. Browser

Page 36: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Server/Browser modules

@ngx-translate

• i18n module

• multiple ways of usage

{{‘Welcome to' | translate}}

<div [innerHTML]="'HELLO' | translate"></div>

{

"Welcome to": "Ласкаво просимо в"

}

uk.json

Server vs. Browser

Page 37: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Server/Browser modules

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 vs. Browser

Page 38: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Server/Browser modulesimport { 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 vs. Browser

Page 39: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Server/Browser modulesimport { 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 vs. Browser

Page 40: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

i18n with Universalhttps://www.twilio.com/blog/create-search-engine-friendly-internationalized-web-apps-angular-universal-ngx-translate

Server vs. Browser

Page 41: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Server vs. Browser

Page 42: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

API optimization

Server vs. APIs

Page 43: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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'); } }

Server vs. APIs

Page 44: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

2

1

3

45 6

external.api.com

Server vs. APIs

Page 45: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

external.api.com

my-website.com

12

3

Server vs. APIs

Page 46: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

HttpCacheModulenpm 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 { }

Server vs. APIs

Page 47: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

TransferState

• ServerTransferStateModule (@angular/platform-server)

• BrowserTransferStateModule (@angular/platform-browser)

• get(key, fallbackValue)

• set(key, value)

• has(key)

• remove(key)

Server vs. APIs

Page 48: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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

Server vs. APIs

Page 49: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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);             }         }));     } }

Server vs. APIs

Page 50: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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);     } }

Server vs. APIs

Page 51: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

HTTP_INTERCEPTORimport {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, } ]

Server vs. APIs

Page 52: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Performanceexport 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() })

Server vs. APIs

Page 53: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

0.5

sec

Server vs. APIs

Page 54: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

0.5

sec

Server vs. APIs

Page 55: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

https://www.twilio.com/blog/faster-javascript-web-apps-angular-universal-transferstate-api-watchdog

DRY(c) & Performance

Server vs. APIs

Page 56: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Server vs. APIs

Page 57: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Deployment

Server vs. Deploy

Page 58: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Let’s go Serverless!

• Function as a Service

• Event-driven

• Scalable

• Pay for the up-time

Server vs. Deploy

Page 59: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Let’s go Serverless!https://www.twilio.com/blog/angular-universal-javascript-node-js-aws-lambda

Server vs. Deploy

Page 60: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Server vs. Deploy

Page 61: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

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

• @ng-toolkit/universal + npm run build:prerender

Server vs. Deploy

Page 62: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

Summaryserver-side renderingprerenderng build —prod

SEO

Performance

Difficulty

SEO + external calls

Additional back-end logic

Server vs. Summary

Page 63: Maciej Treder - files.speakerdeck.com€¦ · A medicine for the SEO/CDN issues Maciej Treder @maciejtreder. Outline

@maciejtreder