覚書 (7)

記事の属性(タグ)

URL とファイル名を決める

記事にタグを設定

/src/content/blog/post-1.md
---
title: ブログ記事1
created: 2023-10-01
modified: 2023-10-01
tags: ['ねこ', 'いぬ']
---

ブログの目次にタグ一覧を追加

/src/pages/blog.astro
---
import { getCollection } from 'astro:content';
import BlogLayout from '../layouts/BlogLayout.astro';
import BlogTime from '../components/BlogTime.astro';

const posts = await getCollection('blog', ({ data }) => {
  return data.title !== undefined && data.created !== undefined;
});

posts.sort((a, b) => {
  if (!a.data.created || !b.data.created) return 0;
  return b.data.created.valueOf() - a.data.created.valueOf();
});

// 記事(posts)からタグを取得し、未設定のタグは排除し、重複を取り除いた配列を作成している
const tags = [
  ...new Set(
    posts
      .map((post) => post.data.tags)
      .filter((tags) => tags !== undefined)
      .flat()
  ),
];

// なんとなくsort
tags.sort();
---

<BlogLayout title="Blog">
  <h2>記事一覧</h2>
  <ul class="posts">
    {
      posts.map((post) => (
        <li>
          {post.data.created && <BlogTime date={post.data.created} />}
          <a href={`/posts/${post.slug}/`}>{post.data.title}</a>
        </li>
      ))
    }
  </ul>
  <h2>タグ一覧</h2>
  <ul class="tags">
    {
      tags.map((tag) => (
        <li>
          <a href={`/tags/${tag}/`}>{tag}</a>
        </li>
      ))
    }
  </ul>
</BlogLayout>

タグ一覧を作る

やっていることは [slug].astro と大差ないけど、いくつかの違いがある。

  1. titlecreated に加えて tags(タグ)も必須
  2. 各タグごとに記事を分割(重複あり)している
/src/pages/tags/[tag].astro
---
import { getCollection } from 'astro:content';
import BlogLayout from '../../layouts/BlogLayout.astro';
import BlogTime from '../../components/BlogTime.astro';
import type { CollectionEntry } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog', ({ data }) => {
    return data.title !== undefined && data.created !== undefined && data.tags !== undefined;
  });

  const tags = [...new Set(posts.map((post) => post.data.tags).flat())];

  return tags.map((tag) => {
    return {
      params: { tag },
      props: {
        posts: posts.filter((post) => {
          if (tag && post.data.tags) {
            return post.data.tags.includes(tag);
          }
        }),
      },
    };
  });
}

interface Props { posts: CollectionEntry<'blog'>[] }
const { tag } = Astro.params;
const { posts } = Astro.props;
---

<BlogLayout title={`タグ: ${tag}`}>
  <h2>記事一覧</h2>
  <ul class="posts">
    {
      posts.map((post) => (
        <li>
          {post.data.created && <BlogTime date={post.data.created} />}
          <a href={`/posts/${post.slug}/`}>{post.data.title}</a>
        </li>
      ))
    }
  </ul>
</BlogLayout>

ひとまずおわり?

さて、ブログはこんなもんでよろしいのではないだろうか。さっさとデプロイしてしまおう。

その前に、/src 以下はこうなっている。なかなか大変だった割にファイルは少ないな。

src
├── components
│   └── BlogTime.astro
├── content
│   ├── blog
│   │   ├── post-1.md
│   │   └── post-2.md
│   └── config.ts
├── env.d.ts
├── layouts
│   ├── BlogLayout.astro
│   ├── Footer.astro
│   ├── Head.astro
│   ├── Header.astro
│   └── Nav.astro
├── pages
│   ├── blog.astro
│   ├── index.astro
│   ├── page-test.md
│   ├── posts
│   │   └── [slug].astro
│   └── tags
│       └── [tag].astro
└── styles
    ├── simple.css
    └── style.css