この記事の重要ポイント
Webサイトの「アプリをインストール」バナーは、現代のニュースレター購読ポップアップのようなものです。便利ではありますが、ユーザーのコンテキストを無視して表示されることがあまりにも多いのが現状です。
今回のアップデートでは、ブログの ランタイムの安定性 (Three.jsのクラッシュ修正)と ユーザー体験 (PWAフローの再設計)という2つの核心的な問題に取り組みました。
ブラウザは標準で「アプリをインストール」する機能を提供していますが、これには欠点があります。多くの場合、ページ読み込み直後にトリガーされ、ユーザーが最初の一文すら読み始めていない段階で割り込んでしまいます。
検索結果からブログにたどり着いたユーザーの目的は、読むことであってインストールすることではありません。ポップアップ攻撃は、直帰率と認知的負荷を高めるだけです。
私は パッシブ(受動的)インストールUI を実装しました。ルールはシンプルです: 「興味を持ってくれたユーザーにのみ提案する」 こと。
以下は、プロンプト表示を決定するロジックです。
sequenceDiagram
participant User
participant Browser
participant PWA as PWA Component
User->>Browser: ブログ記事を開く
Browser->>PWA: "beforeinstallprompt" 発火
PWA->>Browser: Prevent Default (バナー阻止)
PWA->>PWA: 30秒タイマー開始 ⏳
rect rgb(30, 30, "30)
note right of User: 記事を読んでいる...
end
alt 30秒以内に離脱
PWA */}>User: 何もしない (集中を尊重)
else 30秒以上滞在
PWA->>User: "アプリをインストール" トースト表示 📱
User->>PWA: インストールをクリック
PWA->>Browser: prompt()
end
ページが読み込まれ、ユーザーが読み始める。
ブラウザが `beforeinstallprompt` を発火。`preventDefault()` でネイティブバナーを阻止。
30秒のタイマーを開始。この間にユーザーが離脱すれば何もしない。
まだ記事を読んでいれば、右下に控えめなトーストを表示する。
PWAInstallToast.astro コンポーネントでイベントを制御しています。
// イベントを保持して後でトリガーできるようにする
window.addEventListener("beforeinstallprompt", (e) => {
e.preventDefault();
deferredPrompt = e;
// 最近閉じた履歴がなければ...
if (!sessionStorage.getItem("pwa-install-dismissed")) {
// エンゲージメントの高いユーザーを狙って遅延させる (30秒)
setTimeout(showToast, 30000);
}
}); この小さな変更により、「インストールしてください!」という押し付けから、「お、気に入ってくれましたか?アプリ版もありますよ」という提案へと、関係性がシフトします。
アップデート中、ビルドをクラッシュさせる重大なエラーに遭遇しました。
ReferenceError: module is not defined
at .../node_modules/three/build/three.module.js 依存関係を「最適化」しようとして、astro.config.mjs に手動でエイリアスを追加していました:
// 悪い設定例
resolve: {
alias: {
"react": "path/to/react", // <--- これがクラッシュの原因
"three": "path/to/three" }
}
これにより、Viteは react と three を特定のパスに解決することを強制されました。しかし、@react-three/fiber(特定のバージョンに依存)が期待するパスと競合してしまったのです。
graph TD
subgraph "Before (Crash)"
A[Astro/Vite] */}|Alias| B[React (v18.x at /path/A)]
A */}|Node Resolve| C[React (v18.x at /path/B)]
D[@react-three/fiber] */} C
style C fill:#f96,stroke:#333,stroke-width:2px,color:#fff
style B fill:#f96,stroke:#333,stroke-width:2px,color:#fff
end
subgraph "After (Fix)"
X[Astro/Vite] */}|Dedupe| Y[React (Single Instance)]
Z[@react-three/fiber] */} Y
style Y fill:#9f9,stroke:#333,stroke-width:2px,color:#000
end
結果として、 複数のReactインスタンス が読み込まれたり、ESM環境で module 参照が壊れたりしていました。
dedupe を信頼する解決策は「最適化」よりもシンプルでした。手動エイリアスを削除し、Viteの dedupe 設定に任せることで、ライブラリが1つだけバンドルされるようにしました。
vite: {
resolve: {
// 手動エイリアスは完全に削除!
dedupe: ["react", "react-dom", "three"]
},
optimizeDeps: {
exclude: ["amazon-paapi", "sharp"],
include: ["@react-three/fiber", "@react-three/drei"]
}
} これを適用すると、pnpm build は Exit Code 0 で即座に成功しました。
もう一つの厄介事は、開発中に Pagefind 検索エンジンが 404 エラーを吐き続けていたことです。Pagefind はビルド後に実行される静的検索ツールなので、開発モード(dev)では pagefind.js が存在しません。
そこで、SearchDialog.astro を更新し、開発モードでは明示的にロードをスキップするようにしました。
// SearchDialog.astro
if (!pagefind) {
// 開発中はPagefindのロードをスキップして404を防ぐ
if (import.meta.env.DEV) {
return;
}
// Try import...
}
安定性とUXは、何を追加するかよりも、 何を削除するか (手動エイリアス、即時のポップアップ)にかかっていることが多いです。これらの修正により、HonoGearはより安定し、ユーザーの注意力を尊重するブログになりました。
[!TIP] 教訓 : React/Vite/Astroの混合スタックで奇妙な “ReferenceError” や “Module not found” に遭遇したら、まず
astro.config.mjsを確認してください。9割方、解決の競合が原因です。
次は、コンテンツの拡充と新しい「スマート検索」機能の強化に注力します。