26
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

react-router + 静的ファイル(css, js) の組み合わせの罠

Last updated at Posted at 2016-07-08

状況

react-routerにてURL Paramsを使って動的にページ制御しようとしたところ、該当のURIをサーバーにリクエストした結果をロードしても、reactで描画すべきコンテンツが空になってしまった

問題点と原因

初期表示用のHTMLファイル(index.html)に記述した静的ファイル(css, js)の参照URLが存在しない場所を指していて、SPAとしての機能が働かなかった。これが直接的な問題。

その原因は、次のようにreact-routerのルーティング設定をし、かつ静的ファイルを相対パスで指定したことで、返されたHTMLファイル内の静的ファイル参照先をブラウザが解決する際に矛盾を発生させていたからだった。

react-routerは悪くない。アプリケーションの設定方法に誤りがあった。

bundle.jsの記述抜粋
  <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>

index.htmlの記述抜粋
<!-- いずれも参照先は相対パス指定 -->
<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は動かない。

error.png

詳細な問題分析

react-routerHistory APIを使っている、という点に着目すると、実装時に注意しなくてはならない点が見えてきた。

そもそも、History APIpushState関数により、より自然な形でSPAのロケーションを操作できるようになった経緯がある。しかし、同時にサーバのサポートが必要になった。もしそのURIがアドレスバーをクリックするなどしてサーバに送信されたら、適切な制御を行ってレスポンスを返す必要があるからだ(react-router公式にも書いてある1)。そのサポートとは次のような制御を指す。

  1. 個々のページのリクエストに対して、サーバはその時の状態を解釈して適切なコンテンツを描画し、応答する(いわゆるサーバサイドレンダリング)。
  2. すべてのリクエストに対して、同一の初期表示用ページを戻し、その後の実際の描画処理はクライアントに任せる
  3. 上記をミックスする

もし、Nodejs + express を使ってアプリケーション開発していて、次のような記述をしているなら、そのアプリのサーバのサポートは上記の2に当たる。

server.js
// 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の深さによらずに静的ファイルを確実に参照できるようになる。

index.htmlの記述抜粋(新)
<link href="/css/animate.css" rel="stylesheet">
<script src="/build/bundle.js"></script>

ハマリ時間

4時間。。

参照したページ

  1. https://github.com/reactjs/react-router/blob/master/docs/guides/Histories.md#configuring-your-server

26
21
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?