<head>

服务端 render() hook 中定义 head 标签(如 <title><meta name="description">

我们还可以按页和按组件地定义 head 标签,请参阅以下内容

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

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

export { render };

async function render(pageContext) {
  return escapeInject`<html>
    <head>
      <title>SpaceX</title>
      <meta name="description" content="We deliver payload to space.">
    </head>
    <body>
      <div id="root">
        ${dangerouslySkipEscape(await renderToHtml(pageContext.Page))}
      </div>
    </body>
  </html>`;
}

按页 (静态)

我们可以使用 Custom Export 为指定页面定义 head 标签

// /pages/about.page.js

// 自定义Exports
export const documentProps = {
  // title 和 description 会覆盖默认值
  title: "About SpaceX",
  description: "Our mission is to explore the galaxy.",
};
// /renderer/_default.page.server.js
// Environment: Node.js

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

export { render };

async function render(pageContext) {
  // 自定义Exports可在此处作为 `pageContext.exports.documentProps` 使用
  const { documentProps } = pageContext.exports;

  // 默认值
  const title = documentProps.title || "SpaceX";
  const description =
    documentProps.description || "We deliver payload to space.";

  return escapeInject`<html>
    <head>
      <title>${title}</title>
      <meta name="description" content="${description}">
    </head>
    <body>
      <div id="root">
        ${dangerouslySkipEscape(await renderToHtml(pageContext.Page))}
      </div>
    </body>
  </html>`;
}

按页 (动态)

我们可以使用 Custom Export 定义动态的 head 标签(运行时确定)

// /pages/rocket.page.route.js
export const '/rocket/@rocketSlug'
// /pages/rocket.page.js

// 自定义Exports
export { getDocumentProps };

function getDocumentProps(pageProps) {
  return {
    title: pageProps.product.name,
    description: pageProps.product.description,
  };
}
// /renderer/_default.page.server.js
// Environment: Node.js

export { render };

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

async function render(pageContext) {
  // `pageProps` 由数据获取机制提供,参见 “数据获取” 指南
  const { Page, pageProps } = pageContext;

  // 自定义Exports可在此处作为 `pageContext.exports.documentProps` 使用
  const { getDocumentProps } = pageContext.exports;

  // `getDocumentProps()` 可以使用获取的数据来提供页面的元数据
  const { title, description } = getDocumentProps(pageProps);

  return escapeInject`<html>
    <head>
      <title>${title}</title>
      <meta name="description" content="${description}">
    </head>
    <body>
      <div id="root">
        ${dangerouslySkipEscape(await renderToHtml(pageContext.Page))}
      </div>
    </body>
  </html>`;
}

按组件

通过一些深度嵌套的视图(React/Vue/...)组件:

  1. documentProps 添加到 passToClient
  2. pageContext.documentProps 传递给所有组件,请参考 Guides > Access pageContext anywhere
  3. 在深度嵌套的组件中修改 pageContext.documentProps

例子:

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

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

export async function render(pageContext) {
  // 使用 UI 框架将 `pageContext.documentProps` 传递给所有组件
  //(如 React Context 或 Vue 的 `app.config.globalProperties`)
  const pageHtml = await renderToHtml(
    <ContextProvider documentProps={pageContext.documentProps}>
      <Page />
    </ContextProvider>
  );

  // 此处发生的是:
  // 1. UI 框架将 `documentProps` 传递给所有组件
  // 2. (深度嵌套的)组件修改了 `documentProps`
  // 3. 将 `documentProps` 渲染到 HTML meta 标签中
  return escapeInject`<html>
    <head>
      <title>${pageContext.documentProps.title}</title>
      <meta name="description" content="${
        pageContext.documentProps.description
      }">
    </head>
    <body>
      <div id="app">
        ${dangerouslySkipEscape(pageHtml)}
      </div>
    </body>
  </html>`;
}
// 在组件树深处的某个组件中

// 由于之前的步骤,使得我们可以在这里使用 `documentProps`
documentProps.title = "I was set by some deep component.";
documentProps.description = "Me too.";

客户端路由

若使用客户端路由,我们需要确保在页面导航时更新 document.title

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

// 使 `pageContext.documentProps` 在浏览器环境中可用
export const passToClient = ["documentProps", "pageProps"];
// /renderer/_default.page.client.js
// Environment: Browser

export { render };

function render(pageContext) {
  if (!pageContext.isHydration) {
    // 页面导航 — 更新网页title
    document.title = pageContext.documentProps.title;
  }
  // ...
}

第三方库

我们也可以使用诸如 @vueuse/headreact-helmet 之类的库

我们建议仅当你有以下理由时才使用此类库: 上述解决方案更简单,适用于大多数用例

Head 库已经净化了 HTML <head>,这意味着我们可以跳过 escapeInject 并使用 dangerouslySkipEscape() 包装整个结果

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

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

export async function render(pageContext) {
  return dangerouslySkipEscape(await renderToHtml(pageContext.Page));
}

Markdown

对于使用 markdown 定义的页面,请参考 集成 > Markdown > <head> (pageContext.exports)