このブログでは、React の最新機能を活用して、最小限のツールとフレームワークで軽量で柔軟な React アプリケーションを作成する方法を探ります。 Next.js や Remix などのフレームワークは多くのユースケースに優れていますが、このガイドは、同様の機能を保持しながら完全な制御を行うことに興味がある人向けです。
React 19 がリリースされ、SSR をサポートする新機能が追加されたため、最小限のツールで SSR をサポートする React アプリケーションを作成して実験することにしました。その前に、Next.js が提供する考慮すべき重要な機能を調べてみましょう。
Feature | Next.js |
---|---|
SSR (Server-Side Rendering) | Built-in with minimal setup. |
SSG (Static Site Generation) | Built-in with getStaticProps. |
Routing | File-based routing. |
Code Splitting | Automatic. |
Image Optimization | Built-in with next/image. |
Performance Optimizations | Automatic (e.g., prefetching, critical CSS). |
SEO | Built-in tools like next/head. |
これらの機能に基づいて、最小限のセットアップを作成します。ステップバイステップのガイドは次のとおりです:
注: このチュートリアルの内容は、私のリポジトリ https://github.com/willyelm/react-app で見つけることができます
セットアップの前提条件として、Node.js がインストールされており、次のパッケージが必要です:
私たちのセットアップは、静的ファイル、パブリック ファイル、および REST API を提供するために Express を使用してルーティングを処理します。次に、react-router-dom を使用してすべてのリクエストを処理します。ブラウザにロードされると、クライアント バンドルは事前レンダリングされたコンテンツをハイドレートし、コンポーネントをインタラクティブにします。このアイデアを図で表したものが次のとおりです。
この図は、Express アプリがサーバー上で React コンポーネントを事前レンダリングし、HTML を返すことによってリクエストをどのように処理するかを示しています。クライアント側では、React がこれらの事前レンダリングされたコンポーネントをハイドレートして対話性を可能にします。
この図を念頭に置いて、フォルダー構造を作成してみましょう。
react-app/ # This will be our workspace directory. - public/ - scripts/ - build.js # Bundle our server and client scripts. - config.js # esbuild config to bundle. - dev.js # Bundle on watch mode and run server. - src/ - App/ # Our components will be here. - App.tsx # The main application with browser routing. - Home.tsx. # Our default page component. - NotFound.tsx # Fallback page for unmatched routes. - index.tsx # Hydrate our pre-rendered client app. - main.tsx # Server app with SSR components. - style.css # Initial stylesheet. package.json tsconfig.json
依存関係を追加して、package.json をセットアップしましょう:
{ "name": "react-app", "type": "module", "devDependencies": { "@types/express": "^5.0.0", "@types/node": "^22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "esbuild": "^0.24.2", "typescript": "^5.7.2" }, "dependencies": { "express": "^4.21.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.1.0" }, "scripts": { "build": "node scripts/build", "dev": "node scripts/dev", "start": "node dist/main.js" } }
注: Node.js が ESM スクリプトを実行できるようにするには、「type」:「module」プロパティが必要です。
Typescript を使用するので、tsconfig.json ファイルを構成します。
{ "compilerOptions": { "esModuleInterop": true, "verbatimModuleSyntax": true, "noEmit": true, "resolveJsonModule": true, "skipLibCheck": true, "strict": true, "lib": ["DOM", "DOM.Iterable", "ES2022"], "target": "ES2022", "module": "ES2022", "moduleResolution": "bundler", "jsx": "react-jsx", "baseUrl": ".", "paths": { "src": [ "./src/" ] } }, "include": [ "src" ], "exclude": [ "node_modules" ] }
なぜエスビルドなのか?他のツールと比較して、esbuild は最小限の機能を維持し、非常に高速で (現時点で最速のバンドラー)、デフォルトで typescript と esm をサポートします。
この設定では、esbuild を使用して開発スクリプトとビルド スクリプトを作成し、クライアント バンドルとサーバー バンドルの両方をトランスパイルします。このセクションでは、ワークスペースの script フォルダーで作業します。
scripts/config.js: このファイルには、スクリプトで共有されるクライアントとサーバーのバンドルの基本構成が含まれます。
import path from 'node:path'; // Working dir const workspace = process.cwd(); // Server bundle configuration export const serverConfig = { bundle: true, platform: 'node', format: 'esm', // Support esm packages packages: 'external', // Omit node packages from our node bundle logLevel: 'error', sourcemap: 'external', entryPoints: { main: path.join(workspace, 'src', 'main.tsx') // Express app }, tsconfig: path.join(workspace, 'tsconfig.json'), outdir: path.join(workspace, 'dist') }; // Client bundle configuration export const clientConfig = { bundle: true, platform: 'browser', format: 'esm', sourcemap: 'external', logLevel: 'error', tsconfig: path.join(workspace, 'tsconfig.json'), entryPoints: { index: path.join(workspace, 'src', 'index.tsx'), // Client react app style: path.join(workspace, 'src', 'style.css') // Stylesheet }, outdir: path.join(workspace, 'dist', 'static'), // Served as /static by express };
scripts/dev.js: このスクリプトはクライアント アプリとサーバー アプリの両方をバンドルし、メイン サーバー スクリプトを監視モードで実行します。
react-app/ # This will be our workspace directory. - public/ - scripts/ - build.js # Bundle our server and client scripts. - config.js # esbuild config to bundle. - dev.js # Bundle on watch mode and run server. - src/ - App/ # Our components will be here. - App.tsx # The main application with browser routing. - Home.tsx. # Our default page component. - NotFound.tsx # Fallback page for unmatched routes. - index.tsx # Hydrate our pre-rendered client app. - main.tsx # Server app with SSR components. - style.css # Initial stylesheet. package.json tsconfig.json
このスクリプトを使用すると、ワークスペースの package.json で設定されたとおりに npm run dev を実行できるはずです。
scripts/build.js: dev と似ていますが、必要なのは minify を有効にすることだけです。
{ "name": "react-app", "type": "module", "devDependencies": { "@types/express": "^5.0.0", "@types/node": "^22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "esbuild": "^0.24.2", "typescript": "^5.7.2" }, "dependencies": { "express": "^4.21.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.1.0" }, "scripts": { "build": "node scripts/build", "dev": "node scripts/dev", "start": "node dist/main.js" } }
このスクリプトは、npm run build を実行し、npm start を使用してアプリを実行することにより、本番環境で使用できる dist バンドルを生成します。
ノードとクライアント アプリの両方をバンドルするように esbuild を設定したので、Express サーバーの作成を開始して React SSR を実装しましょう。
これは、静的ファイルを提供し、サーバー ルートを処理し、react-router-dom を使用してルートするための Express 静的およびミドルウェア アプローチを使用したシンプルなアプリケーションです。
src/main.tsx: これは、サーバーを初期化し、Express でルートを処理し、React SSR を実装するメインの Node.js アプリケーションです。
{ "compilerOptions": { "esModuleInterop": true, "verbatimModuleSyntax": true, "noEmit": true, "resolveJsonModule": true, "skipLibCheck": true, "strict": true, "lib": ["DOM", "DOM.Iterable", "ES2022"], "target": "ES2022", "module": "ES2022", "moduleResolution": "bundler", "jsx": "react-jsx", "baseUrl": ".", "paths": { "src": [ "./src/" ] } }, "include": [ "src" ], "exclude": [ "node_modules" ] }
反応アプリは、react-router-dom を使用してルートを処理します。アプリはホームページとハイドレーションをテストするための NotFound ページで構成されます。ホームページにカウンター ボタンを追加し、React 19 を利用します。メタタグのタイトルと説明を更新します。
src/App/Home.tsx: 非常に最小限の FunctionComponent。
import path from 'node:path'; // Working dir const workspace = process.cwd(); // Server bundle configuration export const serverConfig = { bundle: true, platform: 'node', format: 'esm', // Support esm packages packages: 'external', // Omit node packages from our node bundle logLevel: 'error', sourcemap: 'external', entryPoints: { main: path.join(workspace, 'src', 'main.tsx') // Express app }, tsconfig: path.join(workspace, 'tsconfig.json'), outdir: path.join(workspace, 'dist') }; // Client bundle configuration export const clientConfig = { bundle: true, platform: 'browser', format: 'esm', sourcemap: 'external', logLevel: 'error', tsconfig: path.join(workspace, 'tsconfig.json'), entryPoints: { index: path.join(workspace, 'src', 'index.tsx'), // Client react app style: path.join(workspace, 'src', 'style.css') // Stylesheet }, outdir: path.join(workspace, 'dist', 'static'), // Served as /static by express };
src/App/NotFound.tsx: ページが見つからない場合のデフォルトの FunctionComponent。
import { spawn } from 'node:child_process'; import path from 'node:path'; import { context } from 'esbuild'; import { serverConfig, clientConfig } from './config.js'; // Working dir const workspace = process.cwd(); // Dev process async function dev() { // Build server in watch mode const serverContext = await context(serverConfig); serverContext.watch(); // Build client in watch mode const clientContext = await context(clientConfig); clientContext.watch(); // Run server const childProcess = spawn('node', [ '--watch', path.join(workspace, 'dist', 'main.js') ], { stdio: 'inherit' }); // Kill child process on program interruption process.on('SIGINT', () => { if (childProcess) { childProcess.kill(); } process.exit(0); }); } // Start the dev process dev();
src/App/App.tsx:react-router-dom を使用してアプリをセットアップします。
import { build } from 'esbuild'; import { clientConfig, serverConfig } from './config.js'; // build process async function bundle() { // Build server await build({ ...serverConfig, minify: true }); // Build client await build({ ...clientConfig, minify: true }); } // Start the build process bundle();
最後に、esbuild スクリプトを設定し、Express サーバーをセットアップし、React SSR を実装しました。サーバーを実行できます:
import path from 'node:path'; import express, { type Request, type Response } from 'express'; import { renderToPipeableStream } from 'react-dom/server'; import { StaticRouter } from 'react-router'; import { App } from './App/App'; const app = express(); // Create Express App const port = 3000; // Port to listen const workspace = process.cwd(); // workspace // Serve static files like js bundles and css files app.use('/static', express.static(path.join(workspace, 'dist', 'static'))); // Server files from the /public folder app.use(express.static(path.join(workspace, 'public'))); // Fallback to render the SSR react app app.use((request: Request, response: Response) => { // React SSR rendering as a stream const { pipe } = renderToPipeableStream( <html lang="en"> <head> <meta charSet="UTF-8" /> <link rel='stylesheet' href={`/static/style.css`} /> </head> <body> <base href="/" /> <div> <p>src/index.tsx: On the client side to activate our components and make them interactive we need to "hydrate".<br> </p> <pre class="brush:php;toolbar:false">import { hydrateRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router'; import { App } from './App/App'; // Hydrate pre-renderer #app element hydrateRoot( document.getElementById('app') as HTMLElement, <BrowserRouter> <App /> </BrowserRouter> );
これにより、[アプリ] がポート 3000 でリッスンしているというメッセージが表示されます。
に移動します
http://localhost:3000 で確認してください。
SSR および SEO メタ タグのテスト:
このチュートリアルのソース コードは、私のリポジトリ willyelm/react-app にもあります
このプロジェクトは、react@19、react-router-dom@7 などの最新機能を利用して SSR を構成します。
react-app/ # This will be our workspace directory. - public/ - scripts/ - build.js # Bundle our server and client scripts. - config.js # esbuild config to bundle. - dev.js # Bundle on watch mode and run server. - src/ - App/ # Our components will be here. - App.tsx # The main application with browser routing. - Home.tsx. # Our default page component. - NotFound.tsx # Fallback page for unmatched routes. - index.tsx # Hydrate our pre-rendered client app. - main.tsx # Server app with SSR components. - style.css # Initial stylesheet. package.json tsconfig.json
このガイドでは、最小限で柔軟なセットアップに重点を置き、esbuild と Express を使用して SSR を備えた React アプリを構築しました。 React 19、React Router DOM 7、esbuild などの最新ツールを使用して、大規模なフレームワークのオーバーヘッドを回避しながら、高速で効率的なワークフローを実現しました。
この実装を拡張して以下を含めることができます:
読んでいただきありがとうございます。コーディングを楽しんでください。
以上がReact と esbuild を使用した SSR へのシンプルなアプローチの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。