3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Qiitaの累計記事から「今、トレンドの技術書」を自動選別するランキングサイトを大幅アップデートした話

3
Posted at

はじめに

以前投稿した「技術書ランキングサイト」の作成記事から数ヶ月。ありがたいことに多くの反響をいただきましたが、運用していく中で 「全期間のランキングだけでは、技術のトレンドを追いきれない」 という課題が見えてきました。

そこで今回、 「期間(月間・年間)」×「ジャンル(インフラ・フロント等)」を自在に掛け合わせ、かつ「記事タグによる高精度な自動ジャンル分け」 を搭載した新生「Engineer's Book Hub」へと進化させました。

本記事では、フロントエンド(Next.js)とバックエンド(Python × Supabase)の両面から、その改修の舞台裏を解説します。

1. 新しくなったサイトの全体像

新サイトでは、ユーザーが「今、何が熱いのか」を直感的に探れるよう、UI/UXを大幅に刷新しました。

進化した3つのポイント
マトリックス検索: 「2026年2月」×「フロントエンド」といった、詳細なセグメントでのランキング表示が可能に。

タグベースの自動分類: 書籍タイトルだけでなく、紹介しているQiita記事の「タグ」を解析。

高速なデータ連動: App Routerの特性を活かし、フィルタリングとデータ取得をシームレスに統合。

2. 【バックエンド】Pythonによる「判定ロジック」の強化

これまではタイトルに「Python」と入っていなければバックエンド本と判定できませんでしたが、新ロジックでは 「紹介しているQiita記事のタグ」 を判定ソースに加えました。

記事タグを活用したカテゴリ判定
例えば『リーダブルコード』という本。タイトルには言語名がありませんが、紹介記事には「TypeScript」や「Go」というタグが付きます。これらを収集して集計することで、より実態に近いジャンル分けを実現しました。

# カテゴリ判定の核となるロジック(抜粋)
def determine_categories(title, articles):
  matched = set()
  all_tags = [t.lower() for a in articles for t in a.get('tags', [])]    Python
#カテゴリ判定の核となるロジック(抜粋)
def determine_categories(title, articles):
  matched = set()
  all_tags = [t.lower() for a in articles for t in a.get('tags', [])]
  for cat_name, keywords in CATEGORIES_MAP.items():
      # タイトルまたは記事タグにキーワードが含まれるか
      if any(kw.lower() in title.lower() or kw.lower() in all_tags for kw in keywords):
          matched.add(cat_name)
  return list(matched)

期間別集計のパイプライン
Supabase上の3つのテーブル(book_rankings, monthly_rankings, yearly_rankings)に対し、Pythonから定期的にバッチ処理を行い、常に最新の「トレンド」を流し込んでいます。

3. 【フロントエンド】期間×ジャンルの連動実装

Next.js (Client Components) において、複数のフィルタ条件を矛盾なくSupabaseクエリに反映させるのが今回最大の難所でした。

宣言的なクエリ構築
useEffect 内で、選択されている period, detail, activeTab の状態を監視し、動的にクエリを組み立てる手法をとっています。

useEffect(() => {
  const fetchRankings = async () => {
    // 1. テーブル選択
    let query = supabase.from(tableName).select('*');

    // 2. 期間フィルタ (eq)
    if (period !== 'all') {
      query = query.eq('period', detail);
    }

    // 3. ジャンルフィルタ (contains)
    if (activeTab !== "ALL") {
      query = query.contains('categories', [categoryId]);
    }

    const { data } = await query.order('total_points', { ascending: false });
    setBooks(data);
  };
  fetchRankings();
}, [activeTab, period, detail]);

この実装により、「月間ランキングを見ている最中にジャンルを切り替えても、期間設定がリセットされない」というストレスフリーな操作感を実現しました。

4. こだわりのUI/UXデザイン

エンジニアが毎日使いたくなるよう、デザインにもこだわりました。

スコアリングの可視化: 単なる順位だけでなく、記事数とLGTM数から算出した「pt」を表示。

記事へのクイックアクセス: その本が「なぜ評価されているのか」をすぐ読めるよう、上位3つのQiita記事リンクをカード内に埋め込み。

レスポンシブなタブ操作: スマホでもサクサクと期間を切り替えられるよう、横スクロール可能なチップUIを採用。

5. 今後の展望:GitHub Actionsでの完全自動化

現在、データ更新は手動実行を含むハイブリッドですが、今後は GitHub Actions を利用して週次・月次で完全に全自動更新される「メンテナンスフリー」な体制を目指しています。

また、将来的には「特定の技術スタック(例:Next.js + Tailwind)」に特化した、よりニッチなランキング機能の追加も検討中です。

最後に

今回の改修を通して、 「データの価値は、どう切り出すか(フィルタリングするか)で決まる」 ということを再認識しました。
Qiitaという膨大な知見の集積地から、自分にぴったりの一冊を見つける助けになれば幸いです。
最後まで読んでいただきありがとうございました!ぜひ今回作成したサイトに遊びに行って改善点などあればコメントください。
「Engineer's Book Hub」

3
4
0

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
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?