Hướng dẫn react ssr css - phản ứng ssr css

Chào bạn, sau một khoảng thời gian nghỉ tết ngắn, chúng ta hầu hết đã trở lại với công việc và mình cũng không ngoại lệ. Dư âm tết vẫn còn đấy nhưng cũng không thể quên nhiệm vụ, thế là hôm nay mình đã trở lại với một bài viết mới với một chủ đề tuy cũ nhưng lại mới với bản thân mình, đó là xây dựng các ứng dụng Server-Side Rendering (SSR) trong React và đặc biệt hơn mình sẽ không theo 1 Framework SSR thông dụng nào cả (Ví dụ

module.exports = {
  presets: [
    [
      '@babel/env',
      {
        useBuiltIns: 'usage',
        corejs: require('core-js/package.json').version,
      },
    ],
    '@babel/react',
  ],
};
0) mà sẽ tự xây dựng theo phong cách của bản thân. Nào bạn cùng theo dõi bài viết nhé .
Hướng dẫn react ssr css - phản ứng ssr css
.

1. Chuẩn bị

Yêu cầu:

  • Môi trường mình sẽ sử dụng:
    • window 10
    • node v12.14.0
    • yarn v1.22.4
    • editor: VSCode
  • Có kiến thức cơ bản về React
  • Có một chút kiến thức về
    module.exports = {
      presets: [
        [
          '@babel/env',
          {
            useBuiltIns: 'usage',
            corejs: require('core-js/package.json').version,
          },
        ],
        '@babel/react',
      ],
    };
    
    2
  • Có một chút kiến thức về
    module.exports = {
      presets: [
        [
          '@babel/env',
          {
            useBuiltIns: 'usage',
            corejs: require('core-js/package.json').version,
          },
        ],
        '@babel/react',
      ],
    };
    
    3

Mục đích:

  • Hiểu cách xây dựng một ứng dụng nhỏ theo xu thế
    module.exports = {
      presets: [
        [
          '@babel/env',
          {
            useBuiltIns: 'usage',
            corejs: require('core-js/package.json').version,
          },
        ],
        '@babel/react',
      ],
    };
    
    4 bằng
    module.exports = {
      presets: [
        [
          '@babel/env',
          {
            useBuiltIns: 'usage',
            corejs: require('core-js/package.json').version,
          },
        ],
        '@babel/react',
      ],
    };
    
    3
  • Tiền đề để bạn có thể đào sâu hơn về các công nghệ liên quan đến
    module.exports = {
      presets: [
        [
          '@babel/env',
          {
            useBuiltIns: 'usage',
            corejs: require('core-js/package.json').version,
          },
        ],
        '@babel/react',
      ],
    };
    
    3 trong React

Những phần bỏ qua:

  • Phần cấu hình mình sẽ không mô tả chi tiết trong bài viết, các bạn có thể theo dõi thông qua repo.
  • Không giải thích các thuật ngữ, khái niệm cơ bản mà các bạn hoàn toàn có thể đọc thông qua trang chính thức của thư viện đó.

2. Nội dung

Nói một chút về SSR trong React

Bạn sẽ thường nghĩ đến

module.exports = {
  presets: [
    [
      '@babel/env',
      {
        useBuiltIns: 'usage',
        corejs: require('core-js/package.json').version,
      },
    ],
    '@babel/react',
  ],
};
3 là cách nội dung của một trang web (full html) được render trên server chứ không phải trên trình duyệt của người dùng (client) bằng
module.exports = {
  presets: [
    [
      '@babel/env',
      {
        useBuiltIns: 'usage',
        corejs: require('core-js/package.json').version,
      },
    ],
    '@babel/react',
  ],
};
9, và mọi trang đều làm cùng 1 cách như vậy, thường thì bạn sẽ thấy cách render này được sử dụng bởi các ngôn ngữ/ framework server như
const path = require('path');
const fs = require('fs');

const webpackNodeExternals = require('webpack-node-externals');

const rootDir = fs.realpathSync(process.cwd());
const srcDir = path.resolve(rootDir, 'src');
const buildDir = path.resolve(rootDir, 'build');

const common = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: srcDir,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', srcDir],
    extensions: ['.js', '.jsx', '.json'],
  },
};

