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

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

Jan 05, 2025 am 11:51 AM

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 サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Python vs. JavaScript:学習曲線と使いやすさ Python vs. JavaScript:学習曲線と使いやすさ Apr 16, 2025 am 12:12 AM

Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。

C/CからJavaScriptへ:すべてがどのように機能するか C/CからJavaScriptへ:すべてがどのように機能するか Apr 14, 2025 am 12:05 AM

C/CからJavaScriptへのシフトには、動的なタイピング、ゴミ収集、非同期プログラミングへの適応が必要です。 1)C/Cは、手動メモリ管理を必要とする静的に型付けられた言語であり、JavaScriptは動的に型付けされ、ごみ収集が自動的に処理されます。 2)C/Cはマシンコードにコンパイルする必要がありますが、JavaScriptは解釈言語です。 3)JavaScriptは、閉鎖、プロトタイプチェーン、約束などの概念を導入します。これにより、柔軟性と非同期プログラミング機能が向上します。

JavaScriptとWeb:コア機能とユースケース JavaScriptとWeb:コア機能とユースケース Apr 18, 2025 am 12:19 AM

Web開発におけるJavaScriptの主な用途には、クライアントの相互作用、フォーム検証、非同期通信が含まれます。 1)DOM操作による動的なコンテンツの更新とユーザーインタラクション。 2)ユーザーエクスペリエンスを改善するためにデータを提出する前に、クライアントの検証が実行されます。 3)サーバーとのリフレッシュレス通信は、AJAXテクノロジーを通じて達成されます。

JavaScript in Action:実際の例とプロジェクト JavaScript in Action:実際の例とプロジェクト Apr 19, 2025 am 12:13 AM

現実世界でのJavaScriptのアプリケーションには、フロントエンドとバックエンドの開発が含まれます。 1)DOM操作とイベント処理を含むTODOリストアプリケーションを構築して、フロントエンドアプリケーションを表示します。 2)node.jsを介してRestfulapiを構築し、バックエンドアプリケーションをデモンストレーションします。

JavaScriptエンジンの理解:実装の詳細 JavaScriptエンジンの理解:実装の詳細 Apr 17, 2025 am 12:05 AM

JavaScriptエンジンが内部的にどのように機能するかを理解することは、開発者にとってより効率的なコードの作成とパフォーマンスのボトルネックと最適化戦略の理解に役立つためです。 1)エンジンのワークフローには、3つの段階が含まれます。解析、コンパイル、実行。 2)実行プロセス中、エンジンはインラインキャッシュや非表示クラスなどの動的最適化を実行します。 3)ベストプラクティスには、グローバル変数の避け、ループの最適化、constとletsの使用、閉鎖の過度の使用の回避が含まれます。

Python vs. JavaScript:コミュニティ、ライブラリ、リソース Python vs. JavaScript:コミュニティ、ライブラリ、リソース Apr 15, 2025 am 12:16 AM

PythonとJavaScriptには、コミュニティ、ライブラリ、リソースの観点から、独自の利点と短所があります。 1)Pythonコミュニティはフレンドリーで初心者に適していますが、フロントエンドの開発リソースはJavaScriptほど豊富ではありません。 2)Pythonはデータサイエンスおよび機械学習ライブラリで強力ですが、JavaScriptはフロントエンド開発ライブラリとフレームワークで優れています。 3)どちらも豊富な学習リソースを持っていますが、Pythonは公式文書から始めるのに適していますが、JavaScriptはMDNWebDocsにより優れています。選択は、プロジェクトのニーズと個人的な関心に基づいている必要があります。

Python vs. JavaScript:開発環境とツール Python vs. JavaScript:開発環境とツール Apr 26, 2025 am 12:09 AM

開発環境におけるPythonとJavaScriptの両方の選択が重要です。 1)Pythonの開発環境には、Pycharm、Jupyternotebook、Anacondaが含まれます。これらは、データサイエンスと迅速なプロトタイピングに適しています。 2)JavaScriptの開発環境には、フロントエンドおよびバックエンド開発に適したnode.js、vscode、およびwebpackが含まれます。プロジェクトのニーズに応じて適切なツールを選択すると、開発効率とプロジェクトの成功率が向上する可能性があります。

JavaScript通訳者とコンパイラにおけるC/Cの役割 JavaScript通訳者とコンパイラにおけるC/Cの役割 Apr 20, 2025 am 12:01 AM

CとCは、主に通訳者とJITコンパイラを実装するために使用されるJavaScriptエンジンで重要な役割を果たします。 1)cは、JavaScriptソースコードを解析し、抽象的な構文ツリーを生成するために使用されます。 2)Cは、Bytecodeの生成と実行を担当します。 3)Cは、JITコンパイラを実装し、実行時にホットスポットコードを最適化およびコンパイルし、JavaScriptの実行効率を大幅に改善します。

See all articles