サーバーサイドレンダリング (SSR)
注意
SSR とは、特にフロントエンドフレームワーク (React、Preact、Vue、Svelte など) が Node.js で同じアプリケーションを実行し、それを HTML に事前レンダリングし、最終的にクライアントでハイドレーションする機能を指します。従来のサーバーサイドフレームワークとの統合をお探しの場合は、「バックエンド統合ガイド」を参照してください。
以下のガイドは、選択したフレームワークで SSR を扱った経験があることを前提としており、Vite に特化した統合の詳細のみに焦点を当てています。
低レベル API
これは、ライブラリやフレームワークの作者向けの低レベル API です。アプリケーションを作成することが目的の場合は、まず Awesome Vite SSR セクションのより高レベルな SSR プラグインとツールを確認してください。とはいえ、多くのアプリケーションは Vite のネイティブな低レベル API 上で直接構築されています。
現在、Vite は Environment API を用いた改良された SSR API に取り組んでいます。詳細はリンクを参照してください。
例プロジェクト
Vite はサーバーサイドレンダリング (SSR) の組み込みサポートを提供します。create-vite-extra には、このガイドの参考として使用できる SSR セットアップの例が含まれています。
create-vite を実行し、フレームワークオプションで Others > create-vite-extra を選択することで、これらのプロジェクトをローカルでスキャフォールドすることもできます。
ソース構造
典型的な SSR アプリケーションは、以下のソースファイル構造を持ちます。
- index.html
- server.js # main application server
- 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 APIindex.html は entry-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) {
// ... server only logic
}これはビルド中に静的に置き換えられるため、使用されていないブランチのツリーシェイキングが可能になります。
開発サーバーのセットアップ
SSR アプリを構築する場合、メインサーバーを完全に制御し、Vite を本番環境から切り離したいと考えるでしょう。したがって、Vite をミドルウェアモードで使用することをお勧めします。以下は express を使用した例です。
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import express from 'express'
import { createServer as createViteServer } from 'vite'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
async function createServer() {
const app = express()
// Create Vite server in middleware mode and configure the app type as
// 'custom', disabling Vite's own HTML serving logic so parent server
// can take control
const vite = await createViteServer({
server: { middlewareMode: true },
appType: 'custom'
})
// Use vite's connect instance as middleware. If you use your own
// express router (express.Router()), you should use router.use
// When the server restarts (for example after the user modifies
// vite.config.js), `vite.middlewares` is still going to be the same
// reference (with a new internal stack of Vite and plugin-injected
// middlewares). The following is valid even after restarts.
app.use(vite.middlewares)
app.use('*all', async (req, res) => {
// serve index.html - we will tackle this next
})
app.listen(5173)
}
createServer()ここで vite は ViteDevServer のインスタンスです。vite.middlewares は Connect のインスタンスであり、Connect 互換の Node.js フレームワークでミドルウェアとして使用できます。
次のステップは、サーバーでレンダリングされた HTML を提供するための * ハンドラーを実装することです。
app.use('*all', async (req, res, next) => {
const url = req.originalUrl
try {
// 1. Read index.html
let template = fs.readFileSync(
path.resolve(__dirname, 'index.html'),
'utf-8',
)
// 2. Apply Vite HTML transforms. This injects the Vite HMR client,
// and also applies HTML transforms from Vite plugins, e.g. global
// preambles from @vitejs/plugin-react
template = await vite.transformIndexHtml(url, template)
// 3. Load the server entry. ssrLoadModule automatically transforms
// ESM source code to be usable in Node.js! There is no bundling
// required, and provides efficient invalidation similar to HMR.
const { render } = await vite.ssrLoadModule('/src/entry-server.js')
// 4. render the app HTML. This assumes entry-server.js's exported
// `render` function calls appropriate framework SSR APIs,
// e.g. ReactDOMServer.renderToString()
const appHtml = await render(url)
// 5. Inject the app-rendered HTML into the template.
const html = template.replace(`<!--ssr-outlet-->`, () => appHtml)
// 6. Send the rendered HTML back.
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
// If an error is caught, let Vite fix the stack trace so it maps back
// to your actual source code.
vite.ssrFixStacktrace(e)
next(e)
}
})package.json の dev スクリプトも、サーバー スクリプトを使用するように変更する必要があります。
"scripts": {
- "dev": "vite"
+ "dev": "node server"
}本番環境用のビルド
本番環境用に SSR プロジェクトを出荷するには、以下を行う必要があります。
- 通常のクライアントビルドを生成する。
- SSR ビルドを生成する。これは
import()を介して直接ロードできるため、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.js で process.env.NODE_ENV を確認して、本番環境に特化したロジックを追加する必要があります。
ルートの
index.htmlを読み取る代わりに、クライアントビルドへの正しいアセットリンクが含まれているため、dist/client/index.htmlをテンプレートとして使用します。await vite.ssrLoadModule('/src/entry-server.js')の代わりにimport('./dist/server/entry-server.js')を使用します (このファイルは SSR ビルドの結果です)。vite開発サーバーの作成とすべての使用を開発環境専用の条件分岐の背後に移動し、dist/clientからファイルを供給するための静的ファイルサービスミドルウェアを追加します。
機能するセットアップについては、例プロジェクトを参照してください。
プリロードディレクティブの生成
vite build は --ssrManifest フラグをサポートしており、ビルド出力ディレクトリに .vite/ssr-manifest.json を生成します。
- "build:client": "vite build --outDir dist/client",
+ "build:client": "vite build --outDir dist/client --ssrManifest",上記のスクリプトは、クライアントビルド用に dist/client/.vite/ssr-manifest.json を生成します (はい、SSR マニフェストはモジュール ID をクライアントファイルにマッピングしたいため、クライアントビルドから生成されます)。マニフェストには、モジュール ID とそれに関連付けられたチャンクおよびアセットファイルのマップが含まれています。
マニフェストを活用するには、フレームワークはサーバーレンダリング呼び出し中に使用されたコンポーネントのモジュール ID を収集する方法を提供する必要があります。
@vitejs/plugin-vue はこれをすぐにサポートしており、使用されたコンポーネントのモジュール ID を関連する Vue SSR コンテキストに自動的に登録します。
const ctx = {}
const html = await vueServerRenderer.renderToString(app, ctx)
// ctx.modules is now a Set of module IDs that were used during the renderserver.js の本番ブランチでは、マニフェストを読み取り、src/entry-server.js によってエクスポートされた render 関数に渡す必要があります。これにより、非同期ルートで使用されるファイルのプリロードディレクティブをレンダリングするのに十分な情報が得られます!完全な例については、デモソースを参照してください。この情報は 103 Early Hints にも使用できます。
プリレンダリング / SSG
特定のルートに必要なルートとデータが事前にわかっている場合、本番 SSR と同じロジックを使用して、これらのルートを静的 HTML にプリレンダリングできます。これは、静的サイト生成 (SSG) の一種と見なすこともできます。動作する例については、デモプリレンダリングスクリプトを参照してください。
SSR 外部化
SSR 実行時、依存関係はデフォルトで Vite の SSR 変換モジュールシステムから「外部化」されます。これにより、開発とビルドの両方が高速化されます。
依存関係が Vite のパイプラインによって変換される必要がある場合、たとえば、Vite の機能がそれらで未変換のまま使用されているため、ssr.noExternal に追加できます。
リンクされた依存関係の場合、Vite の HMR を活用するために、デフォルトでは外部化されません。これを望まない場合、たとえば、リンクされていないかのように依存関係をテストするために、ssr.external に追加できます。
エイリアスの操作
あるパッケージを別のパッケージにリダイレクトするエイリアスを設定している場合、SSR 外部化された依存関係で機能するように、実際の node_modules パッケージをエイリアス化したい場合があります。Yarn と pnpm の両方が npm: プレフィックスを介したエイリアスをサポートしています。
SSR 固有のプラグインロジック
Vue や Svelte などの一部のフレームワークは、クライアントと SSR に基づいてコンポーネントを異なる形式にコンパイルします。条件付き変換をサポートするために、Vite は次のプラグインフックの options オブジェクトに追加の ssr プロパティを渡します。
resolveIdloadtransform
例
export function mySSRPlugin() {
return {
name: 'my-ssr',
transform(code, id, options) {
if (options?.ssr) {
// perform ssr-specific transform...
}
},
}
}load と transform のオプションオブジェクトはオプションであり、Rollup は現在このオブジェクトを使用していませんが、将来これらのフックを追加のメタデータで拡張する可能性があります。
注意
Vite 2.7 以前は、options オブジェクトを使用する代わりに、位置指定の ssr パラメータを使用してプラグインフックに通知されていました。主要なフレームワークとプラグインはすべて更新されていますが、以前の API を使用している古い記事が見つかる可能性があります。
SSR ターゲット
SSR ビルドのデフォルトターゲットは Node 環境ですが、Web Worker でサーバーを実行することもできます。パッケージのエントリ解決はプラットフォームごとに異なります。ssr.target を 'webworker' に設定することで、ターゲットを Web Worker に構成できます。
SSR バンドル
webworker ランタイムのような一部のケースでは、SSR ビルドを単一の JavaScript ファイルにバンドルしたい場合があります。ssr.noExternal を true に設定することで、この動作を有効にできます。これにより、次の 2 つのことが行われます。
- すべての依存関係を
noExternalとして扱う - Node.js の組み込みがインポートされた場合はエラーをスローする
SSR 解決条件
デフォルトでは、パッケージエントリ解決は、SSR ビルドの resolve.conditions で設定された条件を使用します。この動作をカスタマイズするには、ssr.resolve.conditions と ssr.resolve.externalConditions を使用できます。
Vite CLI
CLI コマンド $ vite dev と $ vite preview も SSR アプリに使用できます。configureServer を使用して開発サーバーに、configurePreviewServer を使用してプレビューサーバーに SSR ミドルウェアを追加できます。
注意
Vite のミドルウェアの_後に_ SSR ミドルウェアが実行されるように、ポストフックを使用してください。