サーバーサイドレンダリング
注意
SSRは、特にフロントエンドフレームワーク(React、Preact、Vue、Svelteなど)がNode.jsで同じアプリケーションを実行し、HTMLにプリレンダリングし、最終的にクライアントでハイドレーションすることをサポートすることを指します。従来のサーバーサイドフレームワークとの統合をお探しの場合は、代わりにバックエンド統合ガイドをご覧ください。
以下のガイドでは、選択したフレームワークでSSRを扱う経験があることを前提としており、Vite固有の統合の詳細にのみ焦点を当てます。
低レベルAPI
これは、ライブラリやフレームワークの作者向けの低レベルAPIです。アプリケーションを作成するのが目的の場合は、まずAwesome Vite SSRセクションにある高レベルのSSRプラグインとツールを確認してください。とはいえ、多くのアプリケーションはViteのネイティブな低レベルAPIの上に直接構築されています.
現在、Viteは環境APIを使用して、改良されたSSR APIに取り組んでいます。詳細については、リンクをご覧ください。
ヘルプ
ご質問がある場合は、Vite Discordの#ssrチャンネルでコミュニティがお役に立てます。
サンプルプロジェクト
Viteは、サーバーサイドレンダリング(SSR)の組み込みサポートを提供します。create-vite-extra
には、このガイドの参考として使用できるSSR設定の例が含まれています
create-vite
を実行し、フレームワークオプションで`その他 > 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 API
index.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 (v4)を使用した例を次に示します
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('*', 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('*', 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
は、ビルド出力ディレクトリに.vite/ssr-manifest.json
を生成する--ssrManifest
フラグをサポートしています
- "build:client": "vite build --outDir dist/client",
+ "build:client": "vite build --outDir dist/client --ssrManifest",
上記のスクリプトは、クライアントビルド用にdist/client/.vite/ssr-manifest.json
を生成するようになりました(はい、モジュールIDをクライアントファイルにマッピングしたいので、SSRマニフェストはクライアントビルドから生成されます)。マニフェストには、モジュール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 render
server.js
のproductionブランチでは、マニフェストを読み取り、src/entry-server.js
によってエクスポートされたrender
関数に渡す必要があります。これにより、非同期ルートで使用されるファイルのプリロードディレクティブをレンダリングするのに十分な情報が得られます!完全な例については、デモソースを参照してください。この情報は、103 Early Hintsにも使用できます。
プリレンダリング / SSG
特定のルートのルートと必要なデータが事前にわかっている場合は、本番SSRと同じロジックを使用して、これらのルートを静的HTMLにプリレンダリングできます。これは、静的サイト生成(SSG)の一形態とも考えることができます。動作する例については、デモプリレンダースクリプトを参照してください。
SSR Externals
SSRを実行すると、依存関係はデフォルトでViteのSSR変換モジュールシステムから「外部化」されます。これにより、開発とビルドの両方が高速化されます。
依存関係がViteのパイプラインによって変換される必要がある場合、たとえば、Viteの機能がトランスパイルされずに使用されているため、ssr.noExternal
に追加できます。
リンクされた依存関係の場合、ViteのHMRを活用するために、デフォルトでは外部化されません。これが望ましくない場合、たとえば、依存関係がリンクされていないかのようにテストするには、ssr.external
に追加できます。
エイリアスの使用
あるパッケージを別のパッケージにリダイレクトするエイリアスを設定した場合、SSRの外部化された依存関係で機能させるために、実際のnode_modules
パッケージのエイリアスを作成することをお勧めします。Yarnとpnpmはどちらも、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) {
// perform ssr-specific transform...
}
},
}
}
load
とtransform
のoptionsオブジェクトはオプションです。rollupは現在このオブジェクトを使用していませんが、将来的には追加のメタデータでこれらのフックを拡張する可能性があります。
注意
Vite 2.7より前では、これはoptions
オブジェクトを使用する代わりに、位置パラメータssr
を使用してプラグインフックに通知されていました。すべての主要なフレームワークとプラグインは更新されていますが、以前のAPIを使用している古い投稿が見つかる場合があります。
SSRターゲット
SSRビルドのデフォルトのターゲットはノード環境ですが、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ミドルウェアが実行されるように、ポストフックを使用してください。