Reactでページ更新すると404|SPAの仕組みを理解する
ReactのSPAでページ更新時に404エラーが発生する原因と解決方法を詳しく解説。サーバー設定とルーティングの仕組みを初心者向けに説明します。
みなさん、ReactでSPA(Single Page Application)を作成した際、ページを更新すると404エラーが発生して困ったことはありませんか?
「React Routerでページ遷移は正常に動作するのに、F5で更新すると404になる」 「デプロイ後に直接URLにアクセスすると404が表示される」 「開発環境では問題ないのに、本番環境だけエラーになる」
このような問題を経験したことはありませんか?
こうした問題は、SPAの仕組みを理解すれば解決できます。 この記事では、SPAで発生する404エラーの原因と、各種サーバーでの対処法を詳しく解説します。
SPAで404エラーが発生する理由
SPAの基本的な仕組み
SPAは単一のHTMLファイルを基にして、JavaScriptで動的にページの内容を変更します。
簡単に言うと、実際にはページが1つしかないのに、まるで複数のページがあるように見せる技術です。
// React Routerの例
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Router>
);
}
このコードでは、/about
や/contact
のURLが表示されますが、実際にはこれらのページファイルは存在しません。
すべてJavaScriptで動的に作られているんです。
404エラーが発生する流れ
どうして404エラーが発生するのか、流れを見てみましょう。
- 通常のページ遷移:React Router内でのナビゲーション
- ページ更新:ブラウザがサーバーに
/about
を直接リクエスト - サーバーの応答:
/about
というファイルが存在しないため404を返す
つまり、サーバーは React Router の存在を知らないため、直接URLにアクセスされると404エラーが発生するんです。
通常のWebサイトとSPAの違い
この違いを理解することが重要です。
通常のWebサイト
/about
にアクセス →about.html
ファイルを返す- ファイルが存在するので正常に表示される
SPA(React)
/about
にアクセス →about.html
ファイルは存在しない- サーバーは404エラーを返す
- 本来は
index.html
を返してReact Routerに処理を任せるべき
この仕組みを理解すると、解決策も見えてきますよね。
各種サーバーでの対処法
Apache (.htaccess)
Apacheサーバーを使用している場合の設定方法です。
# .htaccess
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]
この設定を詳しく見てみましょう。
各行の意味
Options -MultiViews
この行で、Apacheの自動コンテンツネゴシエーションを無効にします。
RewriteEngine On
URLリライト機能を有効にします。
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]
「ファイルが存在しない場合は、すべてのリクエストを index.html
にリダイレクト」という設定です。
これにより、存在しないファイルへのリクエストを index.html
にリダイレクトします。
Nginx
Nginxサーバーでの設定例です。
# nginx.conf
server {
listen 80;
server_name your-domain.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
Nginxの設定も詳しく見てみましょう。
try_filesディレクティブの説明
try_files $uri $uri/ /index.html;
この設定は以下の順序でファイルを探します:
$uri
- リクエストされたファイルをそのまま探す$uri/
- ディレクトリとして探す/index.html
- 上記で見つからない場合はindex.html
を返す
つまり、静的ファイルは正常に返し、存在しないルートは index.html
に任せるということです。
Node.js (Express)
Node.jsのExpressフレームワークでの設定例です。
// server.js
const express = require('express');
const path = require('path');
const app = express();
// 静的ファイルの配信
app.use(express.static(path.join(__dirname, 'build')));
// すべてのルートでindex.htmlを返す
app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
このExpressサーバーの仕組みを説明しましょう。
静的ファイルの配信
app.use(express.static(path.join(__dirname, 'build')));
build
フォルダ内の静的ファイル(CSS、JS、画像など)を配信します。
フォールバックルート
app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
すべてのGETリクエストに対して、最終的に index.html
を返します。
この設定により、React Routerがルーティングを処理できるようになります。
主要ホスティングサービスでの設定
Netlify
Netlifyでの設定方法をご紹介します。
方法1: netlify.toml ファイル
# netlify.toml
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
方法2: _redirects ファイル
プロジェクトの public
フォルダに _redirects
ファイルを作成します:
/* /index.html 200
この設定により、すべてのリクエストが index.html
にリダイレクトされます。
ステータスコード200で返すことで、SEOにも配慮した設定になります。
Vercel
Vercelでの設定は vercel.json
ファイルで行います。
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
この設定により、すべてのルートが index.html
にリライトされます。
Vercelでは rewrites
を使うことで、URLを変更せずに内部的にファイルを変更できます。
Firebase Hosting
Firebase Hostingでの設定は firebase.json
ファイルで行います。
{
"hosting": {
"public": "build",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
設定の詳細
"public": "build"
デプロイするディレクトリを指定します。
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
すべてのパス(**
)を index.html
にリライトします。
GitHub Pages
GitHub Pagesは少し特殊な設定が必要です。
package.json の設定
// package.json
{
"homepage": "https://yourusername.github.io/repository-name",
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
}
}
404.html ファイルの作成
GitHub Pagesでは、public
フォルダに 404.html
ファイルを作成してSPAに対応します:
<!-- public/404.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Redirecting...</title>
<script type="text/javascript">
var pathSegmentsToKeep = 1;
var l = window.location;
l.replace(
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') +
'/?/' + l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +
(l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +
l.hash
);
</script>
</head>
<body>
</body>
</html>
このスクリプトは、404エラーが発生した場合に適切なURLにリダイレクトする仕組みです。 少し複雑ですが、GitHub Pagesの制約に対応するための方法です。
開発環境での設定
Create React App
Create React Appを使用している場合の設定です。
// src/index.js
import { BrowserRouter } from 'react-router-dom';
// 開発サーバーは自動的にSPAに対応
// historyAPIFallback が有効になっている
Create React Appでは、開発サーバーが自動的にSPAに対応してくれます。 そのため、特別な設定は必要ありません。
Vite
Viteを使用している場合の設定方法です。
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
historyApiFallback: true
}
});
historyApiFallbackの役割
server: {
historyApiFallback: true
}
この設定により、存在しないルートへのリクエストを index.html
にフォールバックします。
これで開発環境でも本番環境と同じような動作になります。
Webpack
Webpackを直接使用している場合の設定です。
// webpack.config.js
module.exports = {
// ...
devServer: {
historyApiFallback: true
}
};
Webpackでも historyApiFallback
を有効にすることで、SPAに対応できます。
React Router の設定
BrowserRouter vs HashRouter
React Routerには2つの主要なルーターがあります。
BrowserRouter(推奨)
// BrowserRouter(推奨)
import { BrowserRouter as Router } from 'react-router-dom';
function App() {
return (
<Router>
{/* ルート設定 */}
</Router>
);
}
HashRouter(サーバー設定不要)
// HashRouter(サーバー設定不要)
import { HashRouter as Router } from 'react-router-dom';
function App() {
return (
<Router>
{/* ルート設定 */}
</Router>
);
}
それぞれの特徴
BrowserRouter:
- URL:
https://example.com/about
- サーバー設定が必要
- SEOに適している
- より自然なURL
HashRouter:
- URL:
https://example.com/#/about
- サーバー設定不要
- SEOに不利
#
が含まれるURL
基本的には BrowserRouter
の使用をおすすめします。
ただし、サーバー設定ができない場合は HashRouter
を使うこともできます。
basename の設定
サブディレクトリにデプロイする場合の設定方法です。
// サブディレクトリにデプロイする場合
<BrowserRouter basename="/my-app">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
basenameの役割
<BrowserRouter basename="/my-app">
この設定により、https://example.com/my-app/about
のようなURLに対応できます。
サブディレクトリでホスティングする場合に必要な設定です。
実際の実装例
基本的なルーティング設定
実際のReactアプリでの実装例をご紹介します。
// App.js
import React from 'react';
import {
BrowserRouter as Router,
Routes,
Route,
Link,
Navigate
} from 'react-router-dom';
const Home = () => <h1>ホーム</h1>;
const About = () => <h1>会社概要</h1>;
const Contact = () => <h1>お問い合わせ</h1>;
const NotFound = () => <h1>404 - ページが見つかりません</h1>;
function App() {
return (
<Router>
<nav>
<Link to="/">ホーム</Link>
<Link to="/about">会社概要</Link>
<Link to="/contact">お問い合わせ</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/404" element={<NotFound />} />
<Route path="*" element={<Navigate to="/404" replace />} />
</Routes>
</Router>
);
}
export default App;
このコードの重要なポイントを見てみましょう。
ナビゲーション部分
<nav>
<Link to="/">ホーム</Link>
<Link to="/about">会社概要</Link>
<Link to="/contact">お問い合わせ</Link>
</nav>
Link
コンポーネントを使用することで、ページリロードなしでルーティングができます。
キャッチオールルート
<Route path="*" element={<Navigate to="/404" replace />} />
定義されていないすべてのルートを404ページにリダイレクトします。
動的ルーティング
URLパラメータを使用した動的ルーティングの例です。
// 動的ルーティングの例
import { useParams } from 'react-router-dom';
const UserProfile = () => {
const { userId } = useParams();
return (
<div>
<h1>ユーザープロフィール</h1>
<p>ユーザーID: {userId}</p>
</div>
);
};
// App.js内でのルート設定
<Route path="/user/:userId" element={<UserProfile />} />
useParamsフックの使用
const { userId } = useParams();
URLパラメータを取得して、動的にコンテンツを表示できます。
/user/123
にアクセスすると、userId
に "123"
が入ります。
デバッグ方法
1. ブラウザでの確認
問題の原因を特定するための確認方法です。
// 開発者ツールでの確認
// 1. Network タブでリクエストを確認
// 2. Console でエラーメッセージを確認
// 3. History APIの動作を確認
console.log('現在のURL:', window.location.pathname);
console.log('History state:', window.history.state);
確認すべきポイント
- ページ更新時にどのようなリクエストが送信されるか
- サーバーがどのようなレスポンスを返すか
- JavaScriptエラーが発生していないか
2. サーバーログの確認
サーバー側でのログ確認方法です。
# Apacheのログ確認
tail -f /var/log/apache2/access.log
# Nginxのログ確認
tail -f /var/log/nginx/access.log
ログを確認することで、どのようなリクエストが来ているかがわかります。
3. 設定ファイルの確認
React Router の設定を確認するためのデバッグコンポーネントです。
// React Router の設定確認
const DebugRouter = () => {
const location = useLocation();
useEffect(() => {
console.log('Current location:', location.pathname);
}, [location]);
return null;
};
// App.js内で使用
<Router>
<DebugRouter />
<Routes>
{/* ルート設定 */}
</Routes>
</Router>
この方法で、ルーティングの動作を詳しく追跡できます。
よくある問題と解決法
問題1: 一部のページで404が発生
よくある間違いとその解決方法です。
// 問題:相対パスでのリンク
<Link to="about">会社概要</Link>
// 解決:絶対パスでのリンク
<Link to="/about">会社概要</Link>
なぜ問題が起こるのか
相対パス "about"
を使用すると、現在のURLに追加される形になります。
例えば、/contact
ページにいる場合、/contact/about
のようなURLになってしまいます。
正しい書き方
絶対パス "/about"
を使用することで、常に正しいURLになります。
問題2: アセットファイルが読み込めない
CSS や画像ファイルが読み込めない場合の対処法です。
// package.json での設定
{
"homepage": "https://yourdomain.com",
"scripts": {
"build": "react-scripts build"
}
}
// 相対パスの設定
{
"homepage": "."
}
homepageフィールドの役割
"homepage": "https://yourdomain.com"
この設定により、ビルド時にアセットファイルの正しいパスが生成されます。
問題3: サブディレクトリでのデプロイ
サブディレクトリにデプロイする場合の正しい設定方法です。
// 正しい設定
<BrowserRouter basename="/subdirectory">
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
// package.json
{
"homepage": "https://yourdomain.com/subdirectory"
}
basenameとhomepageの組み合わせ
両方を正しく設定することで、サブディレクトリでも問題なく動作します。
問題4: 環境によって動作が異なる
開発環境と本番環境で動作が異なる場合の対処法です。
// 環境に応じた設定
const basename = process.env.NODE_ENV === 'production'
? '/my-app'
: '';
<BrowserRouter basename={basename}>
<Routes>
{/* ルート設定 */}
</Routes>
</BrowserRouter>
環境変数を使用することで、環境に応じて動的に設定を変更できます。
まとめ
ReactのSPAで404エラーが発生する問題は、以下の対処により解決できます:
基本的な解決方法
- サーバー設定:すべてのルートを
index.html
にリダイレクト - ホスティングサービス:各サービスに応じた設定ファイルを作成
- React Router:適切なルーティング設定
- ビルド設定:
homepage
やbasename
の正確な設定
重要なポイント
- SPAの仕組みを理解することで、404エラーの原因が明確になります
- 開発環境と本番環境で同様の設定が必要です
- 各ホスティングサービスには独自の設定方法があります
トラブルシューティングのコツ
- ブラウザの開発者ツールでネットワークタブを確認
- サーバーログでリクエストの流れを追跡
- 段階的に設定を確認して問題を特定
404エラーは初心者が必ずと言っていいほど遭遇する問題です。 でも大丈夫です!仕組みを理解すれば、必ず解決できます。
ぜひ今回の対処法を参考に、スムーズに動作するSPAを構築してみてください。