サーバサイドレンダリング

実験的な機能

SSR のサポートはまだ実験段階で、バグやサポートされていないユースケースが発生する可能性があります。ご自身の責任で進めてください。

注意

SSR は特に、Node.js で同じアプリケーションを実行し、HTML を先読みレンダリングし、最後にクライアントでハイドレートすることをサポートするフロントエンドフレームワーク(React、Preact、Vue、Svelte など)を指します。 従来のサーバサイドフレームワークとの統合をお探しの場合は、代わりに バックエンド統合ガイド を確認してください。

次のガイドは、選択したフレームワークで SSR を使用した経験があることも前提としており、Vite 固有の統合の詳細のみに焦点を当てています。

低レベル API

これは、ライブラリやフレームワーク製作者のための低レベル API です。アプリケーションを作成することが目的ならば、まず Awesome Vite の SSR セクションにある高レベルの SSR プラグインとツールを確認してください。とはいえ、多くのアプリケーションは、Vite のネイティブの低レベル API 上に直接構築されています。

ヘルプ

質問がある場合は、Vite Discord の #ssr チャンネルでコミュニティがいつでも助けてくれます。

プロジェクトの例

Vite はサーバサイドレンダリング ( SSR ) の組み込みサポートを提供します。Vite プレイグラウンドには、Vue 3 および React の SSR セットアップの例が含まれています。これらは、このガイドのリファレンスとして使用できます。:

ソースファイルの構成

一般的な SSR アプリケーションは、次のようなソースファイル構造になります。:

- index.html
- src/
  - main.js          # exports env-agnostic (universal) app code
  - entry-client.js  # mounts the app to a DOM element
  - entry-server.js  # renders the app using the framework's SSR API

index.htmlentry-client.js を参照し、サーバサイドでレンダリングされたマークアップを挿入するためにプレースホルダを含める必要があります。:

<div id="app"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.js"></script>

正確に置き換えることができる限り、<!--ssr-outlet--> の代わりに任意のプレースホルダを使用することができます。

条件付きロジック

SSR とクライアントに基づいて条件付きロジックを実行する場合は次を使用できます。

if (import.meta.env.SSR) {
  // ... サーバのロジック
}

これはビルド中に静的に置き換えられるため、未使用のブランチのツリーシェイクが可能になります。

開発サーバのセットアップ

SSR をビルドする際、メインサーバを完全に制御し、Vite を本番環境から切り離したいと思うでしょう。したがってミドルウェアモードで Vite を使用することをお勧めします。これは express の例です:

server.js

















 
 
 









const fs = require('fs')
const path = require('path')
const express = require('express')
const { createServer: createViteServer } = require('vite')

async function createServer() {
  const app = express()

  // ミドルウェアモードで Vite サーバを作成します。これにより、Vite 自体のHTMLが無効になります。
  // ロジックを提供し、親サーバに制御を任せます。
  //
  // Vite 独自の HTML 配信ロジックを使用したい場合(Vite を開発用ミドルウェアとして使用する場合)は、
  // 代わりに 'html' を使用します。
  const vite = await createViteServer({
    server: { middlewareMode: 'ssr' }
  })
  // Vite の接続インスタンスをミドルウェアとして使用します。
  app.use(vite.middlewares)

  app.use('*', async (req, res) => {
    // index.html を提供します - 次にこれに取り組みます。
  })

  app.listen(3000)
}

createServer()

ここで viteViteDevServer のインスタンスです。 vite.middlewares は、connect 互換の Node.js フレームワークでミドルウェアとして使用できる Connect インスタンスです。

次のステップはサーバサイドでレンダリングされた HTML を提供するための * ハンドラの実装です:

app.use('*', async (req, res) => {
  const url = req.originalUrl

  try {
    // 1. index.html を読み込む
    let template = fs.readFileSync(
      path.resolve(__dirname, 'index.html'),
      'utf-8'
    )

    // 2. Vite を使用して HTML への変換を適用します。これにより Vite の HMR クライアントが定義され
    //    Vite プラグインからの HTML 変換も適用します。 e.g. global preambles
    //    from @vitejs/plugin-react-refresh
    template = await vite.transformIndexHtml(url, template)

    // 3. サーバサイドのエントリポイントを読み込みます。 vite.ssrLoadModule は自動的に
    //    ESM を Node.js で使用できるコードに変換します! ここではバンドルは必要ありません
    //    さらに HMR と同様に効率的な無効化を提供します。
    const { render } = await vite.ssrLoadModule('/src/entry-server.js')

    // 4. アプリケーションで HTML をレンダリングします。これは entry-server.js からエクスポートされた `render` を使用しています。
    //    関数は適切なフレームワーク SSR API を呼び出します。
    //    e.g. ReactDOMServer.renderToString()
    const appHtml = await render(url)

    // 5. アプリケーションでレンダリングされた HTML をテンプレートに挿入します。
    const html = template.replace(`<!--ssr-outlet-->`, appHtml)

    // 6. レンダリングされた HTML をクライアントに送ります。
    res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
  } catch (e) {
    // エラーが検出された場合は、Vite に stracktrace を修正させて、次のようにマップします。
    // 実際のソースコード
    vite.ssrFixStacktrace(e)
    console.error(e)
    res.status(500).end(e.message)
  }
})

package.jsondev スクリプトも代わりにサーバスクリプトを使用するように変更する必要があります:

  "scripts": {
-   "dev": "vite"
+   "dev": "node server"
  }

プロダクションビルド

SSR プロジェクトを本番環境に適用するには次の作業を行う必要があります:

  1. 通常通りクライアントビルドします。
  2. SSR ビルドを作成します、これは require() を介して直接ロードできるので、Vite の ssrLoadModule を経由する必要はありません。

package.json は次のようになります:

{
  "scripts": {
    "dev": "node server",
    "build:client": "vite build --outDir dist/client",
    "build:server": "vite build --outDir dist/server --ssr src/entry-server.js "
  }
}

これは SSR ビルドだということを示す --ssr フラグに注意してください。 また、SSR エントリを指定する必要があります。

次に server.jsprocess.env.NODE_ENV をチェックして本番固有のロジックを追加する必要があります:

  • ルートの index.html を読み取る代わりに dist/client/index.html を使用します。これはクライアントビルドへの正しいアセットリンクが含まれているためです。

  • await vite.ssrLoadModule('/src/entry-server.js') の代わりに require('./dist/server/entry-server.js') を使用します(このファイルは SSR ビルドした結果のファイルです)。

  • vite 開発サーバの作成とすべての使用を開発専用サーバかどうかの条件分岐の後ろに移動します。次に静的ファイルを提供するミドルウェアを追加し、dist/client からファイルを提供します。

詳しくは VueReact のデモを参照してください。

Preload Directives の作成

vite build はビルド出力ディレクトリに ssr-manifest.json を生成する --ssrManifest フラグをサポートしています:

- "build:client": "vite build --outDir dist/client",
+ "build:client": "vite build --outDir dist/client --ssrManifest",

上記のスクリプトはクライアントビルドの際に dist/client/ssr-manifest.json を生成します(モジュール ID をクライアントファイルにマップするため、SSR マニフェストはクライアントビルドから生成されます)。マニフェストには、モジュール ID の関連するチャンクおよびアセットファイルへのマッピングが含まれています。

マニフェストを活用するには、フレームワークはサーバのレンダリング呼び出し中に使用されたコンポーネントのモジュール ID を収集する方法を提供する必要があります。

@vitejs/plugin-vue は自動で使用されたコンポーネントモジュール ID を関連する VueSSR コンテキストに登録することを標準でサポートしています:

// src/entry-server.js
const ctx = {}
const html = await vueServerRenderer.renderToString(app, ctx)
// ctx.modules はレンダリング中にしようされたモジュール ID をセットします。

本番ブランチの server.js では、マニフェストを読み取って、src/entry-server.js によってエクスポートされた render 関数に渡す必要があります。これにより、非同期ルートで使用されるファイルのプリロードディレクティブをレンダリングするのに十分な情報が得られます! 詳しくは demo source をご覧ください。

Pre-Rendering / SSG

ルートと特定のルートに必要なデータが事前にわかっている場合は、本番 SSR と同じロジックを使用して、これらのルートを静的 HTML に先読みでレンダリングすることができます。これは、SSG の形式と見なすこともできます。 詳しくは demo pre-render script をご覧ください。

外部 SSR

多くの依存関係は、ESM ファイルと CommonJS ファイルの両方を出荷します。SSR を実行する場合、CommonJS ビルドを提供する依存関係を Vite の SSR トランスフォーム/モジュールシステムから外部化することで、開発とビルドの両方を高速化できます。 例えば、あらかじめバンドルされている ESM バージョンの React を用いて、それから変換してそれを Node.js 互換に戻す代わりに、単純に require('react') を使用する方が効率的です。また、SSR バンドルビルドの速度も大幅に向上します。

Vite は、次のヒューリスティックに基づいて自動化された SSR 外部化を実行します:

  • 依存関係の解決された ESM エントリポイントとそのデフォルトの Node エントリポイントが異なる場合、そのデフォルトの Node エントリはおそらく外部化できる CommonJS ビルドです。例えば、vue は ESM ビルドと CommonJS ビルドの両方を出荷するため、自動的に外部化されます。

  • それ以外の場合、Vite はパッケージのエントリポイントに有効な ESM 構文が含まれているかどうかを確認します。含まれていない場合、パッケージは CommonJS の可能性が高く、外部化されます。例として、react-dom は、CommonJS 形式の単一のエントリのみを指定するため、自動的に外部化されます。

このヒューリスティックがエラーにつながる場合は、ssr.external および ssr.noExternal のオプションを使用して SSR の外部化を手動で調整することができます。

将来的には、このヒューリスティックは、プロジェクトで type: "module" が有効になっているかどうかを検出するように改善される可能性があります。これにより、Vite は、SSR 中に動的な import() を介してインポートすることにより、Node 互換の ESM ビルドを出荷する依存関係を外部化することもできます。

エイリアスの操作

あるパッケージを別のパッケージにリダイレクトするエイリアスを設定した場合は、SSR の外部化された依存関係で機能するように、代わりに実際の node_modules パッケージにエイリアスを設定することをお勧めします。Yarnpnpm の両方で npm: のエイリアスをサポートします。

SSR 固有のプラグインロジック

Vue や Svelte などの一部のフレームワークは、クライアントと SSR に基づいてコンポーネントをさまざまな形式にコンパイルします。条件付き変換をサポートするために、Vite は次のプラグインフックの options オブジェクトに、追加の ssr プロパティをに渡します:

  • resolveId
  • load
  • transform

例:

export function mySSRPlugin() {
  return {
    name: 'my-ssr',
    transform(code, id, options) {
      if (options?.ssr) {
        // SSR 固有の transform を実行する...
      }
    }
  }
}

loadtransform の options オブジェクトは省略可能であり、rollup は現在このオブジェクトを使用していませんが、将来的に追加のメタデータでこれらのフックを拡張する可能性があります。

::: 注意 Vite 2.7 以前では、options オブジェクトを使うのではなく、固定の ssr 引数を使ってプラグインフックに通知されていました。すべての主要なフレームワークとプラグインは更新されていますが、以前の API を使っている古い記事が見つかる場合があります。 :::

SSR ターゲット

SSR ビルドのデフォルトターゲットは Node 環境ですが、Web Worker でサーバを実行することもできます。パッケージのエントリの解決方法はプラットフォームごとに異なります。ターゲットを Web Worker に設定するには、ssr.target'webworker' に設定します。

SSR バンドル

webworker のランタイムなどの場合、SSR のビルドを 1 つの JavaScript ファイルにバンドルしたい場合があります。ssr.noExternaltrue に設定することで、この動作を有効にできます。これは 2 つのことを行います:

  • すべての依存関係を noExternal として扱う
  • Node.js のビルドインがインポートされた場合、エラーを投げる