ネットソリューション

Astroから記事一覧のJSONを出力する Astroを使ってオウンドメディアを作る-5

こんにちは。ネットソリューション事業部の山下です。
今回は、第3回で解説したコンテンツコレクションを使用して、記事一覧のデータをJSONで出力する方法について解説します。

JSON形式で記事一覧データを管理することで、各記事ページに挿入される過去記事一覧などの共通パーツを、それぞれのページを更新することなくJSONデータの差し替えだけで一括更新できるようになります。

作業の前に、コンポーネントファイルの見直し

記事出力をAstroコンポーネントからJavaScriptとJSONファイルの出力に変更するため、Astroコンポーネント内にCSS設定が記述されている場合は、globalで読み込むCSSファイルに記述を移動する必要があります。そうしないと、コンポーネントを読み込まなくなった場合に、そのCSSも読み込まれなくなります。

all-posts.json.tsを作成する

JSONファイルはビルド時に実ファイルとして生成されるため、 pages フォルダ内に配置します。今回は src/pages/api/all-posts.json.ts としました。

import { getCollection } from "astro:content";

export async function GET() {
  const allPosts = await getCollection("techblog");

  // ポストを pubDate でソートする
  allPosts.sort((a, b) => new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime());

  const allPostsFilter = allPosts.map((post) => {
    return {
      title: post.data.title,
      description: post.data.description,
      slug: post.slug,
      pubDate: post.data.pubDate,
      ogImage: post.data.ogImage,
      author: post.data.author,
      tags: post.data.tags,
    };
  });

  return new Response(JSON.stringify(allPostsFilter), {
    headers: {
      'Content-Type': 'application/json',
    },
  });
}

ポイント

Astroのコンテンツコレクションを使用して、変数 allPosts/src/content/techblog 内のデータを読み込んでいます。データは配列として格納され、その後、 pubDate を比較して日付ごとにソートしています。このままだと、各記事の本文など不要なデータが含まれてしまうので、別の変数 allPostsFilter を作成し、必要なデータだけを含む配列を生成しています。

データの確認

pages 内に配置しているので、以下のURLで実際のデータを確認することができます。
http://localhost:4321/techblog/api/all-posts.json

この記事だけのデータを抜き出してみると以下のようになっています。

[{
    "title": "Astroから記事一覧のjsonを出力する Astroを使ってオウンドメディアを作る-5",
    "description": "Astroから全記事のJSONデータを作成し、JSONから記事一覧を出力する方法を解説します。",
    "slug": "net-solution/2024/08/astro-blog-5",
    "pubDate": "2024-08-23T00:00:00.000Z",
    "ogImage": "ogp-astro-blog-5.png",
    "author": "山下",
    "tags": [
        "Astro",
        "オウンドメディア",
        "JSON"
    ]
}]

JSONファイルが完成したので、実際に記事ページの下部に過去記事を反映させてみましょう。

.astroファイルの記述例

<div
  id="outputLatest"
  class="jsInclude"
  data-href="/techblog/api/all-posts.json"
  data-limit="4"
  data-type="2col">
</div>

この部分では、他の場所でも再利用できるように、data属性に様々なプロパティを追加しています。
data-href:データの保存場所を指定
data-limit:表示する記事の上限数を指定
data-type:データの表示形式を指定

.jsファイルの記述例

document.addEventListener('astro:page-load', () => {
  const includeElements = document.querySelectorAll('.jsInclude');
  includeElements.forEach(element => {
    const url = element.getAttribute('data-href');
    const latest = element.getAttribute('data-limit') ? element.getAttribute('data-limit') : 12;
    const type = element.getAttribute('data-type') ? element.getAttribute('data-type') : null;
    const pathname = document.location.pathname;
    fetch(url).then(response => response.json()).then(json => {
      let html = '<div class="article-list">';
      if (type == "2col") {
        html = '<div class="article-list article-list__2column">';
      }
      const filteredItems = json.filter(item => !pathname.includes(item.slug));
      filteredItems.slice(0, latest).map(item => {
        html += createArticle(item);
        return;
      });
      html += '</div>';

      element.innerHTML = html;
    }).catch(error => {
      console.error('Fetch error:', error);
    });
  });
});

const createArticle = (item) => {
  let html = "";
  const author = item.slug.includes("net-solution")
    ? `ネットソリューション ${item.author}`
    : item.slug.includes("image-solution")
      ? `イメージソリューション ${item.author}`
      : item.slug.includes("contents-solution")
        ? `コンテンツソリューション ${item.author}`
        : item.author;
  const date = new Date(item.pubDate);
  const formattedDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
  let tags = "";
  if (item.tags.length) {
    tags += `<div class="article-card-tags">`;
    item.tags.map(tag => {
      tags += `<span class="tag">${tag}</span>`
    });
    tags += `</div>`;
  }
  html += `<article class="article-card"> <div class="article-card-image"> <img src="/techblog/assets/images/ogp/${item.ogImage}" alt=""> </div> <h2 class="article-card-title"> <a href="/techblog/${item.slug}/">${item.title}</a> </h2> <p class="article-card-desc">${item.description}</p> ${tags} <div class="article-card-info"> <span class="category"><span class="material-icons">edit</span>${author}</span> <time datetime="${date}"><span class="material-icons">calendar_month</span>${formattedDate}</time> </div> </article>`;
  return html;
}

ポイント

このメディアでは、Astroのビュートランジション機能を使用して、シームレスなフェードインアウトをページ遷移に取り入れています。そのため、通常のページ表示時に実行されるJavaScriptファイルが、最初のページ表示時にしか実行されません。この問題を解決するために、document.addEventListener('astro:page-load', () => {})で囲むことで、ビュートランジションが発生するたびにスクリプトが実行されるようにしています。

また、.astro側で記述したdata属性の内容に基づいて、JSONから必要なHTMLソースを生成し、異なるページやセクションに応じて動的に記事を表示することができます。


JSONファイルを自動で出力することで、サイト内のさまざまな場所で再利用できるようになります。また、Astroに限らず、JavaScriptでJSON(配列)を操作する技術は、ウェブ開発でよく使うものです。色々な実装方法を試して、できることをどんどん広げていきましょう!

Webの進化の速さにアップアップですがなんとか喰らいついていきたい。

山下 X@Frencel_ns

フロントエンドエンジニア
フレームワーク頑張りたい人
モンハンワイルズではキアヌ・リーブス似のイケオジでプレイしたい

好きなモンスターハンターの武器

双剣・狩猟笛

mail お問い合わせ

ご覧いただきありがとうございます。
当メディアへのご質問や各事業部へのお仕事のご相談がありましたら、お気軽にお問い合わせください。

article 過去記事