コンテンツにスキップ

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

注意

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.htmlentry-client.jsを参照し、サーバーレンダリングされたマークアップが挿入されるプレースホルダーを含める必要があります

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

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

条件付きロジック

SSRとクライアントに基づいて条件付きロジックを実行する必要がある場合は、以下を使用できます

js
if (import.meta.
env
.
SSR
) {
// ... server only logic }

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

開発サーバーの設定

SSRアプリを構築する場合、メインサーバーを完全に制御し、Viteを本番環境から分離したい場合があります。そのため、ミドルウェアモードでViteを使用することをお勧めします。 express (v4)を使用した例を次に示します

server.js
js
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
()

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

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

server.js
js
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.jsondevスクリプトも、代わりにサーバースクリプトを使用するように変更する必要があります

package.json
diff
  "scripts": {
-   "dev": "vite"
+   "dev": "node server"
  }

本番ビルド

SSRプロジェクトを本番環境にデプロイするには、次の手順を実行する必要があります

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

package.jsonのスクリプトは次のようになります

package.json
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フラグをサポートしています

diff
- "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コンテキストに自動的に登録します

src/entry-server.js
js
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パッケージのエイリアスを作成することをお勧めします。Yarnpnpmはどちらも、npm:プレフィックスを介したエイリアスをサポートしています。

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

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

  • resolveId
  • load
  • transform

js
export function 
mySSRPlugin
() {
return {
name
: 'my-ssr',
transform
(
code
,
id
,
options
) {
if (
options
?.
ssr
) {
// perform ssr-specific transform... } }, } }

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

注意

Vite 2.7より前では、これはoptionsオブジェクトを使用する代わりに、位置パラメータssrを使用してプラグインフックに通知されていました。すべての主要なフレームワークとプラグインは更新されていますが、以前のAPIを使用している古い投稿が見つかる場合があります。

SSRターゲット

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

SSRバンドル

webworkerランタイムなどの場合によっては、SSRビルドを単一のJavaScriptファイルにバンドルすることをお勧めします。この動作は、ssr.noExternaltrueに設定することで有効にできます。これにより、次の2つのことが行われます

  • すべての依存関係をnoExternalとして扱います
  • Node.jsの組み込み関数がインポートされた場合、エラーをスローします

SSR解決条件

デフォルトでは、パッケージエントリの解決はSSRビルドのために resolve.conditions に設定された条件を使用します。 ssr.resolve.conditionsssr.resolve.externalConditions を使用して、この動作をカスタマイズできます。

Vite CLI

CLIコマンド $ vite dev$ vite preview はSSRアプリにも使用できます。 configureServer を使用して開発サーバーに、configurePreviewServer を使用してプレビューサーバーに、SSRミドルウェアを追加できます。

注意

Viteのミドルウェアの*後*にSSRミドルウェアが実行されるように、ポストフックを使用してください。

MITライセンスで公開されています。(ccee3d7c)