Reactで作ったものが動かない|本番環境での注意点

React本番環境でのよくある問題と解決方法を詳しく解説。ビルドエラー、環境変数、ルーティング、APIエラーなど実例付きで対処法を説明します。

Learning Next 運営
28 分で読めます

みなさん、Reactアプリを本番環境にデプロイした時に動かなくて困ったことはありませんか?

「開発環境では動いていたのに本番で動かない」「ビルドは成功するが画面が真っ白」「APIが呼び出せない」

そんな悩みを抱えていませんか?

実は、これらの問題にはよくあるパターンがあります。 原因さえ分かれば、解決方法も見つけられるんです。

この記事では、React アプリを本番環境にデプロイする際によくある問題と解決方法を詳しく解説します。 開発環境と本番環境の違いから、具体的なトラブルシューティングまで、実際の例とともに学んでいきましょう。

開発環境と本番環境の違いを知ろう

まず、開発環境と本番環境の基本的な違いを理解しましょう。

「同じReactアプリなのに、なんで違うの?」と思うかもしれません。 実は、大きな違いがあるんです。

開発環境は便利機能がいっぱい

開発環境では、React Development Server が多くの便利機能を提供します。

// 開発環境での特徴
const developmentFeatures = {
    hotReload: true,           // ファイル変更時の自動リロード
    sourceMap: true,           // エラー時の詳細な情報
    devtools: true,            // React Developer Tools
    cors: false,               // CORS制限の緩和
    errorOverlay: true,        // エラーの画面表示
    bundleAnalyzer: false      // 軽量なバンドル
};

// 開発サーバーの起動
// npm start → http://localhost:3000

これらの機能により、「開発がしやすい環境」が提供されています。 エラーが出ても分かりやすく表示されるし、CORSの制限も緩いんです。

本番環境は最適化重視

本番環境では、最適化されたビルドファイルが使用されます。

// 本番環境での特徴
const productionFeatures = {
    hotReload: false,          // リロード機能なし
    sourceMap: false,          // ソースマップなし(通常)
    devtools: false,           // 開発ツール無効
    cors: true,                // CORS制限あり
    errorOverlay: false,       // エラー画面なし
    bundleOptimized: true      // 最適化されたバンドル
};

// ビルドコマンド
// npm run build → build/ ディレクトリに静的ファイル生成

本番環境では、「速度とセキュリティを重視」した設定になります。 便利機能は削除されて、パフォーマンスが優先されるんです。

ビルドプロセスの仕組み

# 開発環境での実行
npm start
# → Webpack Dev Server が起動
# → メモリ上でバンドル
# → ホットリロード有効

# 本番環境用のビルド
npm run build
# → 静的ファイルを生成
# → JSファイルの最適化・圧縮
# → CSSファイルの最適化
# → アセットファイルのハッシュ化

この違いを理解することで、「なぜ本番で動かないのか」が見えてきます。

よくある問題と解決方法を覚えよう

具体的な問題とその解決方法を見ていきましょう。

「どの問題も一度は遭遇する」ものばかりです。

画面が真っ白になる問題を解決しよう

最もよくある問題の一つです。

どんな症状?

<!-- ブラウザで表示される内容 -->
<!DOCTYPE html>
<html>
<head>
    <title>React App</title>
</head>
<body>
    <div id="root"></div>
    <!-- スクリプトファイルが読み込まれない -->
</body>
</html>

「HTMLは表示されるけど、Reactアプリが動かない」状態ですね。

解決方法1: 相対パスの問題を修正

// package.json での設定
{
  "name": "my-react-app",
  "homepage": ".",  // 相対パスを指定
  "scripts": {
    "build": "react-scripts build"
  }
}
// または絶対パスを指定
{
  "homepage": "https://yourdomain.com/app"
}

homepageの設定により、「ファイルの読み込みパスが正しく設定」されます。

解決方法2: サブディレクトリでの動作を設定

// サブディレクトリにデプロイする場合
{
  "homepage": "/subdirectory"
}
// React Router使用時
import { BrowserRouter } from 'react-router-dom';

function App() {
    return (
        <BrowserRouter basename="/subdirectory">
            {/* ルート定義 */}
        </BrowserRouter>
    );
}