const clientConfig = {
  ...common,
  target: 'web',
  name: 'client',
  entry: {
    client: path.resolve(srcDir, 'client.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true,
        },
      },
    },
  },
  devtool: 'eval-source-map',
};

const serverConfig = {
  ...common,
  target: 'node',
  name: 'server',
  entry: {
    server: path.resolve(srcDir, 'server.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: 'server.js',
  },
  devtool: 'eval-source-map',
  externals: [webpackNodeExternals()],
  node: {
    __dirname: false,
  },
};

module.exports = [clientConfig, serverConfig];
0 và các tương tác phía client sẽ được triển khai bằng JavaScript.

Điều này gần như trái ngược với cách tiếp cận của

const path = require('path');
const fs = require('fs');

const webpackNodeExternals = require('webpack-node-externals');

const rootDir = fs.realpathSync(process.cwd());
const srcDir = path.resolve(rootDir, 'src');
const buildDir = path.resolve(rootDir, 'build');

const common = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: srcDir,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', srcDir],
    extensions: ['.js', '.jsx', '.json'],
  },
};

const clientConfig = {
  ...common,
  target: 'web',
  name: 'client',
  entry: {
    client: path.resolve(srcDir, 'client.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true,
        },
      },
    },
  },
  devtool: 'eval-source-map',
};

const serverConfig = {
  ...common,
  target: 'node',
  name: 'server',
  entry: {
    server: path.resolve(srcDir, 'server.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: 'server.js',
  },
  devtool: 'eval-source-map',
  externals: [webpackNodeExternals()],
  node: {
    __dirname: false,
  },
};

module.exports = [clientConfig, serverConfig];
1 đó là mỗi khi user truy cập vào một trang web, phía server sẽ gởi đến client một file html đơn giản có chưa các liên kết gồm link và script sau đó browser sẽ tiếp tục kích hoạt request để lấy các asset ấy, trong đó sẽ bao gồm 1 file
const path = require('path');
const fs = require('fs');

const webpackNodeExternals = require('webpack-node-externals');

const rootDir = fs.realpathSync(process.cwd());
const srcDir = path.resolve(rootDir, 'src');
const buildDir = path.resolve(rootDir, 'build');

const common = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: srcDir,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', srcDir],
    extensions: ['.js', '.jsx', '.json'],
  },
};

const clientConfig = {
  ...common,
  target: 'web',
  name: 'client',
  entry: {
    client: path.resolve(srcDir, 'client.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true,
        },
      },
    },
  },
  devtool: 'eval-source-map',
};

const serverConfig = {
  ...common,
  target: 'node',
  name: 'server',
  entry: {
    server: path.resolve(srcDir, 'server.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: 'server.js',
  },
  devtool: 'eval-source-map',
  externals: [webpackNodeExternals()],
  node: {
    __dirname: false,
  },
};

module.exports = [clientConfig, serverConfig];
2 (đóng gọi toàn bộ những thứ cần thiết để tạo markup thông qua JS), và sau đó
const path = require('path');
const fs = require('fs');

const webpackNodeExternals = require('webpack-node-externals');

const rootDir = fs.realpathSync(process.cwd());
const srcDir = path.resolve(rootDir, 'src');
const buildDir = path.resolve(rootDir, 'build');

const common = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: srcDir,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', srcDir],
    extensions: ['.js', '.jsx', '.json'],
  },
};

const clientConfig = {
  ...common,
  target: 'web',
  name: 'client',
  entry: {
    client: path.resolve(srcDir, 'client.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true,
        },
      },
    },
  },
  devtool: 'eval-source-map',
};

const serverConfig = {
  ...common,
  target: 'node',
  name: 'server',
  entry: {
    server: path.resolve(srcDir, 'server.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: 'server.js',
  },
  devtool: 'eval-source-map',
  externals: [webpackNodeExternals()],
  node: {
    __dirname: false,
  },
};

module.exports = [clientConfig, serverConfig];
3 từ trình duyệt của client sẽ tạo ra markup dựa vào tệp
const path = require('path');
const fs = require('fs');

const webpackNodeExternals = require('webpack-node-externals');

const rootDir = fs.realpathSync(process.cwd());
const srcDir = path.resolve(rootDir, 'src');
const buildDir = path.resolve(rootDir, 'build');

const common = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: srcDir,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', srcDir],
    extensions: ['.js', '.jsx', '.json'],
  },
};

const clientConfig = {
  ...common,
  target: 'web',
  name: 'client',
  entry: {
    client: path.resolve(srcDir, 'client.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true,
        },
      },
    },
  },
  devtool: 'eval-source-map',
};

const serverConfig = {
  ...common,
  target: 'node',
  name: 'server',
  entry: {
    server: path.resolve(srcDir, 'server.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: 'server.js',
  },
  devtool: 'eval-source-map',
  externals: [webpackNodeExternals()],
  node: {
    __dirname: false,
  },
};

module.exports = [clientConfig, serverConfig];
2 đã được tải.

Theo cách hiểu của mình thì

module.exports = {
  presets: [
    [
      '@babel/env',
      {
        useBuiltIns: 'usage',
        corejs: require('core-js/package.json').version,
      },
    ],
    '@babel/react',
  ],
};
3 trong React sẽ hơi khác một chút, trang đầu tiên (
const path = require('path');
const fs = require('fs');

const webpackNodeExternals = require('webpack-node-externals');

const rootDir = fs.realpathSync(process.cwd());
const srcDir = path.resolve(rootDir, 'src');
const buildDir = path.resolve(rootDir, 'build');

const common = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: srcDir,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', srcDir],
    extensions: ['.js', '.jsx', '.json'],
  },
};

const clientConfig = {
  ...common,
  target: 'web',
  name: 'client',
  entry: {
    client: path.resolve(srcDir, 'client.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true,
        },
      },
    },
  },
  devtool: 'eval-source-map',
};

const serverConfig = {
  ...common,
  target: 'node',
  name: 'server',
  entry: {
    server: path.resolve(srcDir, 'server.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: 'server.js',
  },
  devtool: 'eval-source-map',
  externals: [webpackNodeExternals()],
  node: {
    __dirname: false,
  },
};

module.exports = [clientConfig, serverConfig];
7) sẽ được render từ phía server và gửi đến trình duyệt của client, khi user điều hướng đến các trang tiếp theo sẽ render trực tiếp tại client (đối với cách áp dụng code-splitting thì sẽ cần tải thêm script từ phía server), render theo hướng này thường được gọi là
const path = require('path');
const fs = require('fs');

const webpackNodeExternals = require('webpack-node-externals');

const rootDir = fs.realpathSync(process.cwd());
const srcDir = path.resolve(rootDir, 'src');
const buildDir = path.resolve(rootDir, 'build');

const common = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: srcDir,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', srcDir],
    extensions: ['.js', '.jsx', '.json'],
  },
};

const clientConfig = {
  ...common,
  target: 'web',
  name: 'client',
  entry: {
    client: path.resolve(srcDir, 'client.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true,
        },
      },
    },
  },
  devtool: 'eval-source-map',
};

const serverConfig = {
  ...common,
  target: 'node',
  name: 'server',
  entry: {
    server: path.resolve(srcDir, 'server.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: 'server.js',
  },
  devtool: 'eval-source-map',
  externals: [webpackNodeExternals()],
  node: {
    __dirname: false,
  },
};

module.exports = [clientConfig, serverConfig];
8.

Server dùng để render

Với bài viết này mình sẽ dùng Node server (với express framework) để xây dựng phần server-side vì tính phổ biến của nó, ngoài ra bạn cũng có thể dùng nhiều loại server khác.

Khởi tạo ứng dụng

Tất nhiên là bạn hoàn toàn có thể tạo ứng dụng node bằng

const path = require('path');
const fs = require('fs');

const webpackNodeExternals = require('webpack-node-externals');

const rootDir = fs.realpathSync(process.cwd());
const srcDir = path.resolve(rootDir, 'src');
const buildDir = path.resolve(rootDir, 'build');

const common = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: srcDir,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', srcDir],
    extensions: ['.js', '.jsx', '.json'],
  },
};

const clientConfig = {
  ...common,
  target: 'web',
  name: 'client',
  entry: {
    client: path.resolve(srcDir, 'client.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true,
        },
      },
    },
  },
  devtool: 'eval-source-map',
};

const serverConfig = {
  ...common,
  target: 'node',
  name: 'server',
  entry: {
    server: path.resolve(srcDir, 'server.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: 'server.js',
  },
  devtool: 'eval-source-map',
  externals: [webpackNodeExternals()],
  node: {
    __dirname: false,
  },
};

module.exports = [clientConfig, serverConfig];
9 hoặc
import React from 'react';

const Html = ({ content, state, scripts }) => {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Server Side Rendered React App !title>
      head>
      <body>
        <div id="app" dangerouslySetInnerHTML={{ __html: content }} />
        <script
          dangerouslySetInnerHTML={{
            __html: `window.APP_STATE=${JSON.stringify(state)}`,
          }}
        />
        {scripts.map((script, index) => (
          <script key={index} src={script} />
        ))}
      body>
    html>
  );
};

export default Html;
0, trong bài viết này mình sẽ dùng
const path = require('path');
const fs = require('fs');

const webpackNodeExternals = require('webpack-node-externals');

const rootDir = fs.realpathSync(process.cwd());
const srcDir = path.resolve(rootDir, 'src');
const buildDir = path.resolve(rootDir, 'build');

const common = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: srcDir,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', srcDir],
    extensions: ['.js', '.jsx', '.json'],
  },
};

const clientConfig = {
  ...common,
  target: 'web',
  name: 'client',
  entry: {
    client: path.resolve(srcDir, 'client.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true,
        },
      },
    },
  },
  devtool: 'eval-source-map',
};

const serverConfig = {
  ...common,
  target: 'node',
  name: 'server',
  entry: {
    server: path.resolve(srcDir, 'server.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: 'server.js',
  },
  devtool: 'eval-source-map',
  externals: [webpackNodeExternals()],
  node: {
    __dirname: false,
  },
};

module.exports = [clientConfig, serverConfig];
9.

Cài đặt thư viện

Mình sẽ cần cài một số lib quan trọng sau

# devDependencies
yarn add -D @babel/core @babel/preset-env @babel/preset-react babel-loader webpack@4 webpack-cli@3 webpack-node-externals

# dependencies
yarn add express react react-dom

# polyfill
yarn add core-js regenerator-runtime

Cấu hình để build

import React from 'react';

const Html = ({ content, state, scripts }) => {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Server Side Rendered React App !title>
      head>
      <body>
        <div id="app" dangerouslySetInnerHTML={{ __html: content }} />
        <script
          dangerouslySetInnerHTML={{
            __html: `window.APP_STATE=${JSON.stringify(state)}`,
          }}
        />
        {scripts.map((script, index) => (
          <script key={index} src={script} />
        ))}
      body>
    html>
  );
};

export default Html;
2

module.exports = {
  presets: [
    [
      '@babel/env',
      {
        useBuiltIns: 'usage',
        corejs: require('core-js/package.json').version,
      },
    ],
    '@babel/react',
  ],
};

import React from 'react';

const Html = ({ content, state, scripts }) => {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Server Side Rendered React App !title>
      head>
      <body>
        <div id="app" dangerouslySetInnerHTML={{ __html: content }} />
        <script
          dangerouslySetInnerHTML={{
            __html: `window.APP_STATE=${JSON.stringify(state)}`,
          }}
        />
        {scripts.map((script, index) => (
          <script key={index} src={script} />
        ))}
      body>
    html>
  );
};

export default Html;
3

const path = require('path');
const fs = require('fs');

const webpackNodeExternals = require('webpack-node-externals');

const rootDir = fs.realpathSync(process.cwd());
const srcDir = path.resolve(rootDir, 'src');
const buildDir = path.resolve(rootDir, 'build');

const common = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: srcDir,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  resolve: {
    modules: ['node_modules', srcDir],
    extensions: ['.js', '.jsx', '.json'],
  },
};

const clientConfig = {
  ...common,
  target: 'web',
  name: 'client',
  entry: {
    client: path.resolve(srcDir, 'client.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true,
        },
      },
    },
  },
  devtool: 'eval-source-map',
};

