ReactJS: Cách bật tính năng kết xuất phía máy chủ cho ứng dụng React
Cảnh báo: Bài hướng dẫn này nhằm mục đích giới thiệu ngắn gọn về ReactDOM.hydrate()
và ReactDOMServer.rendertoString()
. Nó không được thiết kế để sử dụng cho production.
Ngoài ra, Next.js cung cấp một cách tiếp cận hiện đại để tạo các ứng dụng tĩnh và được kết xuất từ máy chủ được xây dựng bằng React.
Giới thiệu
Kết xuất phía máy chủ (Server Side Rendering - SSR) là một kỹ thuật phổ biến để kết xuất một ứng dụng trang đơn phía máy khách (SPA) trên máy chủ và sau đó gửi một trang được kết xuất đầy đủ đến máy khách. Điều này cho phép các thành phần động được phục vụ dưới dạng đánh dấu HTML tĩnh.
Cách tiếp cận này có thể hữu ích cho việc tối ưu hóa công cụ tìm kiếm (SEO) khi lập chỉ mục không xử lý JavaScript đúng cách. Nó cũng có thể có lợi trong các tình huống tải xuống một gói JavaScript lớn bị ảnh hưởng bởi mạng chậm.
Trong bài hướng dẫn này, bạn sẽ khởi tạo ứng dụng React bằng Create React App và sau đó sửa đổi dự án để kích hoạt hiển thị phía máy chủ.
Ở cuối hướng dẫn này, bạn sẽ có một dự án làm việc với ứng dụng React phía máy khách và ứng dụng Express phía máy chủ.
Điều kiện tiên quyết
Để hoàn thành hướng dẫn này, bạn sẽ cần:
- Node.js được cài đặt cục bộ, bạn có thể thực hiện bằng cách làm theo Cách cài đặt Node.js và Tạo môi trường phát triển cục bộ.
Hướng dẫn này đã được thử nghiệm thành công với Node v16.13.1, npm
v8.1.2, react
v17.0.2, @babel/core
v7.16.0, webpack
v4.44.2, express
v4.17.1, nodemon
v2.0.15 và npm-run-all
v4.1.5.
Bước 1 - Tạo ứng dụng React và sửa đổi component ứng dụng
Đầu tiên, sử dụng npx để khởi động một ứng dụng React mới bằng cách sử dụng phiên bản Create React App mới nhất.
Ta gọi ứng dụng là react-ssr-example:
npx create-react-app react-ssr-example
Sau đó, cd
vào thư mục mới:
cd react-ssr-example
Cuối cùng, khởi động ứng dụng phía máy khách mới để xác minh cài đặt:
npm start
Bạn sẽ thấy một ứng dụng React mẫu được hiển thị trong cửa sổ trình duyệt của bạn.
Bây giờ, trong thư mục src
, hãy tạo một component mới <Home>
:
nano src/Home.js
Tiếp theo, thêm mã sau vào file Home.js
:
function Home(props) { return <h1>Hello {props.name}!</h1>; }; export default Home;
Thao tác này sẽ tạo một heading <h1>
với một thông báo "Hello"
được dẫn đến một tên.
Tiếp theo, hãy kết xuất component <Home>
trong component <App>
. Mở file App.js
trong thư mục src
:
nano src/App.js
Sau đó, thay thế các dòng mã hiện có bằng các dòng mã mới sau:
import Home from './Home'; function App() { return <Home name="Sammy"/>; } export default App;
Điều này sẽ truyền name
tới component <Home>
và vì vậy thông báo mà bạn muốn hiển thị là:
Output
"Hello Sammy!"
Trong file index.js của ứng dụng, bạn sẽ sử dụng phương thức hydrate của ReactDOM thay thế cho render để chỉ ra cho trình kết xuất DOM rằng bạn định rehydrate cho ứng dụng sau khi kết xuất phía máy chủ.
Hãy mở file index.js
trong thư mục src
:
nano src/index.js
Sau đó, thay thế nội dung của file index.js
bằng mã sau:
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.hydrate( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
Điều đó kết thúc việc thiết lập phía máy khách, bạn có thể chuyển sang thiết lập phía máy chủ.
Bước 2 - Tạo máy chủ Express và kết xuất component App
Bây giờ bạn đã có ứng dụng, hãy thiết lập một máy chủ sẽ gửi cùng một phiên bản được kết xuất. Bạn sẽ sử dụng Express cho máy chủ.
Lưu ý: Tạo ứng dụng React
react-scripts
cài đặt phiên bảnexpress
theo yêu cầuwebpack-dev-server
. Để tránh xung đột cây phụ thuộc, hướng dẫn này không còn bao gồm bước cài đặt này nữa.
Tiếp theo, tạo một thư mục mới server
trong thư mục gốc của dự án:
mkdir server
Sau đó, bên trong thư mục server
, tạo một file index.js
mới chứa mã máy chủ Express:
nano server/index.js
Thêm các import sẽ cần và định nghĩa một số hằng số:
import path from 'path'; import fs from 'fs'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; import express from 'express'; import App from '../src/App'; const PORT = process.env.PORT || 3006; const app = express();
Tiếp theo, thêm mã máy chủ với một số xử lý lỗi:
// ... app.get('/', (req, res) => { const app = ReactDOMServer.renderToString(<App />); const indexFile = path.resolve('./build/index.html'); fs.readFile(indexFile, 'utf8', (err, data) => { if (err) { console.error('Something went wrong:', err); return res.status(500).send('Oops, better luck next time!'); } return res.send( data.replace('<div id="root"></div>', `<div id="root">${app}</div>`) ); }); }); app.use(express.static('./build')); app.listen(PORT, () => { console.log(`Server is listening on port ${PORT}`); });
Có thể import component <App>
từ ứng dụng khách trực tiếp từ máy chủ.
Ba điều quan trọng đang diễn ra ở đây:
- Express được sử dụng để cung cấp nội dung từ thư mục
build
dưới dạng tệp tĩnh. renderToString
củaReactDOMServer
được sử dụng để hiển thị ứng dụng thành một chuỗi HTML tĩnh.- File tĩnh
index.html
từ ứng dụng khách đã xây dựng sẽ được đọc. Nội dung tĩnh của ứng dụng được chèn vào<div>
với id là "root". Điều này được gửi dưới dạng phản hồi cho yêu cầu.
Bước 3 - Định cấu hình webpack, Babel và tập lệnh npm
Để mã máy chủ hoạt động, bạn sẽ cần phải đóng gói và chuyển nó, sử dụng webpack và Babel để hoàn thành việc này.
Lưu ý: Phiên bản trước của hướng dẫn này đã được cài đặt babel-core
, babel-preset-env
và babel-preset-react-app
. Các gói này đã được lưu trữ và các phiên bản mono repo đã được sử dụng để thay thế.
Tạo react-scripts
của React App xử lý việc cài đặt các gói sau: webpack
, webpack-cli, webpack-node-externals, @babel/core, babel-loader, @babel/preset-env, @babel/preset-react. Để tránh xung đột cây phụ thuộc, hướng dẫn này không còn bao gồm bước cài đặt này nữa.
Tiếp theo, tạo một tệp cấu hình Babel mới trong thư mục gốc của dự án:
nano .babelrc.json
Sau đó, thêm các preset env
và react-app
:
{ "presets": [ "@babel/preset-env", "@babel/preset-react" ] }
Bây giờ, hãy tạo một cấu hình webpack cho máy chủ sử dụng Babel Loader để truyền mã. Bắt đầu bằng cách tạo file webpack.server.js
trong thư mục gốc của dự án:
nano webpack.server.js
Sau đó, thêm các cấu hình sau vào file webpack.server.js
:
const path = require('path'); const nodeExternals = require('webpack-node-externals'); module.exports = { entry: './server/index.js', target: 'node', externals: [nodeExternals()], output: { path: path.resolve('server-build'), filename: 'index.js' }, module: { rules: [ { test: /\.js$/, use: 'babel-loader' } ] } };
Với cấu hình này, gói máy chủ được chuyển đổi sẽ được xuất ra thư mục server-build
trong một tệp được gọi là index.js
.
Lưu ý việc sử dụng target: 'node'
và externals: [nodeExternals()]
từ webpack-node-externals
, sẽ bỏ qua các tệp từ node_modules
trong gói; máy chủ có thể truy cập trực tiếp các tệp này.
Điều này hoàn tất cài đặt phụ thuộc và cấu hình webpack và Babel.
Bây giờ, hãy truy cập lại package.json
và thêm các tập lệnh npm
của trình trợ giúp:
nano package.json
Thêm các script dev:build-server
, dev:start
và dev
vào file package.json
để xây dựng và cung cấp ứng dụng SSR:
"scripts": { "dev:build-server": "NODE_ENV=development webpack --config webpack.server.js --mode=development -w", "dev:start": "nodemon ./server-build/index.js", "dev": "npm-run-all --parallel build dev:*", // ... },
Script dev:build-server
thiết lập môi thành "development"
và gọi webpack
với tệp cấu hình bạn đã tạo trước đó. Script dev:start
gọi nodemon
để phục vụ output đã xây dựng.
Script dev
gọi npm-run-all
để chạy trong parallel
script build
và tất cả các tập lệnh bắt đầu bằng dev:*
- bao gồm dev:build-server
và dev:start
.
Lưu ý: Bạn không cần phải sửa đổi các script
"start"
, "build", "test" và "eject" hiện có trong filepackage.json
.
nodemon
được sử dụng để khởi động lại máy chủ khi các thay đổi được thực hiện. npm-run-all
được sử dụng để chạy nhiều lệnh song song.
Hãy cài đặt các gói đó ngay bây giờ bằng cách nhập các lệnh sau vào cửa sổ đầu cuối của bạn:
npm install nodemon@2.0.15 --save-dev npm install npm-run-all@4.1.5 --save-dev
Với điều này tại chỗ, bạn có thể chạy phần sau để tạo ứng dụng phía máy khách, gói và truyền mã máy chủ, đồng thời khởi động máy chủ trên :3006
:
npm run dev
Cấu hình webpack của máy chủ bây giờ sẽ theo dõi các thay đổi và máy chủ sẽ khởi động lại khi có các thay đổi. Tuy nhiên, đối với ứng dụng khách sẽ yêu cầu được xây dựng lại theo cách thủ công mỗi khi thực hiện thay đổi.
Lưu ý: Có một vấn đề mở cho điều đó ở đây.
Bây giờ, hãy mở http://localhost:3006/
trong trình duyệt web của bạn và quan sát ứng dụng được hiển thị phía máy chủ của bạn.
Trước đó mã nguồn là:
<div id="root"></div>
Còn bây giờ mã nguồn là:
<div id="root"><h1 data-reactroot="">Hello <!-- -->Sammy<!-- -->!</h1></div>
Kết xuất phía máy chủ đã chuyển đổi thành công component <App>
này thành HTML.
Phần kết luận
Trong bài hướng dẫn này, bạn đã khởi tạo ứng dụng React và kích hoạt hiển thị phía máy chủ.
Với bài viết này, chúng ta chỉ sơ bộ như vậy. Mọi thứ có xu hướng trở nên phức tạp hơn một chút khi route, tìm nạp dữ liệu hoặc Redux cũng cần phải là một phần của ứng dụng được kết xuất phía máy chủ.
Một lợi ích chính của việc sử dụng SSR là có một ứng dụng có thể được thu thập thông tin về nội dung của nó, ngay cả đối với các trình thu thập thông tin không thực thi mã JavaScript. Điều này có thể giúp tối ưu hóa công cụ tìm kiếm (SEO) và cung cấp siêu dữ liệu cho các kênh truyền thông xã hội.
SSR cũng thường có thể giúp tăng hiệu suất vì ứng dụng đã tải đầy đủ được gửi xuống từ máy chủ theo yêu cầu đầu tiên. Đối với các ứng dụng không tầm thường, quãng đường của bạn có thể thay đổi vì SSR yêu cầu thiết lập có thể hơi phức tạp và nó tạo ra tải trọng lớn hơn trên máy chủ. Việc sử dụng kết xuất phía máy chủ cho ứng dụng React của bạn hay không tùy thuộc vào nhu cầu cụ thể của bạn và sự cân bằng nào phù hợp nhất với trường hợp sử dụng của bạn.