開発日誌#5-1(会社HP) Rails 7 + Turbo でリンククリック後に表示が崩れる問題の原因と解決方法
1. 問題の概要
Rails 7 のアプリケーションで、リンクをクリックして別ページへ移動するとデザインが微妙に崩れてしまい、ページをリロードすると正しく表示される、という現象が発生していました。
症状: リンクをクリックして移動後、画像のサイズや配置がずれてしまう
一時対処: F5 リロードや新規タブで開くと正常に表示される
参考にしてもらえれば幸いです。
2. 原因
この問題は、Rails 7 に組み込まれている Turbo Drive(`turbo-rails`)による "差分ロード" が原因でした
Turbo Drive(旧 Turbolinks)とは?
ページ遷移時に DOM 全体を再読み込みせず、差分だけを読み込んで高速化する仕組み
Rails 7 のデフォルト構成だと `turbo-rails` が導入され、リンククリック時のページ遷移に適用される
なぜ差分ロードで崩れるのか?
一般的に、JavaScript で DOMContentLoaded イベントを使って表示の初期化を行う場合、差分ロード ではそのイベントが発火しないようです。
ページを完全リロード(F5 や新規タブ表示)すれば `DOMContentLoaded` が発火して正常に初期化されるのですが、Turbo Drive を経由した遷移では初期化処理が走らず、レイアウトが崩れた状態になってしまうのです。
3. 解決方法
`DOMContentLoaded` → `turbo:load` に切り替える
差分ロード後にもイベントが発火するように、以下のように `turbo:load` イベントを使います。
(Rails 6 以前の Turbolinks では `turbolinks:load` と呼ばれていたものと同様です。)
修正前
document.addEventListener('DOMContentLoaded', () => {
const imageContainer = document.querySelector('.news-images');
if (imageContainer) {
const images = imageContainer.querySelectorAll('img');
const imageCount = images.length;
if (imageCount <= 3) {
imageContainer.style.gridTemplateColumns = repeat(${imageCount}, 1fr);
} else if (imageCount === 4) {
imageContainer.style.gridTemplateColumns = 'repeat(2, 1fr)';
} else {
imageContainer.style.gridTemplateColumns = 'repeat(3, 1fr)';
}
}
});
修正後
document.addEventListener('turbo:load', () => {
const imageContainer = document.querySelector('.news-images');
if (imageContainer) {
const images = imageContainer.querySelectorAll('img');
const imageCount = images.length;
if (imageCount <= 3) {
imageContainer.style.gridTemplateColumns = `repeat(${imageCount}, 1fr)`;
} else if (imageCount === 4) {
imageContainer.style.gridTemplateColumns = 'repeat(2, 1fr)';
} else {
imageContainer.style.gridTemplateColumns = 'repeat(3, 1fr)';
}
}
});
私の場合はこれだけで解決しました。
DOMContentLoaded と turbo:load を併用する方法
もし Turbo Drive を使っていない環境でも同じコードを使いたい場合や、ローカルファイルを直接開くケースに対応したい場合は、両方のイベントを設定する方法もあるようです。
function initLayout() {
// 画像の配置を調整する処理
}
document.addEventListener('DOMContentLoaded', initLayout);
document.addEventListener('turbo:load', initLayout);
4. なぜリロードや新規タブでは直るのか?
リロードや新規タブで開いたときは、ブラウザがページを完全に読み込み直すため、DOMContentLoaded イベントが正常に発火し、JS の初期化処理が行われるからです。
ところが Turbo Drive は差分ロードなので、DOM を完全に再構築しません。そのため、DOMContentLoaded イベントが起きず、画像レイアウト処理などが実行されずに崩れたままの表示となります。
5. まとめ
原因: Turbo Drive による差分ロードで、DOMContentLoaded イベントが発火しない
対策: JS の初期化処理を turbo:load イベントに書き換える
状況によっては: DOMContentLoaded と turbo:load を併用し、両方で初期化する
こうした修正を行うことで、リンクをクリックしてページ移動しても表示崩れがなくなります。
同じ現象でお困りの方の参考になれば嬉しいです。