ブログを高速化・高機能化した話
最近、自分のブログの読み込みが遅いなと感じていました。
特に画像が多い記事だと、表示されるまでに時間がかかって、読んでいてストレスを感じることもありました。
ということで、思い切ってブログの改善に取り組んでみました。
実施した内容
パフォーマンス改善
まず最初に取り組んだのが、読み込み速度の改善です。
画像の最適化
問題点
- 画像が PNG や JPEG のまま配信されていて、ファイルサイズが大きい
- 画面サイズに関係なく、常に同じ大きさの画像を読み込んでいた
- 遅延読み込み(lazy loading)が実装されていなかった
修正方法
OptimizedImage.astro というコンポーネントを作成しました。
<Picture
src={imageSrc}
alt={alt}
formats={['webp', 'jpeg']}
widths={[400, 800, 1200]}
sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"
loading={loading}
/>
Astro の Picture コンポーネントを使って、自動的に WebP 形式に変換し、複数のサイズを生成するようにしました。
結果
- 画像のファイルサイズが平均で 60-70% 削減
- モバイルでは小さい画像を、デスクトップでは大きい画像を配信するようになり、無駄な通信が減少
- 体感でも画像の表示が速くなった
Critical CSS の実装
問題点
- すべての CSS を一度に読み込んでいたため、初期表示が遅い
- 使わないスタイルも最初から読み込まれていた
修正方法
MainLayout.astro に Critical CSS を実装しました。
ファーストビューで必要な最小限の CSS だけをインラインで埋め込み、残りの CSS は非同期で読み込むようにしました。
<style is:inline>
/* Critical CSS */
body { margin: 0; font-family: sans-serif; }
.container { max-width: 1200px; margin: 0 auto; }
/* ... */
</style>
結果
- First Contentful Paint (FCP) が約 30% 改善
- ページの初期表示が目に見えて速くなった
Service Worker でキャッシュ戦略
問題点
- 同じリソースを何度もサーバーから取得していた
- オフラインでは全く表示できなかった
修正方法
sw.js を作成し、キャッシュファースト戦略を実装しました。
// キャッシュファースト戦略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request).then((fetchResponse) => {
return caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, fetchResponse.clone());
return fetchResponse;
});
});
})
);
});
結果
- 2回目以降のアクセスが爆速に(キャッシュから即座に配信)
- オフラインでも過去に閲覧したページは表示可能に
注目の投稿カルーセルの改善
問題点
- 画像の横に黒い空白があって見栄えが悪い
- 記事の内容が全くわからない
修正方法
FeaturedPostsCarousel.astro を大幅に改修しました。
まず、記事の本文から抜粋を取得する関数を作成:
function getExcerpt(body: string, maxLength: number = 150): string {
if (!body) return '';
const plainText = body
.replace(/^#+\s+/gm, '') // 見出しを削除
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // リンクをテキストに
.replace(/[*_~`]/g, '') // マークダウン記法を削除
.trim();
return plainText.length > maxLength
? plainText.substring(0, maxLength) + '...'
: plainText;
}
そして、レイアウトを画像の上にテキストをオーバーレイする形に変更:
.carousel-text-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(to top, rgba(0,0,0,0.9), transparent);
color: white !important;
}
結果
- 記事の内容がひと目でわかるように
- モダンでスタイリッシュなデザインに
- ライトモード・ダークモードの両方で文字が読みやすい
新機能の追加
パフォーマンス改善だけでなく、使い勝手を良くするための機能も追加しました。
PWA 対応
問題点
- ブラウザでしかアクセスできない
- ホーム画面に追加してもただのショートカット
修正方法
manifest.json を作成:
{
"name": "semiramisu blog",
"short_name": "semiramisu",
"start_url": "/",
"display": "standalone",
"theme_color": "#0ea5e9",
"background_color": "#ffffff",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
Service Worker と組み合わせることで、完全な PWA として動作するようにしました。
結果
- スマホのホーム画面に追加すると、ネイティブアプリのような見た目に
- アドレスバーが非表示になり、没入感のある体験に
- オフラインでも基本的な機能が使える
コメント機能(Giscus)
問題点
- 記事に対するフィードバックを受け取る手段がない
- 読者とのコミュニケーションが取れない
修正方法
Comments.astro コンポーネントを作成し、Giscus を統合:
<script src="https://giscus.app/client.js"
data-repo="semiramisu/semiramisu.github.io"
data-repo-id="YOUR_REPO_ID"
data-category="Comments"
data-category-id="YOUR_CATEGORY_ID"
data-mapping="pathname"
data-theme={theme}
async>
</script>
ダークモード対応も実装し、テーマが切り替わると自動的にコメント欄のテーマも変更されるようにしました。
結果
- GitHub アカウントでログインしてコメントできるように
- スパム対策も GitHub が行ってくれるので安心
- ディスカッション形式で会話が続けられる
検索機能の強化
問題点
- 検索機能が全く動作していなかった
- Pagefind のスクリプトが正しく読み込まれていなかった
修正方法
SearchBar.astro を修正し、Pagefind を正しく初期化:
// Pagefindスクリプトを動的に読み込む
const script = document.createElement('script');
script.src = '/pagefind/pagefind.js';
document.head.appendChild(script);
// 検索を実行
const search = await pagefind.search(query);
また、検索結果の表示も改善し、タイトルとタグだけのシンプルな表示に:
<a href="${data.url}" class="search-result-item">
<div class="search-result-title">${title}</div>
<div class="search-result-tags">${tagsHtml}</div>
</a>
結果
- 日本語でも高速に検索できるように
- 検索結果が見やすく、クリックしやすい
- インクリメンタルサーチで、入力中にリアルタイムで結果が表示される
関連記事の表示改善
問題点
- 記事の最後に大きく表示されていて、本文を読み終わるまでスクロールが長い
- サイドバーに移動したら、画像とタイトルが小さすぎて見えない
修正方法
RelatedPosts.astro にコンパクトモードを追加:
{isCompact ? (
<div class="related-posts-list">
{finalPosts.map((post) => (
<a href={`/posts/${IdToSlug(post.id)}/`} class="related-post-item">
<h3 class="related-post-title">{post.data.title}</h3>
<div class="related-post-tags">
{post.data.tags.slice(0, 3).map(tag => (
<span class="related-post-tag">{tag}</span>
))}
</div>
</a>
))}
</div>
) : (
// 通常の表示
)}
結果
- サイドバーでも読みやすい表示に
- 画像を削除してテキスト情報に集中
- 目次の下に配置することで、記事を読みながら関連記事も確認できる
その他の細かい改善
RSS フィードの改善
- 問題:タイトルだけで本文が含まれていなかった
- 修正:MarkdownIt と sanitize-html を使って、Markdown を HTML に変換してから配信
- 結果:フィードリーダーで記事の内容が読めるように
目次の改善
- 問題:長い記事だと目次が邪魔になる
- 修正:折りたたみ機能と読書進捗表示を追加
- 結果:必要な時だけ展開でき、今どこまで読んだかわかる
モバイル対応の強化
- 問題:モバイルでの操作性が悪い
- 修正:プルトゥリフレッシュ、スワイプナビゲーション、ボトムナビゲーションを実装
- 結果:スマホでも快適に操作できるように
実装中に起きた問題と解決策
Netlify のビルドエラー
pnpm-lock.yaml が古くなっていてビルドエラーが発生しました。
エラーメッセージを見ると、依存関係の不整合が原因でした。
pnpm install
を実行して lockfile を更新することで解決しました。
Tailwind CSS の循環参照
@apply visible
を使っていたところで循環参照エラーが発生。
Tailwind の @apply は内部的に CSS カスタムプロパティを使用するため、visibility プロパティとの組み合わせで問題が起きていました。
直接 visibility: visible
を使うように修正して解決しました。
ナビゲーションレイアウトの崩れ
ブックマーク機能を追加したらナビゲーションが崩れてしまいました。
原因は、ナビゲーションバーに要素を追加しすぎて、レスポンシブ対応が破綻したことでした。
結局、ブックマーク機能は複雑さに見合わないと判断し、関連コードをすべて削除しました。
検索機能が動かない
Pagefind の統合がうまくいっていませんでした。
原因は、Pagefind のスクリプトが正しいタイミングで読み込まれていなかったことと、API の使い方が間違っていたことでした。
スクリプトの動的読み込みと、正しい API の使用方法に修正して解決しました。
まとめ
今回の改善で、ブログの読み込み速度が大幅に向上し、使い勝手も良くなったと思います。
特に画像の最適化は効果が大きく、体感でも違いがわかるレベルでした。
技術的な詳細まで含めると、かなり大規模な改修になりましたが、一つ一つ問題を解決していくことで、より良いブログになったと感じています。
まだまだ改善できる点はありそうですが、とりあえず一段落というところでしょうか。
これからも少しずつブログを育てていきたいと思います。
雑談
実は今回の改善、Claude に手伝ってもらいました。
自分一人だとここまで大規模な改修はできなかったと思います。
特に、各機能の実装方法を具体的なコードで提案してもらえたのが助かりました。
AI と一緒に開発するのも、なかなか楽しいものですね。
次は何を改善しようかな…
(この文章もClaudeが制作しました この注意書きだけ人間が書いています)
コメント