Chrononglyph

webサイト更新作業

#7322

三方面からのバグ修正

去年末、辛酸を舐める結果になったピクチャレ大会の期間限定ランキングが中止になった問題。
その直接的な原因はDockerが壊れていたことだったのですが、
デバッグを進めていくと他にも深刻な2つの不具合があることが判明しました。
それは結局年末休みだけでは片付けられず年始に持ち越すことに。
ところが今回、実家という比較的作業が捗る環境で奮闘したところ解決の目処が立ったので、
その備忘録として何が原因だったのかを書き残しておくことにします。
前提として、Next.js 13系、styled-components 5系、MUI 5系を使ったフロントエンドの開発です。
またバックエンドAPIにはLaravelを利用しています。


まずひとつは「ページをリロードすると一部のスタイルが崩壊する」という問題。
詳細については以下のフォーラムが詳しいです。



Prop className did not match (NEXT 13 + styled-component - without app folder)



おおまかに言えば、通常のページ間遷移では問題なく表示されるのに、
ページをブラウザの機能でリロードした場合にCSSのクラス名が不一致になるという現象です。
CSSのクラスが不一致になるとスタイルの指定がすべて無効になるため、
必然的に骨組みのような見た目になります。
上記のフォーラムではimport記法を修正することで直ったとの声が支持されていますが、
自分の場合はそれでも直りませんでした。
そこで、もうこれはstyled-componentsのバグだろうと割り切って
人力で同じスタイルを定義したclassを作り、バグる部分のコンポーネントに割り当てることに。
そうすれば当然直るわけですが、テーマ設定によって出力が異なる部分はそれでも直らず。
最終的に、テーマによって分岐する部分はCSSの擬似クラスで処理するようにしたら直りました。


プラグイン「next-themes」はuseThemeという関数(Hook)で現在のテーマを呼び出すことができ、
該当部分はそれを利用して背景色を決定していました。
ところがこれで最初に呼び出されるテーマ変数は初期レンダリング時のものであり、
ページロード中にユーザーが設定したものに内容が変化しているようです。
この不一致が不具合を起こしていた可能性は否定できません。
さんざんstyled-componentsに原因を求めていたのですが、もしかしたらそっちは悪くないのかも……。


もうひとつは「高速で同じディレクトリのページへ次々に遷移するとサイトが壊れる」という問題。
これの解決があまりにも難しかったことが期間限定ランキングの開催を諦めるに至った理由のひとつでもあり、
これは3方面から解決に臨みました。
まず、そもそも「ロード中なのに次の操作ができる」という構造が問題なので、
クリックしてからロード完了まではリンクを触れないようにローディング画面にするというアプローチ。
とりあえずこれを実装すれば「リクエスト中に次のリクエストを送る」ということができなくなり、
バグの根幹だったリクエストエラーは無くなるはず。
その代償としてユーザーはロード完了まで何もできないので読み込み時間が長いとストレスになります。


次に、ページが読み込まれたら必要なデータを都度リクエストするのではなく、
可能な限り事前にページキャッシュを作っておいてそれを読み込むようにしました。
従来はSSR(サーバーサイドレンダリング)という技術を使っていて、
ページが読み込まれるたびにすべてのAPIを実行していました。
これを使っているかぎりは、APIの負荷を減らすことはなかなか難しいと思います。
今回はそれをISR(インクリメンタル・スタティック・リジェネレーション)に変更しました。
これは、ページがリクエストされたらStaticなページ、つまりHTMLファイルを内部でビルドします。
ビルドされたページが存在するリクエストは次回以降それが呼び出されるため
サーバー側での処理が一切必要なくなり、
阿部寛のホームページのように爆速でページが表示されるようになります。
デプロイ時にあらかじめStatic(静的)なページを生成する技術なら前々からありますが、
それはピクチャレ大会のように最新情報を常に取得したい場合に適していません。
また、このサイトはいろいろな変数の組み合わせでページを生成するような仕組みなので、
もしStaticなページをビルドする場合はデプロイ時に全通りのページを生成する必要があり、
これはこれでデプロイの時間と負荷が大幅に高まるので望ましくありません。
しかしISRの場合は初めてアクセスされた時点で生成しかつ一定時間で自動的に再生成するため、
組み合わせが膨大で情報更新が頻繁なこともあるピクチャレ大会にも適用できるようになっています。
しかもこれのすごいのは、キャッシュをビルドしたページに他ページへのリンクがあると、
その中から次にアクセスされそうなページを自動判別して先行してキャッシュを作ってくれることです。