サブディレクトリにデプロイする場合は、「React Routerにもパスを教える」必要があります。

環境変数の問題を解決しよう

開発環境で設定した環境変数が本番で動作しない問題です。

問題のあるコード

// ❌ 本番環境で undefined になる
const API_URL = process.env.API_URL;

fetch(`${API_URL}/api/users`)
    .then(response => response.json())
    .then(data => console.log(data));

「あれ、環境変数が読み込まれない」という状況ですね。

解決方法

// ✅ REACT_APP_ プレフィックスを使用
const API_URL = process.env.REACT_APP_API_URL;

// ✅ デフォルト値を設定
const API_URL = process.env.REACT_APP_API_URL || 'https://api.example.com';

// ✅ 環境別の設定
const getApiUrl = () => {
    if (process.env.NODE_ENV === 'production') {
        return process.env.REACT_APP_PROD_API_URL;
    }
    return process.env.REACT_APP_DEV_API_URL || 'http://localhost:3001';
};

Reactでは、「REACT_APP_」で始まる環境変数のみが使用可能です。

環境変数ファイルの設定

# .env.production
REACT_APP_API_URL=https://api.production.com
REACT_APP_ANALYTICS_ID=prod-12345

# .env.development
REACT_APP_API_URL=http://localhost:3001
REACT_APP_ANALYTICS_ID=dev-67890

環境別にファイルを分けることで、「設定の管理が楽」になります。

ルーティングの問題を解決しよう

React Router を使用した SPA でよくある問題です。

問題の症状

# 直接URLにアクセスすると404エラー
https://yourdomain.com/about  # 404 Not Found
https://yourdomain.com/users/123  # 404 Not Found

「トップページは表示されるけど、他のページは404エラー」という状況です。

解決方法: サーバー設定を変更

Apache (.htaccess)

Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]

Nginx

location / {
    try_files $uri $uri/ /index.html;
}

Netlify (_redirects)

