ホームページ ウェブフロントエンド 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)

フロントエンドのサーマルペーパーレシートのために文字化けしたコード印刷に遭遇した場合はどうすればよいですか? フロントエンドのサーマルペーパーレシートのために文字化けしたコード印刷に遭遇した場合はどうすればよいですか? Apr 04, 2025 pm 02:42 PM

フロントエンドのサーマルペーパーチケット印刷のためのよくある質問とソリューションフロントエンド開発におけるチケット印刷は、一般的な要件です。しかし、多くの開発者が実装しています...

javascriptの分解:それが何をするのか、なぜそれが重要なのか javascriptの分解:それが何をするのか、なぜそれが重要なのか Apr 09, 2025 am 12:07 AM

JavaScriptは現代のWeb開発の基礎であり、その主な機能には、イベント駆動型のプログラミング、動的コンテンツ生成、非同期プログラミングが含まれます。 1)イベント駆動型プログラミングにより、Webページはユーザー操作に応じて動的に変更できます。 2)動的コンテンツ生成により、条件に応じてページコンテンツを調整できます。 3)非同期プログラミングにより、ユーザーインターフェイスがブロックされないようにします。 JavaScriptは、Webインタラクション、シングルページアプリケーション、サーバー側の開発で広く使用されており、ユーザーエクスペリエンスとクロスプラットフォーム開発の柔軟性を大幅に改善しています。

誰がより多くのPythonまたはJavaScriptを支払われますか? 誰がより多くのPythonまたはJavaScriptを支払われますか? Apr 04, 2025 am 12:09 AM

スキルや業界のニーズに応じて、PythonおよびJavaScript開発者には絶対的な給与はありません。 1. Pythonは、データサイエンスと機械学習でさらに支払われる場合があります。 2。JavaScriptは、フロントエンドとフルスタックの開発に大きな需要があり、その給与もかなりです。 3。影響要因には、経験、地理的位置、会社の規模、特定のスキルが含まれます。

Shiseidoの公式Webサイトのように、視差スクロールと要素のアニメーション効果を実現する方法は?
または:
Shiseidoの公式Webサイトのようにスクロールするページを伴うアニメーション効果をどのように実現できますか? Shiseidoの公式Webサイトのように、視差スクロールと要素のアニメーション効果を実現する方法は? または: Shiseidoの公式Webサイトのようにスクロールするページを伴うアニメーション効果をどのように実現できますか? Apr 04, 2025 pm 05:36 PM

この記事の視差スクロールと要素のアニメーション効果の実現に関する議論では、Shiseidoの公式ウェブサイト(https://www.shisido.co.co.jp/sb/wonderland/)と同様の達成方法について説明します。

JavaScriptの進化:現在の傾向と将来の見通し JavaScriptの進化:現在の傾向と将来の見通し Apr 10, 2025 am 09:33 AM

JavaScriptの最新トレンドには、TypeScriptの台頭、最新のフレームワークとライブラリの人気、WebAssemblyの適用が含まれます。将来の見通しは、より強力なタイプシステム、サーバー側のJavaScriptの開発、人工知能と機械学習の拡大、およびIoTおよびEDGEコンピューティングの可能性をカバーしています。

JavaScriptは学ぶのが難しいですか? JavaScriptは学ぶのが難しいですか? Apr 03, 2025 am 12:20 AM

JavaScriptを学ぶことは難しくありませんが、挑戦的です。 1)変数、データ型、関数などの基本概念を理解します。2)非同期プログラミングをマスターし、イベントループを通じて実装します。 3)DOM操作を使用し、非同期リクエストを処理することを約束します。 4)一般的な間違いを避け、デバッグテクニックを使用します。 5)パフォーマンスを最適化し、ベストプラクティスに従ってください。

JavaScriptを使用して、同じIDを持つArray要素を1つのオブジェクトにマージする方法は? JavaScriptを使用して、同じIDを持つArray要素を1つのオブジェクトにマージする方法は? Apr 04, 2025 pm 05:09 PM

同じIDを持つ配列要素をJavaScriptの1つのオブジェクトにマージする方法は?データを処理するとき、私たちはしばしば同じIDを持つ必要性に遭遇します...

フロントエンド開発でVSCodeと同様に、パネルドラッグアンドドロップ調整機能を実装する方法は? フロントエンド開発でVSCodeと同様に、パネルドラッグアンドドロップ調整機能を実装する方法は? Apr 04, 2025 pm 02:06 PM

フロントエンドのVSCodeと同様に、パネルドラッグアンドドロップ調整機能の実装を調べます。フロントエンド開発では、VSCODEと同様のVSCODEを実装する方法...

See all articles