12/08/2018, 16:56

Server side rendering với Angular 4 (Angular Universal)

Single page application (SPA) frameworks có lẽ đang nhận được sự chú ý rất lớn trong thế giới javascript trong các năm qua. Việc xử lý hầu hết các công việc tại client, bảo trì "state" và giảm độ trễ khi chuyển qua các trang chỉ là một trong số các lợi ích của SPA. Nói tóm lại SPA đem lại trải ...

Single page application (SPA) frameworks có lẽ đang nhận được sự chú ý rất lớn trong thế giới javascript trong các năm qua. Việc xử lý hầu hết các công việc tại client, bảo trì "state" và giảm độ trễ khi chuyển qua các trang chỉ là một trong số các lợi ích của SPA. Nói tóm lại SPA đem lại trải nghiệm tuyệt vời cho người dùng. Tuy nhiên, chúng ta có một vấn đề nhỏ là: Ứng dụng cần được indexed bởi seach engines.

Nhiều seach engines và mạng xã hội giống như Facebook và Twetter mong đợi plain HTML để sử dụng meta tags và nội dung trang có liên quan. Chúng không thể xác định khi nào JavaScript framework hoàn thành việc dựng trang. Như một kết quả, chúng chỉ có thể thấy một phần rất nhỏ của HTML. Hay nói một cách khác, SPA chống lại search engines. Mặc dù, Google có thể thu thập thông tin và render một cách đầy đủ hầu hết các website động, nhưng đối với các link được chia sẻ trên mạng xã hội có vẻ không được ổn. Các bạn có thể thấy trong hình sau:

Như vậy, chúng ta cần sự hỗ trợ của SEO. Đúng. Chúng ta cần search engines, mạng xã hội và người dùng của úng dụng thấy một server-rendered view - như việc render phía server là đáng tin cậy, mềm dẻo và hiệu quả để đảm bảo tất cả search engines và mạng xã hội có thể đọc được nội dung của trang. Đây là Angular Universal:

Universal là có thể hiểu nôm na là render một phần nội dung bên máy chủ cho ứng dụng Angular. Nó là middleware ngồi giữa node.js và Angular. Nó đề suất cái tốt nhất của hai thế giới: Trải nghiệm người dùng, hiệu suất cao và thân thiện với SEO. Các bạn có thể tham khảo thêm tại trang chính thức Angular Universal.

Khuyến nghị các bạn clone từ repository @ng-seed/universal, giới thiệu cả Angular Universal và learn Angular trên một project đơn giản (sử dụng node.js + express), với commond patterns và best practices.

# clone the repo
git clone https://github.com/ng-seed/universal.git
cd universal

Tiếp theo, cài đặt dependencies:

npm install

Cái gì bạn cần để cung cấp là để cấu hình cho cả server và trình duyệt? Một cấu hình cho nền tảng đích của bạn hoặc back-end, ex: Node, .Net, ... Cấu trúc bootstraping bên dưới là cái gì Universal khuyến nghị:

main-browser.ts (client.ts)

File này có trách nhiệm khởi động ứng dụng của bạn trên client

// polyfills
import 'zone.js/dist/zone';
import 'reflect-metadata';
import 'rxjs/Observable';
import 'rxjs/add/operator/map';
// angular
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
// libs
import { bootloader } from '@angularclass/bootloader';
// app
import { AppBrowserModule } from './app/app.browser.module';
export function main(): any {
  return platformBrowserDynamic().bootstrapModule(AppBrowserModule);
}
bootloader(main);

server.ts

File này chỉ định cấu hình server/back-end của bạn. Import ngExpressEngine sử dụng việc mapping @ngx-universal/express-engine trên cấu hình server của bạn (ví dụ: server.ts) và khởi động AppServerModule (xem app.server.module là server module trong ứng dụng Angular Universal) sử dụng ngExpressEngine như bên dưới:

// polyfills
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import 'rxjs/Rx';
// angular
import { enableProdMode } from '@angular/core';
// libs
import * as express from 'express';
import * as compression from 'compression';
import { ngExpressEngine } from '@ngx-universal/express-engine';
// module
import { AppServerModule } from './app/app.server.module';
enableProdMode();
const server = express();
server.use(compression());
/**
 * Set view engine
 */
server.engine('html', ngExpressEngine({
  bootstrap: AppServerModule
}));
server.set('view engine', 'html');
server.set('views', 'public');
/**
 * Point static path to `public`
 */
server.use('/', express.static('public', {index: false}));

/**
 * Catch all routes and return the `index.html`
 */
server.get('*', (req, res) => {
  res.render('../public/index.html', {
    req: req,
    res: res
  });
});

/**
 * Port & host settings
 */
const PORT = process.env.PORT || 8000;
const HOST = process.env.BASE_URL || 'localhost';
const baseUrl = `http://${HOST}:${PORT}`;

server.set('port', PORT);

/**
 * Begin listening
 */
server.listen(server.get('port'), () => {
  console.log(`Express server listening on ${baseUrl}`);
});

app.browser.module.ts

Module này chứa mọi thứ chỉ định tới môi trường trình duyệt. Import BrowserStateTransferModule sử dụng mapping @ngx-universal/state-transfer và nối BrowserStateTransferModule.forRoot({...}) bên trong thuộc tính imports của app.browser.module:

...
import { BrowserStateTransferModule } from '@ngx-universal/state-transfer';
...

@NgModule({
  bootstrap: [LayoutMainComponent],
  imports: [
    BrowserModule.withServerTransition({
      appId: 'my-app-id'
    }),
    BrowserStateTransferModule.forRoot(),
    AppModule
  ]
})
export class AppBrowserModule {
}

app.server.module.ts (app.node.module.ts)

Module này được dành riêng cho môi trường server/back-end của bạn

...
import { ServerStateTransferModule, StateTransferService } from '@ngx-universal/state-transfer';
...
@NgModule({
  bootstrap: [AppComponent],
  imports: [
    BrowserModule.withServerTransition({
      appId: 'my-app-id'
    }),
    ServerModule,
    ServerStateTransferModule.forRoot(),
    AppModule
  ]
})
export class AppServerModule {
  constructor(private readonly stateTransfer: StateTransferService) {
  }
  ngOnBootstrap = () => {
    this.stateTransfer.inject();
  }
}

app.module.ts

Import TransferHttpModule sử dụng mapping @ngx-universal/state-transfer và nối TransferHttpModule.forRoot({...}) bên trong thuộc tính imports của app.module:

...
import { TransferHttpModule } from '@ngx-universal/state-transfer';
...

@NgModule({
  bootstrap: [AppComponent],
  imports: [
    BrowserModule,
    TransferHttpModule.forRoot(),
    ...
  ],
  ...
})
export class AppModule {
  ...
}
# dev build (Universal)
npm run build:universal-dev
# prod build (Universal)
npm run build:universal-prod

# start the server (Angular Universal)
npm run serve

Bài viết giới thiệu đến các bạn khái niệm cơ bản về Universal (Angular Universal). Đồng thời giải thích tại sao lại cần dùng nó, và các bước để cấu hình một ứng dụng có sử dụng Anglar Universal. Hy vọng bài viết mang lại nhiều điều bổ ích cho các bạn.

Tham khảo:

  • Angular 4 with server side rendering (aka Angular Universal)
  • Angular Universal
0