Server Rendering React.js + Material UI web application

Trong bài viết này, chúng ta sẽ cùng nhau thực hiện render ứng dụng React.js ở phía server. 1. Setup environment Init project $ mkdir ssr $ npm init -y Install dependencies $ npm i @babel/[email protected] @babel/[email protected] @babel/[email protected] @babel/[email protected] @babel/[email protected] @emotion/[email protected] @emotion/[email protected] @emotion/[email protected] @emotion/[email protected] @material-ui/[email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected] Webpack config # webpack.config.js const path =require('path'); module.exports ={ entry:'./client.js',

Trong bài viết này, chúng ta sẽ cùng nhau thực hiện render ứng dụng React.js ở phía server.

1. Setup environment

Init project

$ mkdir ssr
$ npm init -y

Install dependencies

$ npm i @babel/[email protected] @babel/[email protected] @babel/[email protected] @babel/[email protected] @babel/[email protected] @emotion/[email protected] @emotion/[email protected] @emotion/[email protected] @emotion/[email protected] @material-ui/[email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected]

Webpack config

# webpack.config.js

const path =require('path');

module.exports ={
  entry:'./client.js',
  mode: process.env.NODE_ENV||'development',
  output:{
    path: path.resolve(__dirname,'build'),
    filename:'bundle.js',
    publicPath:'/',},
  module:{
    rules:[{
        test:/.js$/,
        exclude:/node_modules/,
        loader:'babel-loader',},],},};

.babelrc

# .babelrc
{
  "presets": ["@babel/preset-env", "@babel/preset-react"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

Scripts

package.json
 "scripts":{"start":"npm-run-all -p build serve","build":"webpack -w","serve":"nodemon --ignore ./build --exec babel-node -- server.js","production":"cross-env NODE_ENV=production npm start","post-update":"echo "codesandbox preview only, need an update" && yarn upgrade --latest"}

2. Setup express.js server

Tạo file server.js ở root

server.js

# server.js
import express from"express"const app =express()

app.get("/",(req, res)=> res.send("Hello world"))const port =3000
app.listen(port,()=>{
  console.log(`Listening on ${port}`)})

Sau đó chạy:

$ npm run serve

Thử dùng trình duyệt truy cập http://localhost:3000

3. Server rendering React.js application

Create React.js application with a App component

Tạo file client.js ở root với nội dung như sau

# client.js
import*as React from"react"import ReactDOM from"react-dom"import App from"./App"functionMain(){return<App />}

ReactDOM.hydrate(<Main />, document.querySelector("#root"))

Tạo file App.js ở root với nội dung như sau:

#App.js
import*as React from"react"exportdefaultfunctionApp(){return<div>SSR React App</div>}

Cập nhật file server.js lại như sau:

import express from"express"import ReactDOMServer from"react-dom/server"import*as React from"react"import App from"./App"const app =express()functionrenderFullPage(html){return`
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>My page</title>
        <meta name="viewport" content="initial-scale=1, width=device-width" />
      </head>
      <body>
        <script async src="build/bundle.js"></script>
        <div id="root">${html}</div>
      </body>
    </html>
  `}functionhandleRender(req, res){// Render the component to a string.const html = ReactDOMServer.renderToString(<App />)// Send the rendered page back to the client.
  res.send(renderFullPage(html))}

app.use("/build", express.static("build"))// This is fired every time the server-side receives a request.
app.use(handleRender)const port =3000
app.listen(port,()=>{
  console.log(`Listening on ${port}`)})

Sau đó chạy:

$ npm run serve

Truy cập vào http://localhost:3000 ta sẽ nhận được kết quả: SSR React App

Như vậy, ta đã thành công trong việc render một ứng dụng React.js ở phía server 💯💯 nhưng khoan đã ! Làm thế nào chúng ta biết ứng dụng của mình đã render ở Server, rất đơn giản, bấm tổ hợp phím Ctrl + U để mở sourcecode của trang web lên xem. Và ta sẽ nhìn thấy ở giữa thẻ #root là thẻ <div data-reactroot="">SSR React App</div>. Điều này chứng tỏ, ứng dụng web của ta đã render đầy đủ các thẻ ở server và trả đầy đủ về phía client. 😍😍

4. Material UI

Bây giờ sẽ tăng độ khó lên, khi có sự tham gia của CSS-IN-JS vào trang web.

Những ứng dụng react.js có sử dụng giải pháp css-in-js, nghĩa là viết css bên trong các file javascript. Rồi khi ứng dụng chạy lên, javascript mới tạo ra các thẻ style và tiêm vào trang web. Điều này là rất bình thường đối với một trang web single page application. Tuy nhiên, khi đưa vào bài toán Server Rendering, thì nó sinh ra một vấn đề, vấn đề đó là: FOUC

FOUC – Flash of unstyled content là hiện tượng, css về tới browser không cùng lúc với html. Có thể css đó của một server nào đó khác (third-party) và html được trả về client trước, rồi phải một lúc sau css mới về tới, lúc này xảy ra hiện tượng là một document không có style được flash một cái rồi mới có style.

Giải pháp: ta sẽ render luôn cái đống css của thư viện css-in-js đang sử dụng trong trang web, và chúng ta sẽ tiêm (inject) vào cái file html trả về client. Như vậy, sẽ loại bỏ được thời gian delay và file html của ta sẽ có style ngay thời điểm nó về tới trình duyệt.

Emotion cache

Đầu tiên, tạo file createEmotionCache.js ở root với nội dung:

# createEmotionCache.js
import createCache from"@emotion/cache"exportdefaultfunctioncreateEmotionCache(){returncreateCache({ key:"youuuuuuunguyen"})}

Mui Theme

Tạo theme sẽ được dùng chung cho phía server và client, tạo file theme.js ở root với nội dung:

# theme.js
import{ createTheme }from"@material-ui/core/styles"import{ red }from"@material-ui/core/colors"// Create a theme instance.const theme =createTheme({
  palette:{
    primary:{
      main: red[700],},},})exportdefault theme

