Astro.js 記事一覧ページをTailwindでスタイリングする



[前回トップページで作成](https://qiita.com/takeshi_du/items/38b53f6f04bdcd38329a)した記事一覧を表示する `RecentPosts.astro` は「最新5件を取得するロジック」を含んでいるため、ページネーションされたデータを表示するこのページでは **そのまま再利用することができません**。

代わりに、**記事1件分の表示** をする新しいコンポーネント (`PostListItem.astro`) を作成し、それを一覧ページで繰り返し使用します。

## 記事一覧をスタイルする手順

### 1.記事リストアイテムコンポーネントを作成 (`src/components/PostListItem.astro`)

まず、`src/components/` ディレクトリに `PostListItem.astro` という名前で新しいファイルを作成します。

このコンポーネントは、記事1件分のデータ (`post`) をPropsとして受け取り、それを表示するようにします。`getPostUrl` ヘルパー関数もここで使います。

```astro
{/* src/components/PostListItem.astro */}
---
import type { CollectionEntry } from 'astro:content';
import { getPostUrl } from '../utils/url'; // パスは環境に合わせて調整

interface Props {
  post: CollectionEntry<'blog'>; // 'blog'コレクションの記事1件分のデータを受け取る
}

const { post } = Astro.props;
---
<li class="border-b border-gray-300 pb-6"> {/* 記事間の区切り線 */}
  <a href={getPostUrl(post)} class="block group">
    <h2 class="text-2xl font-semibold text-blue-800 group-hover:text-blue-600 transition duration-200 mb-2">
      {post.data.title}
    </h2>
  </a>
  <div class="text-sm text-gray-500 mb-3">
    <span>公開日: {post.data.pubDate.toLocaleDateString()}</span>
    {post.data.tags && post.data.tags.length > 0 && (
      <span class="ml-4">
        タグ:
        {post.data.tags.map(tag => (
          <a href={`/tags/${tag}/`} class="inline-block bg-gray-200 rounded-full px-2 py-0.5 text-xs text-gray-700 hover:bg-gray-300 ml-1">
            {tag}
          </a>
        ))}
      </span>
    )}
  </div>
  {post.data.description && (
    <p class="text-gray-700 leading-relaxed">{post.data.description}</p>
  )}
  <a href={getPostUrl(post)} class="text-blue-600 hover:underline inline-block mt-3">
    続きを読む &rarr;
  </a>
</li>
```
* Propsとして `post` を受け取るように `interface Props` を定義します。型は `CollectionEntry<'blog'>` です。
* `getPostUrl` をインポートします。
* `<li>` 要素をルートとし、その中にタイトル、日付、タグ（あれば）、説明（あれば）、続きを読むリンクなどを配置し、それぞれにTailwindクラスでスタイルを適用します。

### 2.記事一覧ページ (`[...page].astro`) を修正

    * `src/pages/blog/[...page].astro` ファイルを開き、以下のように修正します。

* **フロントマター部分:**
    * 作成した `PostListItem.astro` をインポートします。
    * `getPostUrl` のインポートは不要になります（`PostListItem` に移動したため）。
    * レイアウトコンポーネントも適切なもの（例: `BaseLayout`）に変更します。

```astro
---
// src/pages/blog/[...page].astro
import { getCollection } from 'astro:content';
import type { CollectionEntry } from 'astro:content'; // CollectionEntryをインポート
import type { Page } from 'astro'; // Page型をインポート(必要に応じて)
// import { getPostUrl } from '../../utils/url'; // ← PostListItemに移動したので削除
import BaseLayout from '../../layouts/BaseLayout.astro'; // ← レイアウトをBaseLayoutに変更(例)
import PostListItem from '../../components/PostListItem.astro'; // ← 作成したコンポーネントをインポート

export async function getStaticPaths({ paginate }) {
  const allPosts = await getCollection('blog');
  const sortedPosts = allPosts.sort((a, b) =>
    b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
  );
  return paginate(sortedPosts, { pageSize: 5 });
}

// Page型の定義 (前の手順で作成した CustomPageData などを使用)
interface CustomPageData {
  data: CollectionEntry<'blog'>[];
  start: number;
  end: number;
  size: number;
  total: number;
  currentPage: number;
  lastPage: number;
  url: {
    current: string;
    next?: string;
    prev?: string;
  };
}
interface Props {
  page: CustomPageData;
}
const { page } = Astro.props as Props;
---
```

* **テンプレート部分:**
    * 全体を適切なレイアウトコンポーネントで囲みます（例: `<BaseLayout>`）。
    * ページタイトル (`<h1>`)、件数表示 (`<p>`)、記事リスト (`<ul>`)、ページネーション (`<nav>`) にTailwindクラスを追加してスタイルを適用します。
    * 記事リストの `map` 処理の中で、`<li>` の代わりに `<PostListItem post={post} />` を呼び出すように変更します。

```astro
---
// --- 上記のフロントマター ---
---
<BaseLayout title={`ブログ記事一覧 - ${page.currentPage}ページ目`}>
  <div class="max-w-3xl mx-auto px-4 py-20"> {/* コンテンツ全体のコンテナ */}
    <h1 class="text-3xl md:text-4xl font-bold text-center mb-4 text-gray-800">ブログ記事一覧</h1>
    <p class="text-center text-gray-600 mb-8">{page.start + 1}件目から{page.end + 1}件目を表示 ({page.total}件中)</p>

    <ul class="space-y-8"> {/* 記事間のスペース */}
      {/* page.data を map して PostListItem コンポーネントを呼び出す */}
      {page.data.map((post) => (
        <PostListItem post={post} />
      ))}
    </ul>

    {/* ページネーションリンク (Tailwindでスタイル付け) */}
    <nav aria-label="ページネーション" class="flex justify-center items-center space-x-4 mt-12 mb-8">
      {page.url.prev ? (
        <a href={page.url.prev} class="px-4 py-2 border border-gray-300 rounded hover:bg-gray-100 transition duration-200">
          &larr; 前のページへ
        </a>
      ) : (
        <span class="px-4 py-2 border border-gray-200 rounded text-gray-400 cursor-not-allowed">
          &larr; 前のページへ
        </span>
      )}

      <span class="text-gray-700">
        {page.currentPage} / {page.lastPage} ページ
      </span>

      {page.url.next ? (
        <a href={page.url.next} class="px-4 py-2 border border-gray-300 rounded hover:bg-gray-100 transition duration-200">
          次のページへ &rarr;
        </a>
      ) : (
        <span class="px-4 py-2 border border-gray-200 rounded text-gray-400 cursor-not-allowed">
          次のページへ &rarr;
        </span>
      )}
    </nav>
  </div>
</BaseLayout>
```

### 開発サーバーでの確認

* 開発サーバーを起動 (`npm run dev` など) して、ブログ記事一覧ページ (`/blog/`, `/blog/2` など) にアクセスします。
* ページ全体、記事リスト、ページネーションリンクにTailwind CSSのスタイルが適用されているか確認してください。
* 記事リストの各項目が `PostListItem.astro` コンポーネントによって正しく表示されているか確認してください。

## まとめ
これで記事一覧ページにスタイルが適用され、記事リスト部分もコンポーネント化されてタグごとの記事一覧ページでも再利用できるようになります。

スタイルしたページはこちらの画像のようになります。

![screencapture-localhost-4321-astro-tutorial-blog-2-2025-04-24-22_18_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549249/c30c3534-5323-45d8-be96-74c461c2fe31.png)