/*    /index.html   200

Vercel (vercel.json)

{
  "rewrites": [
    { "source": "/(.*)", "destination": "/" }
  ]
}

これらの設定により、「全てのアクセスをindex.htmlに転送」することで、React Routerが正しく動作します。

CORS(クロスオリジン)の問題を解決しよう

開発環境では動作するAPIが本番で動作しない問題です。

問題の症状

// 開発環境では動作
fetch('http://localhost:3001/api/users')  // OK

// 本番環境ではCORSエラー
fetch('https://api.production.com/users')  // CORS Error

「APIにアクセスできません」というエラーが出る状況ですね。

解決方法1: プロキシ設定

// package.json
{
  "proxy": "http://localhost:3001"
}

解決方法2: 本番用API設定

// APIサーバー側の設定(Express.js例)
const cors = require('cors');

app.use(cors({
    origin: ['https://yourdomain.com', 'https://www.yourdomain.com'],
    credentials: true
}));

解決方法3: API統合

// 同一オリジンのAPIエンドポイントを使用
const API_BASE = window.location.origin;

fetch(`${API_BASE}/api/users`)
    .then(response => response.json())
    .then(data => console.log(data));

CORS問題は「サーバー側の設定で解決」することが多いです。

ビルドエラーの対処法をマスターしよう

ビルド時に発生するエラーとその対処法を紹介します。

「ビルドが通らない」と本番デプロイできませんからね。

TypeScript エラーを修正しよう

// ❌ ビルド時にエラーになる
interface User {
    id: number;
    name: string;
}

function UserComponent({ user }: { user: any }) {  // any型は警告
    return <div>{user.name}</div>;
}

「型定義が不十分」だとビルドエラーになります。

// ✅ 正しい型定義
interface User {
    id: number;
    name: string;
    email?: string;  // オプショナル
}

interface UserComponentProps {
    user: User;
}

function UserComponent({ user }: UserComponentProps) {
    return <div>{user.name}</div>;
}

適切な型定義により、「ビルド時エラーを防ぐ」ことができます。

ESLint エラーを修正しよう

// ❌ ESLint エラーが発生
import React, { useState, useEffect } from 'react';

function Component() {
    const [data, setData] = useState(null);
    
    useEffect(() => {
        fetchData();  // 依存配列に fetchData がない
    }, []);
    
    const fetchData = async () => {
        // データ取得処理
    };
    
    return <div>{data}</div>;
}

「依存配列の設定が不適切」だとESLintエラーになります。

// ✅ ESLint エラーを修正
import React, { useState, useEffect, useCallback } from 'react';

function Component() {
    const [data, setData] = useState(null);
    
    const fetchData = useCallback(async () => {
        // データ取得処理
    }, []);
    
    useEffect(() => {
        fetchData();
    }, [fetchData]);  // 依存配列に追加
    
    return <div>{data}</div>;
}

useCallbackと適切な依存配列により、「ESLintエラーを解決」できます。

バンドルサイズの問題を解決しよう

// ❌ 不要なライブラリ全体をインポート
import * as lodash from 'lodash';
import { format } from 'date-fns';

// ✅ 必要な関数のみインポート
import { debounce } from 'lodash/debounce';
import format from 'date-fns/format';

// ✅ 動的インポートを使用
const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
    return (
        <Suspense fallback={<div>Loading...</div>}>
            <LazyComponent />
        </Suspense>
    );
}

適切なインポート方法により、「バンドルサイズを最小限に抑える」ことができます。

パフォーマンスを最適化しよう

本番環境でのパフォーマンス最適化について説明します。

「速度が遅い」と言われないために重要なポイントです。

チャンク分割を設定しよう

// webpack.config.js(ejected プロジェクトの場合)
module.exports = {
    optimization: {
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendors',
                    chunks: 'all',
                },
                common: {
                    name: 'common',
                    minChunks: 2,
                    chunks: 'all',
                    enforce: true
                }
            }
        }
    }
};

チャンク分割により、「必要な部分のみ読み込み」が可能になります。

画像を最適化しよう

// 画像の遅延読み込み
function OptimizedImage({ src, alt, ...props }) {
    const [isLoaded, setIsLoaded] = useState(false);
    const imgRef = useRef();
    
    useEffect(() => {
        const observer = new IntersectionObserver(
            ([entry]) => {
                if (entry.isIntersecting) {
                    setIsLoaded(true);
                    observer.disconnect();
                }
            },
            { threshold: 0.1 }
        );
        
        if (imgRef.current) {
            observer.observe(imgRef.current);
        }
        
        return () => observer.disconnect();
    }, []);
    
    return (
        <div ref={imgRef}>
            {isLoaded ? (
                <img src={src} alt={alt} {...props} />
            ) : (
                <div className="image-placeholder">Loading...</div>
            )}
        </div>
    );
}

遅延読み込みにより、「初期表示を高速化」できます。

Service Worker を設定しよう

// public/sw.js
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
    '/',
    '/static/js/bundle.js',
    '/static/css/main.css'
];

self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then((cache) => cache.addAll(urlsToCache))
    );
});

self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request)
            .then((response) => {
                return response || fetch(event.request);
            }
        )
    );
});
// src/index.js でのService Worker登録
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/sw.js')
            .then((registration) => {
                console.log('SW registered: ', registration);
            })
            .catch((registrationError) => {
                console.log('SW registration failed: ', registrationError);
            });
    });
}

Service Workerにより、「オフライン対応とキャッシュ機能」を実現できます。

デプロイメント戦略を考えよう

安全で効率的なデプロイメント戦略を紹介します。

「安全にリリースする」ためのポイントです。

段階的デプロイメントを実装しよう

# GitHub Actions例
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install dependencies
        run: npm ci
      - name: Run tests
        run: npm test
      - name: Build
        run: npm run build

  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to staging
        run: echo "Deploy to staging"

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to production
        run: echo "Deploy to production"

段階的デプロイメントにより、「リスクを最小限に抑える」ことができます。

ロールバック戦略を準備しよう

# ビルドファイルのバージョン管理
build/
├── v1.0.0/
│   ├── index.html
│   └── static/
├── v1.0.1/
│   ├── index.html
│   └── static/
└── current -> v1.0.1/  # シンボリックリンク

# ロールバック用スクリプト
#!/bin/bash
PREVIOUS_VERSION=$1
ln -sfn /var/www/build/$PREVIOUS_VERSION /var/www/build/current

ロールバック戦略により、「問題が発生した時の迅速な復旧」が可能になります。

環境設定を管理しよう

// config/environments.js
const environments = {
    development: {
        apiUrl: 'http://localhost:3001',
        debug: true,
        analytics: false
    },
    staging: {
        apiUrl: 'https://api-staging.example.com',
        debug: true,
        analytics: false
    },
    production: {
        apiUrl: 'https://api.example.com',
        debug: false,
        analytics: true
    }
};

export default environments[process.env.NODE_ENV] || environments.development;

環境別設定により、「各環境に応じた適切な動作」を実現できます。

モニタリングとエラー追跡を設定しよう

本番環境でのモニタリング設定について説明します。

「問題を早期発見する」ために重要です。

エラートラッキングを設定しよう

// Sentry の設定例
import * as Sentry from '@sentry/react';

Sentry.init({
    dsn: process.env.REACT_APP_SENTRY_DSN,
    environment: process.env.NODE_ENV,
    beforeSend(event) {
        // 開発環境ではエラーを送信しない
        if (process.env.NODE_ENV === 'development') {
            return null;
        }
        return event;
    }
});

// エラーバウンダリ
function ErrorBoundary({ children }) {
    return (
        <Sentry.ErrorBoundary fallback={ErrorFallback}>
            {children}
        </Sentry.ErrorBoundary>
    );
}

function ErrorFallback({ error, resetError }) {
    return (
        <div role="alert">
            <h2>申し訳ございません。エラーが発生しました。</h2>
            <button onClick={resetError}>再試行</button>
        </div>
    );
}

エラートラッキングにより、「ユーザーが遭遇したエラーを把握」できます。

パフォーマンス監視を設定しよう

// Web Vitals の測定
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics({ name, value, id }) {
    // Google Analytics や独自の分析ツールに送信
    gtag('event', name, {
        event_category: 'Web Vitals',
        value: Math.round(name === 'CLS' ? value * 1000 : value),
        event_label: id,
        non_interaction: true,
    });
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);

パフォーマンス監視により、「ユーザー体験の質を数値で把握」できます。

まとめ:本番環境で安定動作するReactアプリを作ろう!

お疲れ様でした! React アプリの本番環境での問題と解決方法について詳しく解説しました。

よくある問題と解決方法

覚えておきたい重要なポイントです。

  1. 画面が真っ白 - homepage設定、相対パスの修正
  2. 環境変数が動かない - REACT_APP_プレフィックスの使用
  3. ルーティングエラー - サーバー側の設定(リライト)
  4. CORSエラー - APIサーバーの設定、同一オリジン化

ビルドエラー対策

エラーを防ぐためのポイントです。

  • TypeScript の適切な型定義
  • ESLint エラーの修正
  • バンドルサイズの最適化

パフォーマンス最適化

速度向上のためのテクニックです。

  • チャンク分割
  • 画像の遅延読み込み
  • Service Worker の活用

デプロイメント戦略

安全なリリースのための方法です。

  • 段階的デプロイメント
  • ロールバック戦略
  • 環境設定の管理

モニタリング

問題の早期発見のための仕組みです。

  • エラートラッキング(Sentry等)
  • パフォーマンス監視(Web Vitals)

重要なチェックリスト

デプロイ前に確認しましょう。

# デプロイ前の確認項目
□ npm run build が成功する
□ 環境変数が正しく設定されている
□ ルーティング設定が適切
□ API URLが本番環境用に設定されている
□ CORS設定が適切
□ エラートラッキングが設定されている
□ パフォーマンス監視が設定されている

トラブルシューティングの順序

問題が発生した時の対処手順です。

  1. ブラウザの開発者ツールでエラーを確認
  2. ネットワークタブでAPIリクエストを確認
  3. コンソールでJavaScriptエラーを確認
  4. ビルドログでビルドエラーを確認
  5. サーバーログで500エラーを確認

最後に

本番環境での問題は、事前の準備と適切な設定で多くを防ぐことができます。 「開発環境で動作するから大丈夫」と思わずに、本番環境特有の問題を理解して対策を講じましょう。

継続的な監視と改善により、ユーザーに愛される安定したReactアプリケーションを維持できますよ。 ぜひ、今回学んだ知識を実際のプロジェクトで活用してみてくださいね!

関連記事