ホームページ > ウェブフロントエンド > jsチュートリアル > 本番環境に対応した SSR React アプリケーションの構築

本番環境に対応した SSR React アプリケーションの構築

Mary-Kate Olsen
リリース: 2025-01-05 11:51:40
オリジナル
187 人が閲覧しました

Building Production-Ready SSR React Applications

ミリ秒単位が重要な世界では、サーバー側のレンダリングはフロントエンド アプリケーションにとって不可欠な機能となっています。

このガイドでは、React を使用して本番環境に対応した SSR を構築するための基本的なパターンを説明します。 SSR (Next.js など) が組み込まれた React ベースのフレームワークの背後にある原則を理解し、独自のカスタム ソリューションを作成する方法を学びます。

提供されたコードは本番環境に対応しており、Dockerfile を含むクライアント部分とサーバー部分の両方の完全なビルド プロセスを備えています。この実装では、Vite を使用してクライアントと SSR コードを構築しますが、任意の他のツールを使用することもできます。 Vite は、クライアントの開発モード中にホットリロードも行います。

Vite を含まないこのセットアップのバージョンに興味がある場合は、お気軽にお問い合わせください。

目次

  • SSRとは
  • アプリの作成
    • Vite を初期化しています
    • React コンポーネントを更新しています
    • サーバーの作成
    • ビルドの構成
  • ルーティング
  • ドッカー
  • 結論

SSRとは

サーバーサイド レンダリング (SSR) は、サーバーが Web ページの HTML コンテンツを生成してからブラウザーに送信する Web 開発の手法です。 JavaScript が空の HTML シェルをロードした後にユーザーのデバイス上でコンテンツを構築する従来のクライアント側レンダリング (CSR) とは異なり、SSR は完全にレンダリングされた HTML をサーバーから直接配信します。

SSR の主な利点:

  • SEO の改善: 検索エンジン クローラーは完全にレンダリングされたコンテンツを受信するため、SSR によりインデックス付けが向上し、 ランキング。
  • ファーストペイントの高速化: サーバーが面倒な作業を処理するため、ユーザーは意味のあるコンテンツをほぼ即座に見ることができます。 レンダリング。
  • パフォーマンスの向上: ブラウザーのレンダリング ワークロードを軽減することで、SSR はよりスムーズなエクスペリエンスを提供します。 古いデバイスや性能の低いデバイスを使用しているユーザー。
  • サーバーからクライアントへのシームレスなデータ転送: SSR を使用すると、動的サーバー側データをクライアントに渡すことができます。 クライアントバンドルを再構築しています。

アプリの作成

SSR を使用したアプリのフローは次の手順に従います:

  1. テンプレート HTML ファイルを読み取ります。
  2. React を初期化し、アプリのコンテンツの HTML 文字列を生成します。
  3. 生成された HTML 文字列をテンプレートに挿入します。
  4. 完全な HTML をブラウザに送信します。
  5. クライアントで HTML タグを照合し、アプリケーションをハイドレートしてインタラクティブにします。

Vite を初期化しています

私は pnpm と React-swc-ts Vite テンプレートを使用することを好みますが、他のセットアップを選択することもできます。

pnpm create vite react-ssr-app --template react-swc-ts
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

依存関係をインストールします:

pnpm create vite react-ssr-app --template react-swc-ts
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

React コンポーネントの更新

典型的な React アプリケーションには、index.html の main.tsx エントリ ポイントが 1 つあります。 SSR では、サーバー用とクライアント用の 2 つのエントリ ポイントが必要です。

サーバーエントリーポイント

Node.js サーバーはアプリを実行し、React コンポーネントを文字列 (renderToString) にレンダリングすることで HTML を生成します。

pnpm install
ログイン後にコピー
ログイン後にコピー

クライアントエントリポイント

ブラウザはサーバーで生成された HTML をハイドレートし、JavaScript と接続してページをインタラクティブにします。

ハイドレーション は、サーバーによってレンダリングされた静的 HTML にイベント リスナーやその他の動的動作をアタッチするプロセスです。

// ./src/entry-server.tsx
import { renderToString } from 'react-dom/server'
import App from './App'

export function render() {
  return renderToString(<App />)
}
ログイン後にコピー
ログイン後にコピー

Index.html を更新しています

プロジェクトのルートにあるindex.htmlファイルを更新します。 プレースホルダーは、サーバーが生成された HTML を挿入する場所です。