最後に、Laravel API側のリクエスト制限を大幅に緩和しました。
Laravel APIはデフォルトで1分60回まででそれ以上はエラーを出力するようです。
とりあえずこれを1分1800回まで緩和して様子を見ることにしました。
あんまり緩和しすぎるのも望ましくないので、今後もAPIリクエストの数を減らす工夫はしていきます。
JSONで受け取るべきエラーがHTML形式になっていると、



SyntaxError: Unexpected token < in JSON at position 0



というエラーコードが返ってくるのですが、
この問題に向き合った当初はこれの正体が掴めずに非常にモヤモヤしていました。
このエラーコードはレスポンスがHTMLなのでJSONにできないというエラーです。
だったらLaravel側でログ出力すればいいじゃん、と思ってもAPI単体では全然エラーを出力しないし……。
結局エラーハンドリングの部分を書き直してエラーはプレーンテキストで出力するようにしたら、
Too many requestsというエラー本文に辿り着き、やっと解決の糸口を掴めた次第です。


というわけでAPIの仕様変更、ユーザビリティの欠陥、ページ生成の仕様変更と、
3方面からの改善を実施したことでエラーは起こらなくなり、
しかも以前と比べものにならないくらい爆速で表示されるようになったので大いに満足です。
久々に高い壁を乗り越えた感じがしてweb開発もモチベが上がりました。


#6972

似て非なる

新サイト制作プロジェクトにNuxt.jsを採用したものの、
どうも仕様変更やそもそも最新版に対応していない機能が多すぎるため先日乗り換えを決心しました。
いくつか選択肢はありましたが、結局「Next.js」という似たり寄ったりなフレームワークを選択。
そして昨日、今日と環境構築をしていろいろいじってみているところです。


Nuxt.jsがVue.jsのフレームワークであるのに対してNext.jsはReactのフレームワークです。
このVue.jsとReactはよく比較されることがあるのですが、
2020年時点で自分が認識していたかぎり、「Vue.jsの方がとっつきやすくReactは難しい」
というのが一般的な評判だったと思います。少なくとも自分の周りではそうでした。
だからこそ、とっつきやすさ重視でVue.jsを選択したわけです。
Reactは難しいという評判が先行していたので敬遠していました。


しかし実際に両方いじってみると大差ないような気がします。
むしろ、人によってはReactの方が簡単と感じる可能性すらあります。
超ざっくり言えば、VueはHTML、JavaScript、CSSをそれぞれタグで区切って1ファイルに記述します。
これは昔ながらのHTMLの書き方に近く、そういう意味ではとっつきやすいと言えると思います。
一方、Reactは基本的にJavaScriptとして書いてHTMLをエクスポートする形になっています。
つまりコンポーネントとページの書き方が同じなんですよね。
エクスポートする要素は1ファイル1ブロックと厳密に決まっているので、
ブロック単位で管理することになります。


どちらも深く理解しているわけではないのでなんとも言えないところもありますが、
直感的には自分はもしかするとReactの方が向いているのではないかと思いました。
コンポーネントもページも一律同じ形式で管理するのでわかりやすいというのがその理由です。


Nuxt.jsとNext.jsの違いについてはさらによくわかっていませんが、
これに関しては逆にNuxt.jsの方がわかりやすいという印象。
Next.jsはフォルダごとの役割が公式に決められているわけではなかったり
あるいはPagesフォルダの中にApiフォルダが配置されていて複雑な構成になっていますが、
Next.jsはルート直下に各役割のフォルダが明示的に並べられていて、
ユーザーはそれに従うだけでいいのでわかりやすいです。
Serverフォルダに入れたファイルは自動的にサーバーサイドで処理してくれるとか。
Nuxt.jsは公式サイトにプラグインリストがあるのでどういうプラグインがあるのかもわかりやすい。
Next.jsは公式サイトを見ただけではその辺の実態がわかりません。
まあこれはフレームワークの使い勝手というより公式サイトの問題ですが。