跳到主要內容
+

伺服器端渲染

伺服器端渲染最常見的用例是處理使用者(或搜尋引擎爬蟲)首次請求應用程式時的初始渲染。

當伺服器收到請求時,它會將所需的組件渲染成 HTML 字串,然後將其作為響應發送給客戶端。 從那時起,客戶端接管渲染任務。

伺服器上的 Material UI

Material UI 從一開始就設計為可以在伺服器上渲染,但您需要確保它被正確整合。 重要的是為頁面提供所需的 CSS,否則頁面將僅使用 HTML 渲染,然後等待客戶端注入 CSS,導致頁面閃爍(FOUC)。 為了將樣式注入到客戶端,我們需要

  1. 在每次請求時建立一個全新的 emotion cache 實例。
  2. 使用伺服器端收集器渲染 React 樹。
  3. 提取 CSS。
  4. 將 CSS 傳遞給客戶端。

在客戶端,CSS 將被第二次注入,然後移除伺服器端注入的 CSS。

設定

在下面的指南中,我們將看看如何設定伺服器端渲染。

主題

建立一個將在客戶端和伺服器之間共享的主題

theme.js
import { createTheme } from '@mui/material/styles';
import { red } from '@mui/material/colors';

// Create a theme instance.
const theme = createTheme({
  palette: {
    primary: {
      main: '#556cd6',
    },
    secondary: {
      main: '#19857b',
    },
    error: {
      main: red.A400,
    },
  },
});

export default theme;

伺服器端

以下是伺服器端的外觀輪廓。 我們將設定一個 Express 中介軟體,使用 app.use 來處理所有進入伺服器的請求。 如果您不熟悉 Express 或中介軟體,請注意,每次伺服器收到請求時都會調用 handleRender 函數。

server.js
import express from 'express';

// We are going to fill these out in the sections to follow.
function renderFullPage(html, css) {
  /* ... */
}

function handleRender(req, res) {
  /* ... */
}

const app = express();

// This is fired every time the server-side receives a request.
app.use(handleRender);

const port = 3000;
app.listen(port);

處理請求

我們在每次請求中需要做的第一件事是建立一個新的 emotion cache

在渲染時,我們將把根組件 App 包裹在 CacheProviderThemeProvider 中,以使樣式配置和 theme 可用於組件樹中的所有組件。

伺服器端渲染的關鍵步驟是在我們將組件發送到客戶端之前渲染組件的初始 HTML。 為此,我們使用 ReactDOMServer.renderToString()

Material UI 使用 Emotion 作為其預設的樣式引擎。 我們需要從 Emotion 實例中提取樣式。 為此,我們需要為客戶端和伺服器共享相同的快取配置

createEmotionCache.js
import createCache from '@emotion/cache';

export default function createEmotionCache() {
  return createCache({ key: 'css' });
}

透過這樣做,我們正在建立一個新的 Emotion 快取實例,並使用它來提取 HTML 的關鍵樣式。

我們將在 renderFullPage 函數中看到這是如何傳遞的。

import express from 'express';
import * as React from 'react';
import * as ReactDOMServer from 'react-dom/server';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import { CacheProvider } from '@emotion/react';
import createEmotionServer from '@emotion/server/create-instance';
import App from './App';
import theme from './theme';
import createEmotionCache from './createEmotionCache';

function handleRender(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 kickstart an elegant, consistent, and simple baseline
            to build upon. */}
        <CssBaseline />
        <App />
      </ThemeProvider>
    </CacheProvider>,
  );

  // Grab the CSS from emotion
  const emotionChunks = extractCriticalToChunks(html);
  const emotionCss = constructStyleTagsFromChunks(emotionChunks);

  // Send the rendered page back to the client.
  res.send(renderFullPage(html, emotionCss));
}

const app = express();

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);

注入初始組件 HTML 和 CSS

伺服器端的最後一步是將初始組件 HTML 和 CSS 注入到範本中,以便在客戶端渲染。

function renderFullPage(html, css) {
  return `
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My page</title>
        ${css}
        <meta name="viewport" content="initial-scale=1, width=device-width" />
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
        <link
          rel="stylesheet"
          href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
        />
      </head>
      <body>
        <div id="root">${html}</div>
      </body>
    </html>
  `;
}

客戶端

客戶端很簡單。 我們只需要使用與伺服器端相同的快取配置即可。 讓我們看一下客戶端檔案

client.js
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import { CacheProvider } from '@emotion/react';
import App from './App';
import theme from './theme';
import createEmotionCache from './createEmotionCache';

const cache = createEmotionCache();

function Main() {
  return (
    <CacheProvider value={cache}>
      <ThemeProvider theme={theme}>
        {/* CssBaseline kickstart an elegant, consistent, and simple baseline
            to build upon. */}
        <CssBaseline />
        <App />
      </ThemeProvider>
    </CacheProvider>
  );
}

ReactDOM.hydrateRoot(document.querySelector('#root'), <Main />);

參考實作

這是本教程的參考實作。 您可以在 GitHub 儲存庫的 /examples 資料夾下找到更多 SSR 實作,請參閱其他範例