Update server.js

import express from"express"import ReactDOMServer from"react-dom/server"import*as React from"react"import App from"./App"import createEmotionServer from"@emotion/server/create-instance"import createEmotionCache from"./createEmotionCache"import{ ThemeProvider }from"@material-ui/core/styles"import{ CacheProvider }from"@emotion/react"import theme from"./theme"import CssBaseline from"@material-ui/core/CssBaseline"const app =express()functionrenderFullPage(html, styles){return`
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>My page</title>
        <meta name="viewport" content="initial-scale=1, width=device-width" />
        ${styles}
      </head>
      <body>
        <script async src="build/bundle.js"></script>
        <div id="root">${html}</div>
      </body>
    </html>
  `}functionhandleRender(req, res){const cache =createEmotionCache()const{ extractCriticalToChunks, constructStyleTagsFromChunks }=createEmotionServer(cache)// Render the component to a string.const html = ReactDOMServer.renderToString(<CacheProvider value={cache}><ThemeProvider theme={theme}><CssBaseline /><App /></ThemeProvider></CacheProvider>)// Grab the CSS from emotionconst chunks =extractCriticalToChunks(html)const styles =constructStyleTagsFromChunks(chunks)// Send the rendered page back to the client.
  res.send(renderFullPage(html, styles))}

app.use("/build", express.static("build"))// This is fired every time the server-side receives a request.
app.use(handleRender)const port =3000
app.listen(port,()=>{
  console.log(`Listening on ${port}`)})

Update client.js

Cập nhật nội dung client.js như sau:

import*as React from"react"import ReactDOM from"react-dom"import App from"./App"import{ ThemeProvider }from"@material-ui/core/styles"import theme from"./theme"import CssBaseline from"@material-ui/core/CssBaseline"import{ CacheProvider }from"@emotion/react"import createEmotionCache from"./createEmotionCache"const cache =createEmotionCache()functionMain(){return(<CacheProvider value={cache}><ThemeProvider theme={theme}><CssBaseline /><App /></ThemeProvider></CacheProvider>)}

ReactDOM.hydrate(<Main />, document.querySelector("#root"))

5. Conclusion

Nếu như các bạn từng xem về cách để implement server rendering cho React và Material UI, sẽ thấy ngày trước người ta phải xóa bỏ cái styles đã inject vào html ở phía server vì như thế sẽ duplicate với đống styles được tạo ra ở client. Nhưng với EmotionCache, thì khi sử dụng cùng một config (cụ thể là cùng key), phía Emotion sẽ không để xảy ra việc duplicate này.

6. References

[1]. https://next.material-ui.com/guides/server-rendering

[2]. https://emotion.sh/docs/ssr#on-server

Nguồn: viblo.asia

Bài viết liên quan

Thay đổi Package Name của Android Studio dể dàng với plugin APR

Nếu bạn đang gặp khó khăn hoặc bế tắc trong việc thay đổi package name trong And

Lỗi không Update Meta_Value Khi thay thế hình ảnh cũ bằng hình ảnh mới trong WordPress

Mã dưới đây hoạt động tốt có 1 lỗi không update được postmeta ” meta_key=

Bài 1 – React Native DevOps các khái niệm và các cài đặt căn bản

Hướng dẫn setup jenkins agent để bắt đầu build mobile bằng jenkins cho devloper an t

Chuyển đổi từ monolith sang microservices qua ví dụ

1. Why microservices? Microservices là kiến trúc hệ thống phần mềm hướng dịch vụ,