// ./src/entry-client.tsx
import { hydrateRoot } from 'react-dom/client'
import { StrictMode } from 'react'
import App from './App'

import './index.css'

hydrateRoot(
  document.getElementById('root')!,
  <StrictMode>
    <App />
  </StrictMode>,
)
ログイン後にコピー
ログイン後にコピー

サーバーに必要なすべての依存関係は、クライアント バンドルに含まれないように、開発依存関係 (devDependency) としてインストールする必要があります。

次に、プロジェクトのルートに ./server という名前のフォルダーを作成し、次のファイルを追加します。

メインサーバーファイルの再エクスポート

メインサーバーファイルを再エクスポートします。これにより、コマンドの実行がより便利になります。

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div>



<h3>
  
  
  Create Server
</h3>

<p>First, install the dependencies:<br>
</p>

<pre class="brush:php;toolbar:false">pnpm install -D express compression sirv tsup vite-node nodemon @types/express @types/compression
ログイン後にコピー
ログイン後にコピー

定数の定義

HTML_KEY 定数は、index.html のプレースホルダー コメントと一致する必要があります。他の定数は環境設定を管理します。

// ./server/index.ts
export * from './app'
ログイン後にコピー
ログイン後にコピー

Expressサーバーの作成

開発環境と運用環境に異なる構成で Express サーバーをセットアップします。

// ./server/constants.ts
export const NODE_ENV = process.env.NODE_ENV || 'development'
export const APP_PORT = process.env.APP_PORT || 3000

export const PROD = NODE_ENV === 'production'
export const HTML_KEY = `<!--app-html-->`
ログイン後にコピー
ログイン後にコピー

開発モードの構成

開発では、Vite のミドルウェアを使用してリクエストを処理し、ホットリロードでindex.html ファイルを動的に変換します。サーバーは React アプリケーションをロードし、リクエストごとに HTML にレンダリングします。

// ./server/app.ts
import express from 'express'
import { PROD, APP_PORT } from './constants'
import { setupProd } from './prod'
import { setupDev } from './dev'

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

  if (PROD) {
    await setupProd(app)
  } else {
    await setupDev(app)
  }

  app.listen(APP_PORT, () => {
    console.log(`http://localhost:${APP_PORT}`)
  })
}

createServer()
ログイン後にコピー
ログイン後にコピー

実稼働モードの構成

運用環境では、圧縮を使用してパフォーマンスを最適化し、sirv を使用して静的ファイルを提供し、事前構築されたサーバー バンドルを使用してアプリをレンダリングします。

// ./server/dev.ts
import { Application } from 'express'
import fs from 'fs'
import path from 'path'
import { HTML_KEY } from './constants'

const HTML_PATH = path.resolve(process.cwd(), 'index.html')
const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'src/entry-server.tsx')

export async function setupDev(app: Application) {
  // Create a Vite development server in middleware mode
  const vite = await (
    await import('vite')
  ).createServer({
    root: process.cwd(),
    server: { middlewareMode: true },
    appType: 'custom',
  })

  // Use Vite middleware for serving files
  app.use(vite.middlewares)

  app.get('*', async (req, res, next) => {
    try {
      // Read and transform the HTML file
      let html = fs.readFileSync(HTML_PATH, 'utf-8')
      html = await vite.transformIndexHtml(req.originalUrl, html)

      // Load the entry-server.tsx module and render the app
      const { render } = await vite.ssrLoadModule(ENTRY_SERVER_PATH)
      const appHtml = await render()

      // Replace the placeholder with the rendered HTML
      html = html.replace(HTML_KEY, appHtml)
      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      // Fix stack traces for Vite and handle errors
      vite.ssrFixStacktrace(e as Error)
      console.error((e as Error).stack)
      next(e)
    }
  })
}
ログイン後にコピー
ログイン後にコピー

ビルドの構成

アプリケーションを構築するためのベスト プラクティスに従うには、不要なパッケージをすべて除外し、アプリケーションが実際に使用するもののみを含める必要があります。

Vite 構成の更新

ビルド プロセスを最適化し、SSR の依存関係を処理するには、Vite 構成を更新します。

// ./server/prod.ts
import { Application } from 'express'
import fs from 'fs'
import path from 'path'
import compression from 'compression'
import sirv from 'sirv'
import { HTML_KEY } from './constants'

