มาเริ่มสร้าง Angular 4 พร้อม Server-side Rendering กันเถอะ!

อย่างที่รู้กันว่า Angular เป็น Single Page Application (SPA) framework ที่ค่อนข้างได้รับความนิยมในหลายปีที่ผ่านมา ซึ่งสามารถจัดการกับหน้าตาของฝั่ง client ได้เป็นอย่างดี แต่อย่างไรก็ตาม Angular เองก็ยังมีปัญหาเล็กน้อยเกี่ยวกับ index ที่แสดงบน search engines

ปัญหานี้เกิดขึ้นจาก search engines และ social networks อย่าง Google , Facebook และ Twitter ต่างดึงเอา plain HTML เพื่อนำ meta tags และข้อมูลบางส่วนไปแสดง โดย search engines และบรรดา social networks ไม่ได้ดึงส่วนเนื้อหาที่ JavaScript สร้างขึ้นภายหลัง ผลลัพธ์ที่ได้จึงเกิดขึ้นดังภาพ

ผลการค้นหาจาก http://www.google.com

บทความนี้จึงขอพูดถึงการทำ Angular app เบื้องต้น ตั้งแต่เริ่มต้นสร้างโปรเจค จนพร้อมใช้งานได้จริงบนเซิร์ฟเวอร์ พร้อมการทำ Server-side Rendering (SSR) ซึ่งในบทความนี้จะมีเนื้อหาที่ประกอบด้วย

  • Angular 4
  • Angular CLI
  • Angular Unversal
  • แนะนำ Meta Service
  • Preboot

เรามาเริ่มกันเลย!!


เริ่มสร้างโปรเจค โดย Angular CLI

เริ่มจากติดตั้ง Angular CLI ลงบนเครื่องของเราในเวอร์ชั่นล่าสุด

npm install -g @angular/cli@latest

หลังจากนั้นสร้างโปรเจคของเราด้วย CLI -ในบทความนี้เราจะใช้ชื่อว่า my-app

ng new my-app
cd my-app
npm install -D ts-node
npm install -S @angular/platform-server @angular/animations

เมื่อติดตั้งเรียบร้อยแล้ว ให้เพิ่ม script ในไฟล์ ดังต่อไปนี้

src/app/app.module.ts

...
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

@NgModule({
...
imports: [
BrowserModule
.withServerTransition({appId: 'my-app'}),
FormsModule,
HttpModule

],
...

src/tsconfig.app.json

...
"exclude": [

"server.ts",
...
]

tsconfig.json

{
...
"compilerOptions": {
...
"lib": [
"es2016",
"dom"
]
},
"angularCompilerOptions": {
"genDir": "./dist/ngfactory",
"entryModule": "./src/app/app.module#AppModule"
}

}

package.json

{
...
"scripts": {
...
"prestart": "ng build --prod && ngc",
"start": "ts-node src/server.ts",
"start:client": "ng serve"
},
...
}

สร้างไฟล์ src/app/app.server.module.ts

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [
ServerModule,
AppModule
],
bootstrap: [AppComponent]
})
export class AppServerModule { }

สร้างไฟล์ src/server.ts

import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { platformServer, renderModuleFactory } from '@angular/platform-server'
import { enableProdMode } from '@angular/core'
import { AppServerModuleNgFactory } from '../dist/ngfactory/src/app/app.server.module.ngfactory'
import * as express from 'express';
import { readFileSync } from 'fs';
import { join } from 'path';
const PORT = 4000;
enableProdMode();
const app = express();
let template = readFileSync(join(__dirname, '..', 'dist', 'index.html')).toString();
app.engine('html', (_, options, callback) => {
const opts = { document: template, url: options.req.url };
renderModuleFactory(AppServerModuleNgFactory, opts)
.then(html => callback(null, html));
});
app.set('view engine', 'html');
app.set('views', 'src')
app.get('*.*', express.static(join(__dirname, '..', 'dist')));
app.get('*', (req, res) => {
res.render('index', { req });
});
app.listen(PORT, () => {
console.log(`listening on http://localhost:${PORT}!`);
});

หลังจากนั้นลอง run โปรเจคของเราโดยใช้คำสั่ง

npm run start

และเข้าไปที่ http://localhost:4000 จะได้เว็บของเรา

หน้าตาของเว็บไซต์ที่ได้จาก Angular CLI

หน้าตาของเว็บที่ได้นั้น เหมือนกับโปรเจคที่ถูกสร้างด้วย CLI ทุกประการ ซึ่งสามารถเปรียบเทียบกับโปรเจคธรรมดาที่ไม่ใช้ Angular Universal ได้ โดยใช้คำสั่ง

npm run start:client

และเข้าไปที่ http://localhost:4200 ซึ่งเป็น Angular ที่ไม่มี Angular universal ซึ่งสองส่วนนี้แต่ต่างกันตรงที่ Angular Universal จะทำหน้าที่เป็น server-side render ทำให้เนื้อหาภายในเว็บไซต์ถูก render ออกมาเป็น plain HTML ไว้ตั้งแต่แรก ช่วยลดช่องโหว่ของ SPA ได้เป็นอย่างดี

ภาพเปรียบเทียบ Plain HTML ที่ได้จากโปรเจค

ทำ Meta tag ให้ง่ายขึ้น

การตั้ง meta tag ของเว็บไซต์ มีไว้เพื่อจัดการกับ index ใน Search Engine สามารถจัดการด้วยการสร้าง service ไว้คอยควบคุม meta ในไซต์ต่างๆของเรา ซึ่ง Angular ก็มี service ที่ช่วยควบคุม meta tag ให้เป็นเรื่องที่ไม่ยุ่งยากอีกต่อไป โดยสามารถหาอ่านได้ใน Meta Docs ของ Angular

ส่วนการทำ Search Engine Optimization(SEO) นั้นขึ้นอยู่กับเทคนิคของแต่ละคน ยิ่งเราใส่คำที่ถูกค้นหา(keyword) ตรงกับเนื้อหามากเท่าไหร่ เว็บไซต์ของเราก็ติดอันดับหนึ่งของ search engine ได้ไม่ยาก


เพิ่มประสิทธิภาพอีกนิดด้วย Preboot

เมื่อโปรเจคของเรามีขนาดใหญ่ขึ้น มีความซับซ้อนมากขึ้น เว็บก็จะโหลดช้าลงเป็นปกติ หนึ่งในปัญหาที่เกิดขึ้นเมื่อเว็บไซต์เรามี Server-side Rendering คือ เกิด transition ระหว่าง server-side webview ในตอนแรก และเมื่อ javascript ทำงานก็จะแสดง client-side webview ขึ้นมา ถึงจะเป็นเรื่องเล็กน้อย แต่มีผลต่อประสบการณ์การใช้งานของผู้ใช้(User Experience)พอสมควร

การแก้ปัญกา transition ดังกล่าวสามารถใช้ Preboot เข้ามาช่วยจัดการได้ ซึ่งสามารถอ่านเพิ่มเติมได้ที่ https://github.com/angular/preboot

หรือจะทำตามบทความนี้ก็ได้เช่นกัน สำหรับในบทความนี้เพียงต้องการ freeze เว็ปเพจก่อนหน้าไว้ระหว่าง client-side webview กำลังโหลด และค่อยแสดงผลเมื่อโหลด client-side webview เสร็จทีเดียว ซึ่งเริ่มจากติดตั้ง preboot ลงไปในโปรเจคของเรา

npm i preboot --save

หลังจากนั้นเพิ่ม code เข้าไปในโปรเจคของเราเพียง 2 บรรทัด

src/app/app.server.module.ts

...
import { ServerPrebootModule } from 'preboot/server';
@NgModule({
imports: [
ServerModule,
AppModule,

ServerPrebootModule.recordEvents({ appRoot: 'my-app' })
],
bootstrap: [AppComponent]
})
export class AppServerModule { }

ท่านี้เว็บเราก็จะสมบูรณ์ขี้น พร้อมใช้งานอย่างราบรื่น ดูมีระดับตามสไตล์ของ Angular Universal

ภาพแสดงการทำงานของ universal จาก บทความของ Michi Kono

สรุป

Angular Universal เป็น Server-side Renderer ซึ่งจะสร้าง Server-side webview เป็น Plain HTML เพื่อแสดงเนื้อหาในเว็บไซต์ของเราเมื่อถูกค้นหาจาก Search Engine

ภาพแสดงการทำงานของ universal จาก บทความของ Michi Kono

ซึ่งบทความนี้เป็นเพียงการแนะนำคุณสมบัติหนึ่งของ Angular universal สำหรับนำไปใช้งานเบื้อต้น แน่นอนว่าในการสร้างโปรเจคหนึ่งๆต้องใช้เครื่องมือและเทคนิคอีกมากมาย ขึ้นอยู่กับจุดประสงค์ของเว็ปไซต์และคุณภาพที่ต้องการ ซึ่งเครื่องมือในยุคสมัยนี้พัฒนาอย่างรวดเร็ว และแทบจะตลอดเวลา ดังนั้นเราควรติดตามเทคโนโลยีที่ใช้งานอยู่เสมอ และ Angular ก็เช่นกัน