Giới thiệu về UmiJS P4 (Server Side Rendering)

Bài viết này mình sẽ giới thiệu cách triển khai Server side rendering reactJS phát triển trên nền tảng umi. Để coi nhiều cấu hình hơn SSR các bạn xem thêm trên trang chủ umiJS

Tại sao ở đây mình lại triển khai SSR với umi luôn. Bởi vì nếu các bạn đã và đang làm việc với umi trong các ứng dụng reactJS của mình thì việc chuyển qua SSR là vô cùng đơn giản, không cần thay đổi mã nhiều, chỉ cần bật thêm cấu hình lên và thêm phương thức tìm nạp dữ liệu trước khi render là xong, không thay đổi gì quá nhiều về kiến trúc khi làm việc

Cách tiếp cận

Để sử dụng được SSR trong ứng dụng umi, mình có 3 thao tác sau

  1. Dựng server node để xử lý việc render từ server (ở đây mình dùng expressJS, sẽ có nhiều tùy chọn khác như koa, egg)
  2. Bật cấu hình ssr trong file cấu hình (.umirc.js)
  3. Thêm phương thức tĩnh getInitialProps nếu bạn để nạp dữ liệu trước khi render

Dựng server xử lý render

Ở bước này bạn tạo file server.js ở thư mục root của ứng dụng, với nội dung

require('regenerator-runtime/runtime');
const server = require('umi-server');
const express = require('express');
const compression = require('compression');
const helmet = require('helmet');
const { join } = require('path');

const isDev = process.env.NODE_ENV === 'development';

const root = join(__dirname, 'dist');
const render = server({
  root,
  polyfill: false,
  dev: isDev,
});

const app = express();
app.use(compression());
app.use(helmet());
app.use('/dist', express.static(root));

app.get('*', async (req, res, next) => {
  const { ssrHtml } = await render({
    req: {
      url: req.originalUrl,
    },
  });
  res.type('html');
  res.status(200).send(ssrHtml);
  next();
});

if (!process.env.NOW_ZEIT_ENV) {
  app.listen(3000);
  console.log('http://localhost:3000');
}

module.exports = app;

Ở đây umi cung cấp cho mình package umi-server để sử lý việc render từ server. Sau đó, bạn update lại file package.json để thêm thao tác build và chạy ứng dụng

{
    "scripts": {
    "build": "umi build",
    "start": "npm run build && nodemon server.js",
    "dev": "cross-env NODE_ENV=development concurrently \"umi dev\" \"nodemon server.js\"",
    "debug": "cross-env RM_TMPDIR=none COMPRESS=none UMI_ENV=prod umi build && node server.js"
  },
}

Bật cấu hình ssr trong file cấu hình

Bạn thêm cấu hình ssr: true trong file .umirc.js (tùy môi trường khác nhau mà bạn thêm trong các file .umirc.js khác nhau)

export default {
  ssr: true,
  hash: process.env.NODE_ENV === 'production',
  publicPath: '/dist/',
  plugins,
  chainWebpack(config, { webpack }) {
    if (process.env.NODE_ENV === 'development') {
      config.output.publicPath('http://localhost:8000/');
    }
  },
};

Mặc định thì ssr sẽ không được bật. Khi bạn bật ssr lên thì trong quá trình build ứng dụng, umi sẽ phát sinh thêm 2 file mới trong thư mục build (dist) của bạn umi.server.jsssr-client-mainifest.json

Thêm phương tĩnh getInitialProps nếu bạn muốn nạp dữ liệu trước khi render

Xét ví dụ, mình có pages/news/$id.jsx có nội dung

// pages/news/$id.jsx
const News = props => {
  const { id, name, count } = props || {};

  return (
    <div>
      <p>
        {id}-{name}
      </p>
    </div>
  );
};

/**
 *
 * @param {*}
 * {
 *  route (current active route)
 *  location (history object with location, query, ...)
 *  store (need enable `dva: true`, return the Promise via `store.dispatch()` )
 *  isServer (whether run in Server)
 *  req (HTTP server Request object, only exist in Server)
 *  res (HTTP server Response object, only exist in Server)
 * }
 */
News.getInitialProps = async ({ route, location, store, isServer, req, res }) => {
  const { id } = route.params;
  // ?locale=en-US => query: { locale: 'en-US' }
  const { query } = location;
  const data = [
    {
      id: 0,
      name: 'zero',
    },
    {
      id: 1,
      name: 'hello',
    },
    {
      id: 2,
      name: 'world',
    },
  ];
  return Promise.resolve(data[id] || data[0]);
};

Có 2 trường hợp khi nạp dữ liệu trước khi render

Trường hợp không sử dụng dva trong ứng dụng

Nếu bạn không sử dung dva thì phương thức getInitialProps của bạn trả về giá trị là 1 promise đã xử lý Promise.resolve (ở ví dụ trên là data[id] || data[0]), dữ liệu trả về này sẽ được đưa vào props của hàm render như đoạn mã trên, lúc đó thì thuộc tính id và name sẽ được lấy từ props của component

// pages/news/$id.jsx
const News = props => {
  const { id, name } = props || {};

  return (
    <div>
      <p>
        {id}-{name}
      </p>
    </div>
  );
};

Trường hợp có sử dụng dva trong ứng dụng (dva: true)

Trong trường hợp này thì tham số context của hàm getInitialProps sẽ cung cấp cho chúng ta biến store, lúc này việc trả về một promise từ getInitialProps sẽ từ store.dispatch (store.dispatch({type: ‘news/init’}) chẳng hạn). Thường thì với kiến trúc này mổi model của ứng dụng mình  sẽ thêm 1 effect là init để gọi khi mình muốn tải dữ liệu trước khi render

News.getInitialProps = async ({ store }) => {
  return store.dispatch({
  	type: 'news/init'
  });
};

Với giá trị được trả về từ store.dispatch ở trên thường thì sẽ trả về undefined, còn việc lấy dữ liệu ra dùng là lấy từ state của ứng dụng thông qua Hocs connect. Trong trường hợp sau khi thực hiện xong effect init và có trả về dữ liệu gì thì sao, xét file model models/news.js

export default {
  state: {
    list: []
  },
  reducers: {
    add(state) {
      return {
        ...state,
        list: ['news']
      }
    }
  },
  effects: {
    *init(actions, { put }) {
      console.log('init news')
      yield put({ type: 'add' });
      return {title: 'hot news'}
    },
  },
};

Sau khi log thử props ra thì kết quả là object được trả về sẽ được ghi vào trong props với key: value tương ứng, vậy ngoài việc đợi dispatch dữ liệu cập nhật vào store thì nó cũng sẽ ghi dữ liệu trả về vào props hiện tại mà không cần thông qua connect

Xem ứng dụng mẫu

Các bạn có thể tham khảo thêm ví dụ tại umi-ssr

Giới thiệu về UmiJS P1 (Tổng quan về umiJS)
Giới thiệu về UmiJS P2 (Cấu trúc thư mục, cấu hình thường dùng)
Giới thiệu về UmiJS P3 (Permission routing)

Nguồn: umiJS

Rate this post

You May Also Like

About the Author: truongluu

11 Comments

  1. Em gặp 1 lỗi với router của umi mong được a giải đáp:
    Lỗi của em như sau – Khi em build production đường dẫn chỉ tồn tại khi e k reload page còn nếu em đang ở 1 đường dẫn mà reload là nó sẽ hiện thị page 404 not found. :(((. E tìm tài liệu fix mà chưa có giải pháp . mong được a support

    1. Trường hợp này có 2 cách để làm nha em. Một là em chỉnh cấu hình history: ‘hash’ trong umi config (tức là lúc đó các link sẽ có thêm dấu ‘#’, 2 là e cấu hình server nhé em đang dùng (nginx hay apache sẽ có cách cấu hình khác nhau à)

      1. a có thể cho e xin tài lại để cấu hình nginx với lỗi trên được k ạ ?

  2. a cho e hỏi là :
    Em có 1 thư mục html là landing page có sử dụng query và css thì làm sao để e import nó vào được umi vậy ạ ?
    Mong nhận được sự giúp đỡ của a

  3. Cho mình hỏi là với UmiJS thì không cần server side API (Laravel) để hiển thị dữ liệu à bạn ?

    1. Cơ chế của nó là vầy nha bạn, 1 server nodeJS để quản lý việc SSR (ở đây umi cung cấp package umi-server), con này sẽ chạy như 1 service xuyên suốt nhận request và xử lý render ở server trả về dữ liệu, và trong ứng dụng ReactJS của bạn thì api bạn viết bằng gì cũng được (laravel hay node) nó không liên quan gì nhé :), chỉ request lấy dữ liệu thôi.

Leave a Reply

Your email address will not be published. Required fields are marked *