メインコンテンツまでスキップ

ニュースリンク集のページを作成した備忘録

ここでは、サイト内に国内外のニュースサイトを一覧できる「ニュースページ」を実装した際の備忘録として整理します。

備考

この記事でカバーする主なトピック

  • Docusaurusのi18n機能 (<Translate>, translate API)
  • Reactコンポーネント設計と動的ファビコン取得の実装
  • CSS Modulesによるスコープされたスタイリング
  • CSS Grid Layoutによるレスポンシブデザイン
  • HTML <details> タグを用いたアクセシブルなUI

ページの目的と機能の概要

このページの目的は、個人的な情報収集の起点として、信頼できるニュースソースへ素早くアクセスできる環境を構築することです。

  • カテゴリ分類: ニュースサイトを「総合・経済」「テクノロジー」等に分類し、HTMLの<details>タグでカテゴリ毎の開閉を可能にしました。
  • 多言語対応 (i18n): Docusaurusの国際化機能を活用し、ページ内の全テキストを日英で切り替えられるようにしました。
  • セクションへの直接リンク: 各カテゴリ見出しにアンカーリンクを設置し、特定のセクションを直接共有できるようにしました。
  • レスポンシブなカードレイアウト: CSS Gridレイアウトを採用し、様々な画面サイズで表示を最適化しました。

実装の詳細

この機能は、Reactコンポーネントとして src/pages/news.tsx に実装し、スタイリングにはCSS Modules (news.module.css) を採用しました。

多言語対応の実装 (i18n)

Docusaurusが提供する<Translate>コンポーネントとtranslate APIを利用し、マークアップと翻訳コンテンツを分離しました。

  • JSX内での翻訳: <Translate>コンポーネントは、idをキーとして対応する翻訳テキストをcode.jsonから取得します。

    src/pages/news.tsx
    import Translate from '@docusaurus/Translate';

    <h1>
    <Translate id="news.page.heading">ニュース</Translate>
    </h1>
  • JSX外部での翻訳: translate APIは、コンポーネントの属性(props)など、JSXタグの外部で文字列を翻訳する場合に利用します。

    src/pages/news.tsx
    import { translate } from '@docusaurus/Translate';

    <Layout
    title={translate({
    id: 'news.page.title',
    message: 'ニュース一覧',
    })}
    />
翻訳データの一元管理

この仕組みにより、すべての翻訳データは i18n/{locale}/code.json に集約され、テキストの管理と翻訳作業が一元化されます。

コンポーネント設計

ページを構成する要素を機能ごとにコンポーネント化し、再利用性と可読性を高めました。

  • NewsSiteCardコンポーネント: サイトの情報をpropsとして受け取るだけでなく、サイトのファビコンを動的に取得・表示するロジックをカプセル化した、再利用可能なカードコンポーネントです。

    src/components/NewsSiteCard/index.tsx(抜粋)
    export default function NewsSiteCard({ href, title, description }: Props) {
    const domain = new URL(href).hostname;
    // ...ファビコン取得ロジック...
    }
  • SectionHeadingコンポーネント: as propを通じて、h2h3といった見出しレベルを動的に指定できるカスタムコンポーネントです。

スタイリング (CSS Modules & Grid Layout)

CSS Modulesを採用し、コンポーネント単位でスタイルを管理することで、グローバルなCSS汚染を防ぎました。

  • レスポンシブなGridレイアウト: カード一覧のレイアウトにはCSS Gridを使用しました。
    src/pages/news.module.css
    .cardGrid {
    display: grid;
    /* 1列の最小幅を300pxとし、コンテナ幅に応じて列数を自動調整 */
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 1rem;
    }
    この一行で、複雑なメディアクエリなしに柔軟なレスポンシブデザインを実現できます。

UI/UXの工夫

HTMLの標準機能とReactの状態管理を組み合わせ、リッチでアクセシブルなUIを目指しました。

  1. <details>によるアコーディオンUI: カテゴリの開閉機能は、HTML標準の<details><summary>タグで実装しました。これにより、JavaScript不要で、キーボード操作にも対応したアクセシブルなUIが実現します。

  2. 動的ファビコンとフォールバック処理: 各サイトの視認性を高めるため、ファビコンを動的に表示します。APIの不安定さやファビコンが存在しないケースを考慮し、堅牢なフォールバック処理を実装しました。

    • 複数のAPIソース: GoogleとDuckDuckGoのファビコンAPIをリスト化し、優先順位をつけて利用します。
      const faviconSources = [
      `https://www.google.com/s2/favicons?domain=${domain}&sz=128`,
      `https://icons.duckduckgo.com/ip3/${domain}.ico`,
      ];
    • エラーハンドリング: imgタグのonErrorイベントを利用し、画像の読み込み失敗を検知します。失敗した場合、useStateで管理するインデックスを更新し、次のAPIソースを試します。
      const [sourceIndex, setSourceIndex] = useState(0);

      const handleError = () => {
      // 次のソースがあればインデックスを更新
      if (sourceIndex < faviconSources.length - 1) {
      setSourceIndex(sourceIndex + 1);
      } else {
      // 全て失敗した場合、デフォルトアイコンに切り替え
      setUseDefaultIcon(true);
      }
      };
    • 最終手段のデフォルトアイコン: 全てのAPIで取得に失敗した場合でもUIが崩れないよう、コンポーネント内にSVGで定義したデフォルトアイコンを表示します。
  3. パフォーマンスへの配慮: ファビコン画像にはloading="lazy"属性を付与し、ブラウザの遅延読み込み機能を活用。ビューポート外の画像読み込みを遅らせることで、初期表示速度の向上を図りました。

参考資料