const CLIENT_PATH = path.resolve(process.cwd(), 'dist/client')
const HTML_PATH = path.resolve(process.cwd(), 'dist/client/index.html')
const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'dist/ssr/entry-server.js')

export async function setupProd(app: Application) {
  // Use compression for responses
  app.use(compression())
  // Serve static files from the client build folder
  app.use(sirv(CLIENT_PATH, { extensions: [] }))

  app.get('*', async (_, res, next) => {
    try {
      // Read the pre-built HTML file
      let html = fs.readFileSync(HTML_PATH, 'utf-8')

      // Import the server-side render function and generate HTML
      const { render } = await import(ENTRY_SERVER_PATH)
      const appHtml = await render()

      // Replace the placeholder with the rendered HTML
      html = html.replace(HTML_KEY, appHtml)
      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      // Log errors and pass them to the error handler
      console.error((e as Error).stack)
      next(e)
    }
  })
}
ログイン後にコピー
ログイン後にコピー

tsconfig.json の更新

tsconfig.json を更新してサーバー ファイルを含め、TypeScript を適切に構成します。

pnpm create vite react-ssr-app --template react-swc-ts
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

tsup 構成の作成

TypeScript バンドラーである tsup を使用してサーバー コードを構築します。 noExternal オプションは、サーバーにバンドルするパッケージを指定します。 サーバーが使用する追加パッケージを必ず含めてください。

pnpm install
ログイン後にコピー
ログイン後にコピー

ビルドスクリプトの追加

// ./src/entry-server.tsx
import { renderToString } from 'react-dom/server'
import App from './App'

export function render() {
  return renderToString(<App />)
}
ログイン後にコピー
ログイン後にコピー

アプリケーションの実行

開発: 次のコマンドを使用して、ホットリロードでアプリケーションを起動します:

// ./src/entry-client.tsx
import { hydrateRoot } from 'react-dom/client'
import { StrictMode } from 'react'
import App from './App'

import './index.css'

hydrateRoot(
  document.getElementById('root')!,
  <StrictMode>
    <App />
  </StrictMode>,
)
ログイン後にコピー
ログイン後にコピー

本番: アプリケーションをビルドし、本番サーバーを起動します:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div>



<h3>
  
  
  Create Server
</h3>

<p>First, install the dependencies:<br>
</p>

<pre class="brush:php;toolbar:false">pnpm install -D express compression sirv tsup vite-node nodemon @types/express @types/compression
ログイン後にコピー
ログイン後にコピー

SSR が動作していることを確認するには、サーバーへの最初のネットワーク リクエストを確認します。応答には、アプリケーションの完全にレンダリングされた HTML が含まれている必要があります。

ルーティング

アプリにさまざまなページを追加するには、ルーティングを適切に構成し、クライアントとサーバーの両方のエントリ ポイントで処理する必要があります。

// ./server/index.ts
export * from './app'
ログイン後にコピー
ログイン後にコピー

クライアント側ルーティングの追加

クライアント側のルーティングを有効にするために、クライアント エントリ ポイントで BrowserRouter を使用してアプリケーションをラップします。

// ./server/constants.ts
export const NODE_ENV = process.env.NODE_ENV || 'development'
export const APP_PORT = process.env.APP_PORT || 3000

export const PROD = NODE_ENV === 'production'
export const HTML_KEY = `<!--app-html-->`
ログイン後にコピー
ログイン後にコピー

サーバー側ルーティングの追加

サーバー側のルーティングを処理するには、サーバーのエントリ ポイントで StaticRouter を使用します。 URL をプロップとして渡し、リクエストに基づいて正しいルートをレンダリングします。

// ./server/app.ts
import express from 'express'
import { PROD, APP_PORT } from './constants'
import { setupProd } from './prod'
import { setupDev } from './dev'

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

  if (PROD) {
    await setupProd(app)
  } else {
    await setupDev(app)
  }

  app.listen(APP_PORT, () => {
    console.log(`http://localhost:${APP_PORT}`)
  })
}

createServer()
ログイン後にコピー
ログイン後にコピー

サーバー構成の更新

開発サーバーと運用サーバーの両方のセットアップを更新して、リクエスト URL をレンダリング関数に渡します。

// ./server/dev.ts
import { Application } from 'express'
import fs from 'fs'
import path from 'path'
import { HTML_KEY } from './constants'