const serverConfig = {
  ...common,
  target: 'node',
  name: 'server',
  entry: {
    server: path.resolve(srcDir, 'server.js'),
  },
  output: {
    publicPath: '/',
    path: buildDir,
    filename: 'server.js',
  },
  devtool: 'eval-source-map',
  externals: [webpackNodeExternals()],
  node: {
    __dirname: false,
  },
};

module.exports = [clientConfig, serverConfig];

Với webpack bạn có thể sử dụng một mảng chứa nhiều config khác nhau để có thể xử lý cho nhiều đầu vào và đầu ra khác nhau nhé. Cụ thể ở đây sẽ chứa config cho

import React from 'react';

const Html = ({ content, state, scripts }) => {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Server Side Rendered React App !title>
      head>
      <body>
        <div id="app" dangerouslySetInnerHTML={{ __html: content }} />
        <script
          dangerouslySetInnerHTML={{
            __html: `window.APP_STATE=${JSON.stringify(state)}`,
          }}
        />
        {scripts.map((script, index) => (
          <script key={index} src={script} />
        ))}
      body>
    html>
  );
};

export default Html;
4 và
import React from 'react';

const Html = ({ content, state, scripts }) => {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Server Side Rendered React App !title>
      head>
      <body>
        <div id="app" dangerouslySetInnerHTML={{ __html: content }} />
        <script
          dangerouslySetInnerHTML={{
            __html: `window.APP_STATE=${JSON.stringify(state)}`,
          }}
        />
        {scripts.map((script, index) => (
          <script key={index} src={script} />
        ))}
      body>
    html>
  );
};

export default Html;
5 Khi tiến hành build sẽ mong đợi 2 loại đầu ra sau

  1. Cho phía client:
    import React from 'react';
    
    const Html = ({ content, state, scripts }) => {
      return (
        <html lang="en">
          <head>
            <meta charSet="UTF-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>Server Side Rendered React App !title>
          head>
          <body>
            <div id="app" dangerouslySetInnerHTML={{ __html: content }} />
            <script
              dangerouslySetInnerHTML={{
                __html: `window.APP_STATE=${JSON.stringify(state)}`,
              }}
            />
            {scripts.map((script, index) => (
              <script key={index} src={script} />
            ))}
          body>
        html>
      );
    };
    
    export default Html;
    
    6 và
    import React from 'react';
    
    const Html = ({ content, state, scripts }) => {
      return (
        <html lang="en">
          <head>
            <meta charSet="UTF-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>Server Side Rendered React App !title>
          head>
          <body>
            <div id="app" dangerouslySetInnerHTML={{ __html: content }} />
            <script
              dangerouslySetInnerHTML={{
                __html: `window.APP_STATE=${JSON.stringify(state)}`,
              }}
            />
            {scripts.map((script, index) => (
              <script key={index} src={script} />
            ))}
          body>
        html>
      );
    };
    
    export default Html;
    
    7
  2. Cho phía server:
    import React from 'react';
    
    const Html = ({ content, state, scripts }) => {
      return (
        <html lang="en">
          <head>
            <meta charSet="UTF-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>Server Side Rendered React App !title>
          head>
          <body>
            <div id="app" dangerouslySetInnerHTML={{ __html: content }} />
            <script
              dangerouslySetInnerHTML={{
                __html: `window.APP_STATE=${JSON.stringify(state)}`,
              }}
            />
            {scripts.map((script, index) => (
              <script key={index} src={script} />
            ))}
          body>
        html>
      );
    };
    
    export default Html;
    
    8

Tạo các component và entry point

import React from 'react';

const Html = ({ content, state, scripts }) => {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Server Side Rendered React App !title>
      head>
      <body>
        <div id="app" dangerouslySetInnerHTML={{ __html: content }} />
        <script
          dangerouslySetInnerHTML={{
            __html: `window.APP_STATE=${JSON.stringify(state)}`,
          }}
        />
        {scripts.map((script, index) => (
          <script key={index} src={script} />
        ))}
      body>
    html>
  );
};

export default Html;
9

import React from 'react';

const Html = ({ content, state, scripts }) => {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Server Side Rendered React App !title>
      head>
      <body>
        <div id="app" dangerouslySetInnerHTML={{ __html: content }} />
        <script
          dangerouslySetInnerHTML={{
            __html: `window.APP_STATE=${JSON.stringify(state)}`,
          }}
        />
        {scripts.map((script, index) => (
          <script key={index} src={script} />
        ))}
      body>
    html>
  );
};

export default Html;

component này sẽ được dùng để render toàn bộ nội dung html gửi đến client

import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
0

import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;

component này sẽ tạo ra nội dung cụ thể và được đưa vào bên trong

import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
1. bạn có thể xem nó giống như
import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
2 mà các ứng dụng
import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
3 thường làm. Ngoài ra chúng ta sẽ thêm một event-handler đơn giản để thay đổi 1 đoạn text.

import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
4

import express from 'express';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import Html from './Html';
import App from './App';

const app = express();
const port = 6969;

app.use(express.static(path.join(__dirname)));

app.get('*', async (req, res) => {
  const scripts = ['vendor.js', 'client.js'];
  const initialState = {
    initialText: 'rendered on the server side!',
  };

  const content = ReactDOMServer.renderToString(<App {...initialState} />);

  const html = ReactDOMServer.renderToStaticMarkup(
    <Html content={content} state={initialState} scripts={scripts} />,
  );

  res.send(`${html}`);
});

app.listen(port, () => console.log(`Listening on localhost:${port}`));

Chúng ta sẽ dựng 1 server express đơn giản và serve các static file trong cùng folder sau khi build. Phần quan trọng nhất của demo này, bạn sẽ thấy

import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
5 và nó làm nhiệm vụ nhận mọi request từ phía client. Đối với render React phía server chúng ta sẽ cần dùng
import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
6. Mình sẽ dùng 2 phương thức
import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
7 và
import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
8, về cơ bản thì 2 phương thức này là hoàn toàn tương tự nhau chỉ khác là
import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
7 sẽ cho phép event-handler sau khi đồng bộ với phía client và
import React from 'react';

const App = ({ initialText }) => {
  const [text, setText] = React.useState(initialText);

  const handleTextChange = () => {
    setText('changed in the browser!');
  };

  return (
    <div>
      <p>{text}p>
      <button onClick={handleTextChange}>change textbutton>
    div>
  );
};

export default App;
8 sẽ bỏ qua tất cả attribute mà React thêm vào DOM trong quá trình render
import express from 'express';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import Html from './Html';
import App from './App';

const app = express();
const port = 6969;

app.use(express.static(path.join(__dirname)));

app.get('*', async (req, res) => {
  const scripts = ['vendor.js', 'client.js'];
  const initialState = {
    initialText: 'rendered on the server side!',
  };

  const content = ReactDOMServer.renderToString(<App {...initialState} />);

  const html = ReactDOMServer.renderToStaticMarkup(
    <Html content={content} state={initialState} scripts={scripts} />,
  );

  res.send(`${html}`);
});

app.listen(port, () => console.log(`Listening on localhost:${port}`));
1 2 file này sẽ được tạo ra khi ta tiến hành build client bằng webpack Ngoài ra
import express from 'express';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import Html from './Html';
import App from './App';

const app = express();
const port = 6969;

app.use(express.static(path.join(__dirname)));

app.get('*', async (req, res) => {
  const scripts = ['vendor.js', 'client.js'];
  const initialState = {
    initialText: 'rendered on the server side!',
  };

  const content = ReactDOMServer.renderToString(<App {...initialState} />);

  const html = ReactDOMServer.renderToStaticMarkup(
    <Html content={content} state={initialState} scripts={scripts} />,
  );

  res.send(`${html}`);
});

app.listen(port, () => console.log(`Listening on localhost:${port}`));
2 sẽ được dùng để chứng minh việc render phía server lần đầu tiên.

import express from 'express';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import Html from './Html';
import App from './App';

const app = express();
const port = 6969;

app.use(express.static(path.join(__dirname)));

app.get('*', async (req, res) => {
  const scripts = ['vendor.js', 'client.js'];
  const initialState = {
    initialText: 'rendered on the server side!',
  };

  const content = ReactDOMServer.renderToString(<App {...initialState} />);

  const html = ReactDOMServer.renderToStaticMarkup(
    <Html content={content} state={initialState} scripts={scripts} />,
  );

  res.send(`${html}`);
});

app.listen(port, () => console.log(`Listening on localhost:${port}`));
3

import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

// Lấy state từ một biến global được đưa vào HTML do server tạo
const initialState = window.APP_STATE;

ReactDOM.hydrate(<App {...initialState} />, document.getElementById('app'));

Chúng ta sẽ cần dùng

import express from 'express';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import Html from './Html';
import App from './App';

const app = express();
const port = 6969;

app.use(express.static(path.join(__dirname)));

app.get('*', async (req, res) => {
  const scripts = ['vendor.js', 'client.js'];
  const initialState = {
    initialText: 'rendered on the server side!',
  };

  const content = ReactDOMServer.renderToString(<App {...initialState} />);

  const html = ReactDOMServer.renderToStaticMarkup(
    <Html content={content} state={initialState} scripts={scripts} />,
  );

  res.send(`${html}`);
});

app.listen(port, () => console.log(`Listening on localhost:${port}`));
4 thay cho
import express from 'express';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import Html from './Html';
import App from './App';

const app = express();
const port = 6969;

app.use(express.static(path.join(__dirname)));

app.get('*', async (req, res) => {
  const scripts = ['vendor.js', 'client.js'];
  const initialState = {
    initialText: 'rendered on the server side!',
  };

  const content = ReactDOMServer.renderToString(<App {...initialState} />);

  const html = ReactDOMServer.renderToStaticMarkup(
    <Html content={content} state={initialState} scripts={scripts} />,
  );

  res.send(`${html}`);
});

app.listen(port, () => console.log(`Listening on localhost:${port}`));
5 đây là yêu cầu khi bạn đã render ứng dụng từ phía server và muốn kết nối nó với phía client

Chạy demo

yarn start

Truy cập

import express from 'express';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import Html from './Html';
import App from './App';

const app = express();
const port = 6969;

app.use(express.static(path.join(__dirname)));

app.get('*', async (req, res) => {
  const scripts = ['vendor.js', 'client.js'];
  const initialState = {
    initialText: 'rendered on the server side!',
  };

  const content = ReactDOMServer.renderToString(<App {...initialState} />);

  const html = ReactDOMServer.renderToStaticMarkup(
    <Html content={content} state={initialState} scripts={scripts} />,
  );

  res.send(`${html}`);
});

app.listen(port, () => console.log(`Listening on localhost:${port}`));
6

Mọi thứ đã hoạt động mà không có lỗi gì xảy ra

Tiến hay thay đổi text bằng cách click vào button

Text được thay đổi trực tiếp từ phía client, đó chính xác là những gì chúng ta mong đợi.

Demo kết thúc.

3. Kết luận

Mọi thứ trông thật đơn giản bạn nhỉ .

Hướng dẫn react ssr css - phản ứng ssr css
.

Đây chỉ mới được gọi là bước đệm, bước khởi đầu để chúng ta sẽ đi với

module.exports = {
  presets: [
    [
      '@babel/env',
      {
        useBuiltIns: 'usage',
        corejs: require('core-js/package.json').version,
      },
    ],
    '@babel/react',
  ],
};
3 trong React trong tương lai.

Hi vọng bài viết này có ích và giúp bạn trong quá trình tiến tới hoàn thiện khả năng lập trình Web và đặc biệt là luôn giữ tình yêu với React .

Hướng dẫn react ssr css - phản ứng ssr css
.

Nếu bạn cảm thấy thích thú với

module.exports = {
  presets: [
    [
      '@babel/env',
      {
        useBuiltIns: 'usage',
        corejs: require('core-js/package.json').version,
      },
    ],
    '@babel/react',
  ],
};
3 trong React và mong muốn mình chia sẽ thêm về
import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

// Lấy state từ một biến global được đưa vào HTML do server tạo
const initialState = window.APP_STATE;

ReactDOM.hydrate(<App {...initialState} />, document.getElementById('app'));
2 thì hãy comment bên dưới nhé.

Cảm ơn đã đọc bài viết này Repo tại đây

Hướng dẫn react ssr css - phản ứng ssr css
Repo tại đây