React Routerで作ったページが、本番環境で500エラーになったら試してみること

React Routerで作ったページが、本番環境で500エラーになる。開発環境ではちゃんと表示されるのに。

たとえば、こんなケース。下記のコードで、http://example.com/(本番環境)は表示されるけど、http://example.com/single にアクセスしても500エラーとなってしまう。

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './Home';
import Single from './Single';
import './App.css';

function App() {
  return (
    <div className="app">
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/single" element={<Single />} />
        </Routes>
      </Router>
    </div>
  );
}

export default App;

500エラーの原因は多岐に渡りますが、このケースでは、/single というURLに直接アクセスしたときに、/single/index.htmlにリダイレクトされないことで問題が生じている可能性があります。開発環境ではこのへん自動的に解決してくれるんですが、本番環境では設定してあげる必要があります。

この設定はサーバーの種類によって異なります。

Express.jsの場合

このコードは、最初に静的ファイル(ビルドされたReactアプリ)を提供し、それ以外のすべてのリクエストに対してはindex.htmlを提供します。これにより、クライアントサイドのルーティングが期待通りに動作します。

const express = require('express');
const path = require('path');
const app = express();

app.use(express.static(path.join(__dirname, 'build')));

app.get('*', function(req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(9000);

Nginxの場合

nginx.confにこれを追加します。

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

全体的にはこんな感じになるかと思われます。

server {
    listen 80;
    server_name your-domain.com;
    root /path/to/your/react/app;

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

your-domain.com を実際のドメイン名に、/path/to/your/react/app をReactアプリのビルド出力パスに置き換えてください。

設定を変更したら、設定のシンタックスが正しいか確認します。

sudo nginx -t

エラーがなければ、Nginxを再起動して新しい設定を適用します。

sudo service nginx restart

Apacheの場合

.htaccessにこれを追加します。

RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

もうちょい詳しい解説

Reactアプリケーションのルーティングに関する問題は、サーバー側とクライアント側のルーティングの間で不整合が生じている場合に起こりやすいです。これは、サーバーがリクエストされたルートを理解できず、適切にリダイレクトできないために起こります。

Reactでは一般的にクライアントサイドルーティングが使われます。これは、特定のURL(/singleなど)を直接ブラウザに入力した場合やリンクからアクセスした場合でも、全てのリクエストが最初はルートURL( / )へと向けられ、そこからReact Routerなどのクライアントサイドのルーティングライブラリが適切なコンポーネントをレンダリングします。

しかし、Reactアプリケーションをサーバーにデプロイする際、サーバーが全てのリクエストをindex.html(Reactアプリのエントリーポイント)へリダイレクトしなければならないという点で問題が生じることがあります。この設定がないと、ユーザーが直接 /single などのURLにアクセスした場合、サーバーはそのルートに対応する物理的なファイルを探そうとします。そして、そのファイルが存在しないためサーバーエラーが発生します。

この問題を解決するためには、サーバーが全てのリクエストをindex.htmlへリダイレクトするように設定する必要がある。そんなあんばいです。