const HTML_PATH = path.resolve(process.cwd(), 'index.html')
const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'src/entry-server.tsx')

export async function setupDev(app: Application) {
  // Create a Vite development server in middleware mode
  const vite = await (
    await import('vite')
  ).createServer({
    root: process.cwd(),
    server: { middlewareMode: true },
    appType: 'custom',
  })

  // Use Vite middleware for serving files
  app.use(vite.middlewares)

  app.get('*', async (req, res, next) => {
    try {
      // Read and transform the HTML file
      let html = fs.readFileSync(HTML_PATH, 'utf-8')
      html = await vite.transformIndexHtml(req.originalUrl, html)

      // Load the entry-server.tsx module and render the app
      const { render } = await vite.ssrLoadModule(ENTRY_SERVER_PATH)
      const appHtml = await render()

      // Replace the placeholder with the rendered HTML
      html = html.replace(HTML_KEY, appHtml)
      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      // Fix stack traces for Vite and handle errors
      vite.ssrFixStacktrace(e as Error)
      console.error((e as Error).stack)
      next(e)
    }
  })
}
ログイン後にコピー
ログイン後にコピー

これらの変更により、React アプリで SSR と完全に互換性のあるルートを作成できるようになりました。ただし、この基本的なアプローチでは、遅延ロードされたコンポーネント (React.lazy) は処理されません。遅延ロードされたモジュールの管理については、下部にリンクされている私の他の記事 ストリーミングと動的データを使用した高度な React SSR テクニック を参照してください。

ドッカー

アプリケーションをコンテナ化するための Dockerfile は次のとおりです:

// ./server/prod.ts
import { Application } from 'express'
import fs from 'fs'
import path from 'path'
import compression from 'compression'
import sirv from 'sirv'
import { HTML_KEY } from './constants'

const CLIENT_PATH = path.resolve(process.cwd(), 'dist/client')
const HTML_PATH = path.resolve(process.cwd(), 'dist/client/index.html')
const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'dist/ssr/entry-server.js')

export async function setupProd(app: Application) {
  // Use compression for responses
  app.use(compression())
  // Serve static files from the client build folder
  app.use(sirv(CLIENT_PATH, { extensions: [] }))

  app.get('*', async (_, res, next) => {
    try {
      // Read the pre-built HTML file
      let html = fs.readFileSync(HTML_PATH, 'utf-8')

      // Import the server-side render function and generate HTML
      const { render } = await import(ENTRY_SERVER_PATH)
      const appHtml = await render()

      // Replace the placeholder with the rendered HTML
      html = html.replace(HTML_KEY, appHtml)
      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      // Log errors and pass them to the error handler
      console.error((e as Error).stack)
      next(e)
    }
  })
}
ログイン後にコピー
ログイン後にコピー

Docker イメージの構築と実行

// ./vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { dependencies } from './package.json'

export default defineConfig(({ mode }) => ({
  plugins: [react()],
  ssr: {
    noExternal: mode === 'production' ? Object.keys(dependencies) : undefined,
  },
}))
ログイン後にコピー
{
  "include": [
    "src",
    "server",
    "vite.config.ts"
  ]
}
ログイン後にコピー

結論

このガイドでは、React を使用して本番環境に対応した SSR アプリケーションを作成するための強力な基盤を確立しました。プロジェクトのセットアップ、ルーティングの構成、Dockerfile の作成方法を学習しました。この設定は、ランディング ページや小さなアプリを効率的に作成するのに最適です。

コードを探索する

  • : 反応-ssr-基本-例
  • テンプレート: 反応-ssr-テンプレート
  • Vite Extra テンプレート: template-ssr-react-ts

関連記事

これは、React を使用した SSR に関するシリーズの一部です。他の記事もお楽しみに!

  • 本番環境に対応した SSR React アプリケーションの構築 (ここにいます)
  • ストリーミングおよび動的データを使用した高度な React SSR テクニック (近日公開予定)
  • SSR React アプリケーションでのテーマのセットアップ (近日公開予定)

つながりを保つ

フィードバック、コラボレーション、技術的なアイデアについての議論はいつでも受け付けています。お気軽にご連絡ください。

  • ポートフォリオ: maxh1t.xyz
  • メール: m4xh17@gmail.com

以上が本番環境に対応した SSR React アプリケーションの構築の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート