状況
react-routerにてURL Paramsを使って動的にページ制御しようとしたところ、該当のURIをサーバーにリクエストした結果をロードしても、reactで描画すべきコンテンツが空になってしまった。
問題点と原因
初期表示用のHTMLファイル(index.html)に記述した静的ファイル(css, js)の参照URLが存在しない場所を指していて、SPAとしての機能が働かなかった。これが直接的な問題。
その原因は、次のようにreact-routerのルーティング設定をし、かつ静的ファイルを相対パスで指定したことで、返されたHTMLファイル内の静的ファイル参照先をブラウザが解決する際に矛盾を発生させていたからだった。
react-routerは悪くない。アプリケーションの設定方法に誤りがあった。
<Route path="/" component={AppLayout} >
<IndexRoute component={Top} />
<Route path="blog" component={Blog}>
<Route path=":id" component={BlogPost} /> <!-- www.domain.jp/blog/123 となることを想定 -->
</Route>
<Route path="aboutus" component={AboutUs} />
...
</Route>
<!-- いずれも参照先は相対パス指定 -->
<link href="css/animate.css" rel="stylesheet">
<script src="build/bundle.js"></script>
例えばwww.domain.jp/blog/123
に対してリクエストを送ると、ブラウザは上記の静的ファイルを取ろうとして、www.domain.jp/blog/css/animate.css
を参照してしまう。bundle.js
も然り。それらのファイルは存在しないのでロードできず、react
は動かない。
詳細な問題分析
react-router
はHistory API
を使っている、という点に着目すると、実装時に注意しなくてはならない点が見えてきた。
そもそも、History API
のpushState
関数により、より自然な形でSPAのロケーションを操作できるようになった経緯がある。しかし、同時にサーバのサポートが必要になった。もしそのURIがアドレスバーをクリックするなどしてサーバに送信されたら、適切な制御を行ってレスポンスを返す必要があるからだ(react-router公式にも書いてある1)。そのサポートとは次のような制御を指す。
- 個々のページのリクエストに対して、サーバはその時の状態を解釈して適切なコンテンツを描画し、応答する(いわゆるサーバサイドレンダリング)。
- すべてのリクエストに対して、同一の初期表示用ページを戻し、その後の実際の描画処理はクライアントに任せる
- 上記をミックスする
もし、Nodejs + express を使ってアプリケーション開発していて、次のような記述をしているなら、そのアプリのサーバのサポートは上記の2に当たる。
// serve our static stuff like index.css
app.use(express.static(path.join(__dirname, 'public')));
// send all requests to index.html so browserHistory in React Router works.
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
このアプローチだと(かつ、相対パスで静的ファイルを指定していると)、ベースパス直下が第一階層だけのURI構成を検討している間は問題がないが、それより深くしようとすると、深刻な問題が発生する。開発者は常にベースパスからの相対パスとして静的ファイルの場所を指定しているのに、ブラウザはロケーションに指定されたパスからの相対パスに対してファイルをリクエストするからだ。
(前項再掲)例えば
www.domain.jp/blog/123
に対してリクエストを送ると、ブラウザは上記の静的ファイルを取ろうとして、www.domain.jp/blog/css/animate.css
を参照してしまう。bundle.js
も然り。それらのファイルは存在しないのでロードできず、react
は動かない。
解決方針
問題点をまとめると、
- 現在のサーバ実装は2の形を取っている。この時、初期表示用のHTMLファイル内に外部ファイルが相対パスで指定されていると問題が起こる。
- 遷移先のURIが
www.domain/path
の場合、cssやjsの参照先もwww.dmain.jp/path/css/...
といった形で誤った場所を示してしまう。本当は、
www.dmain.jp/css/...
としたい。
ということになる。
そこで解決策としては
パスの頭に/
を追加して、絶対パス化する。
これにより、URIの深さによらずに静的ファイルを確実に参照できるようになる。
<link href="/css/animate.css" rel="stylesheet">
<script src="/build/bundle.js"></script>
ハマリ時間
4時間。。
参照したページ