Render Modes (SPA, SSR, SSG, HTML-only)

对于每个页面,我们可以选择不同的渲染模式:

  • SPA
  • SSR
  • HTML-only
  • Pre-rendering (aka SSG)

比如,我们可以使用 SPA 渲染管理员面板,而使用 SSR 渲染营销页面

"SPA", "SSR", "HTML-only" and "SSG" 的含义以及应该选择哪个,在 SPA vs SSR (and more) 中讲解

vite-plugin-ssr 模板默认选择 SSR,这是适用于大多数应用程序的合理默认设置。

SPA

SPA 意味着页面仅在浏览器中加载和渲染

为此:

  1. 定义 .page.client.js 替代 .page.js
  2. 调整 render() hook

1. .page.js => .page.client.js

通过定义 /pages/about.page.client.js 替代 /pages/about.page,以确保页面仅在浏览器中加载

例子:

2. render() hook (仅 SPA)

如果我们只有 SPA 页面,那么可以像下面一样调整 render() hook

客户端 render() hook:

// /renderer/_default.page.client.js
// Environment: Browser

import { renderToDom } from "some-ui-framework";

export { render };

async function render(pageContext) {
  const { Page } = pageContext;
  // UI 框架通常会有 `renderToDom()` 和 `hydrateDom()` 两个方法
  // 注意使用 `renderToDom()` 而非 `hydrateDom()`
  await renderToDom(document.getElementById("root"), Page);
}

参考 What is Hydration?,以理解 "rendering to the DOM" 和 "hydrating the DOM" 之间的不同

也需调整服务端 render() hook:

// /renderer/_default.page.server.js
// Environment: Node.js

import { escapeInject } from "vite-plugin-ssr";

export function render() {
  // 注意 `div#root` 是空的
  return escapeInject`<html>
    <body>
      <div id="root"></div>
    </body>
  </html>`;
}

这是 SPA 和 SSR 之间主要的区别:在 SPA 中,div#root 是空的,而在 SSR 中,div#root 包含了渲染页面 HTML 的根组件 pageContext.Page

这意味着,针对 SPA,我们使用服务器端 render() hook 只是为了生成一个空的 HTML:HTML 不包含页面的内容

生产环境中,我们通常希望 pre-render SPA 页面的 HTML,以便消除生产环境对 Node.js 服务器的需求

我们也可以使用服务端 render() hook 来渲染 <head>

// /renderer/_default.page.server.js
// Environment: Node.js

import { escapeInject } from "vite-plugin-ssr";

export function render(pageContext) {
  const { title, description } = pageContext.exports.meta;
  // 尽管只在浏览器中加载和渲染页面组件,
  // 我们也应在服务端定义页面的 `<title>` 和 `<meta name="description">`
  return escapeInject`<html>
    <head>
      <title>${title}</title>
      <meta name="description" content="${description}" />
    </head>
    <body>
      <div id="root"></div>
    </body>
  </html>`;
}

指南 > 自定义 Exports/Hooks 中讲解了 pageContext.exports

// /pages/about.page.js
export const meta = {
  title: "About | My App",
  description: "My App is ...",
};

请注意,我们在 about.page.js 中定义 pageContext.exports.meta,而不是 about.page.client.js。因为我们需要在服务端访问 pageContext.exports.meta

也就是说,我们同时定义了 about.page.js(定义 meta 数据) 和 about.page.client.js (定义页面的根组件 pageContext.Page

2. render() hooks (SPA + SSR)

如果同时存在 SPA 和 SSR,那么我们需要像以下调整 render() hook:

// /renderer/_default.page.server.js
// Environment: Node.js

import { escapeInject, dangerouslySkipEscape } from "vite-plugin-ssr";
import { renderToHtml } from "some-ui-framework";

export function render(pageContext) {
  let pageHtml;
  if (pageContext.Page) {
    // For SSR pages
    pageHtml = renderToHtml(pageContext.Page);
  } else {
    // For SPA pages
    pageHtml = "";
  }
  return escapeInject`<html>
    <body>
      <div id="root">${dangerouslySkipEscape(pageHtml)}</div>
    </body>
  </html>`;
}

如果我们在 .page.client.js 而不是 .page.js 中定义页面的根组件(pageContext.Page),那么 pageContext.Page 仅在浏览器中渲染

// /renderer/_default.page.client.js
// Environment: Browser

import { renderToDom, hydrateDom } from "some-ui-framework";

export async function render(pageContext) {
  const { Page } = pageContext;
  const root = document.getElementById("root");
  if (
    // 我们通过使用 `innerHTML === ''` 来探测SPA的首次渲染
    root.innerHTML === "" ||
    // 使用客户端路由导航时, vite-plugin-ssr 将 `pageContext.isHydration` 设置为 `false`
    !pageContext.isHydration
  ) {
    // - SPA 页面没有 hydration 步骤:它们需要完全渲染
    // - SSR 的页面导航也需要完全渲染(如果使用客户端路由)
    await renderToDom(root, Page);
  } else {
    // SSR 页面的第一次渲染只是 hydration(而不是完全渲染)
    await hydrateDom(root, Page);
  }
}

React example: /examples/render-modes/.

Vue Example: GitHub > AaronBeaudoin/vite-plugin-ssr-example.

SSR

vite-plugin-ssr 模板和文档默认使用 SSR

所以,如果我们的页面都是 SSR 渲染,那么我们只需:按照模板/文档简单操作

如果同时拥有 SSR 和 SPA 页面, 请参考 the SPA section

预渲染

参考 指南 > 预渲染 (SSG).

HTML-only

⚠ 使用现代UI框架(React/Vue/...)将页面渲染为纯HTML是一种新技术,应被视为实验性的

将页面渲染为纯 HTML:

  1. 定义 .page.server.js 替代 .page.js.
  2. (可选) 定义 .page.client.js (例如: 添加极少的 JavaScript 注入交互性)
  3. includeAssetsImportedByServer 设置为 true
// /pages/about.page.server.js
// Environment: Node.js

// 通常在 `*.page.js` 中定义 `Page`,但对于 HTML-only
// 我们在 `*.page.server.js` 中定义 `Page`
export { Page };

function Page() {
  return (
    <>
      <h1>HTML-only page</h1>
      <p>
        This page is rendered only to HTML. (It's not loaded/rendered in the
        browser-side.)
      </p>
    </>
  );
}
// /pages/about.page.client.js
// Environment: Browser

// 此文件代表了整个浏览器端的 JavaScript
// 我们可以省略定义 `.page.client.js`,在这种情况下浏览器端就完全没有 JavaScript 了

console.log("I'm the page's only browser-side JavaScript line.");
// vite.config.js

import { ssr } from "vite-plugin-ssr/plugin";

export default {
  plugins: [
    ssr({
      includeAssetsImportedByServer: true,
    }),
  ],
};

Examples: