Skip to content
Cloud AI エンジニア入門ガイド
Go back

06. AstroPaperで記事を整理する:ディレクトリ・URL・タグ

この章の全体像として、URLの決まり方、素材の置き場所、タグの設計、一覧と保管庫の4つを番号順に並べ、増えても崩れない置き方を決められるようになることを示した図

シリーズ目次 前章: 05. AstroPaperで記事を書く:フロントマターと公開制御 次章: 07. AstroPaperのMarkdown表現とアセットの扱い

Table of contents

Open Table of contents

この章のねらい

記事が10本、50本と増えてくると、src/content/posts/ はファイルの山になります。ディレクトリとタグを使った整理の仕方と、それがURLにどう出るのかを見ていきます。URLは後から変えると外部リンクが切れます。早いうちに方針を決めてください。

ファイルパスから公開URLが決まる仕組み

記事ファイルのパスがスラグ化を経て公開URLに変換される流れを示した図

基本は単純です。

ファイルの場所公開URL
src/content/posts/hello.md/posts/hello/
src/content/posts/tech/hello.md/posts/tech/hello/
src/content/posts/tech/astro/hello.md/posts/tech/astro/hello/

src/content/posts/ より下のディレクトリ構造が、そのままURLの階層になります。

変換を担当しているコード

この処理は src/utils/getPostPaths.ts にあります。

function getPostPathSegments(filePath: string | undefined): string[] {
  return (
    filePath
      ?.replace(BLOG_PATH, "")
      .split("/")
      .filter(path => path !== "")
      .filter(path => !path.startsWith("_"))
      .slice(0, -1)
      .map(segment => slugifyStr(segment)) ?? []
  );
}src/utils/getPostPaths.ts

上から順に、

  1. src/content/posts の部分を取り除く
  2. / で分解する
  3. 空の要素を捨てる
  4. _ で始まる要素を捨てる
  5. .slice(0, -1) で最後の要素(ファイル名)を切り離す
  6. 残ったディレクトリ名をURL向けの表記へ変換する

という流れです。ファイル名そのものは、Astroが記事に割り当てたID(拡張子を除いたパス)の末尾から取り出して連結されます。

ディレクトリ名の変換規則

slugifyStr は、文字列に非ASCII文字が含まれるかどうかで処理を切り替えます。

export const slugifyStr = (str: string): string => {
  if (hasNonLatin(str)) {
    return kebabcase(str);
  }
  return slugify(str, { lower: true });
};src/utils/slugify.ts

実際の変換結果は次のようになります。

元の文字列変換後使われる処理
Astroastroslugify
AstroPaperastropaperslugify
E2E Testinge2e-testingslugify
OpenID Connectopenid-connectslugify
初心者向け初心者向けkebabcase
Astro 入門astro-入門kebabcase

日本語はそのまま残ります。URLに日本語が入ってもブラウザは扱えますが、共有するとパーセントエンコード(%E5%88%9D... のような表記)に化けて読めなくなります。ディレクトリ名とファイル名はASCIIの小文字とハイフンで統一してください

ディレクトリの分け方

分けない(フラット)

src/content/posts/
├── hello-world.md
├── astro-basics.md
└── css-tips.md

記事数が少ないうちはこれで十分です。URLも /posts/hello-world/ と短くなります。

カテゴリで分ける

src/content/posts/
├── tech/
│   ├── astro-basics.md
│   └── css-tips.md
└── diary/
    └── hello-world.md

URLは /posts/tech/astro-basics/ になります。URLを見ただけで内容の分類が分かるのが利点です。

年で分ける

src/content/posts/
├── 2025/
│   └── hello-world.md
└── 2026/
    └── astro-basics.md

時系列で整理したいならこれです。ただしURLに年が入るので、記事を書き直したときに移したくなります。移すとURLが変わります

シリーズで分ける

src/content/posts/
└── astro-paper/
    ├── 00-index.md
    ├── 01-what-is-astro-paper.md
    └── 02-getting-started.md

連載向きです。ファイル名に番号を付けておくと、エディタ上で順番どおりに並びます。このシリーズもこの形です。

注意:番号順に並ぶのはファイル一覧だけで、サイト上の記事一覧は日付順です。連載の順序を読者に伝えたい場合は、この章のように目次ページと前後リンクを自分で用意します。

4つの分け方を、生成されるURLと向き不向きで並べると次のようになります。

フラット・カテゴリ・年・シリーズの4つのディレクトリ構成と、それぞれで生成される公開URLおよび向き不向きを並べた図

素材を記事の近くに置く

記事専用の画像を、記事と同じディレクトリに置きたいことがあります。取り込み対象は .md.mdx だけなので、画像やSVGは自由に置いて構いません。困るのはMarkdownファイルだけです。

src/content/posts/astro-paper/
├── 01-what-is-astro-paper.md
└── assets/
    └── layers.svg

記事からは ./assets/layers.svg のような相対パスで参照します。書き方は次章: 07. AstroPaperのMarkdown表現とアセットの扱いで扱います。

補助的なMarkdownを置きたい場合は、ファイル名の先頭_ を付けてください。取り込みパターン **/[^_]*.{md,mdx} が判定しているのはファイル名だけなので、ディレクトリ名を _drafts/ にしても中身は除外されません。

src/content/posts/astro-paper/
├── _drafts/
│   ├── memo.md      ← 取り込まれる(/posts/memo/ として公開される)
│   └── _memo.md     ← 取り込まれない
└── _memo.md         ← 取り込まれない

_drafts/memo.md/posts/drafts/memo/ ではなく /posts/memo/ になるのは、URL生成時に _ で始まるディレクトリが読み飛ばされるからです(src/utils/getPostPaths.ts)。隠したつもりが、かえって階層のないURLで公開される。これがいちばん危ない。

3つの置き方を並べると、効くのがファイル名だけであることがはっきりします。

アンダースコア付きディレクトリの中のMarkdownは取り込まれて階層のないURLで公開され、ファイル名の先頭にアンダースコアを付けたものだけが除外されることを3つの例で比べた図

タグの設計

タグはフロントマターの配列で指定します。

tags:
  - "Astro"
  - "初心者向け"

タグページの生成

タグを付けると、次の2種類のページが自動で作られます。

URL内容
/tags/使われているタグの一覧
/tags/<タグのスラグ>/そのタグが付いた記事の一覧

タグ一覧を作っているのは src/utils/getUniqueTags.ts です。

const tags: Tag[] = posts
  .filter(postFilter)
  .flatMap(post => post.data.tags)
  .map(tag => ({ tag: slugifyStr(tag), tagName: tag }))
  .filter(
    (value, index, self) =>
      self.findIndex(tag => tag.tag === value.tag) === index
  )
  .sort((tagA, tagB) => tagA.tag.localeCompare(tagB.tag));src/utils/getUniqueTags.ts

この処理には性質が2つあります。

  1. 公開されている記事のタグしか集まらないpostFilter を通しているため、下書きだけに付けたタグはタグ一覧に出ません。
  2. 重複判定はスラグで行われるtag(URL用のスラグ)が同じものはひとつにまとめられます。

2つ目の性質により、Astroastro は同じタグになります。表示名には先に見つかったほうの表記が使われるので、記事ごとに大文字小文字がばらついていると表示が安定しません。

表記はプロジェクト内で統一してください。Astro と書くと決めたら、全記事でそろえます。

表記の違う3つのタグが、スラグ化を経て1つのタグページへ集約される様子が次の図です。

Astro・astro・ASTROという表記の違うタグがスラグ化によっていずれもastroになり、1つのタグページへまとまることを示した図

タグを付けるときの目安

タグを省略した記事には自動的に others が付きます。

記事一覧とページ送り

/posts/ は、astro-paper.config.tsposts.perPage 件ずつに分割されます。

posts: {
  perPage: 10,
},astro-paper.config.ts
URL内容
/posts/1ページ目
/posts/2/2ページ目

トップページに出る「Recent Posts」の件数は posts.perIndex で別に指定します。ここには featured: true の記事は含まれません。

並び順

一覧は更新日時(なければ公開日時)の新しい順です。カテゴリもディレクトリも並び順には効きません。

アーカイブページ

/archives/ は、記事を年から月でグループ化して並べるページです。使われるのは pubDatetime で、modDatetime ではありません。いつ書かれたかを軸にした一覧なので、記事を更新しても位置が動きません。

月名の表示には Intl.DateTimeFormat が使われ、サイトの lang 設定に応じた言語で表示されます。lang: "ja" なら「8月」と表示されます。

このページは features.showArchivesfalse にすると非表示になります。

URLを決めるときの心得

一度公開したURLを変えると、外部からのリンクや検索エンジンの評価が失われます。次の点を最初に決めておくと安心です。

決めることおすすめ
ディレクトリで分けるか記事数が50を超える見込みなら分ける
ファイル名の付け方英小文字とハイフン、内容が分かる短い語
日付をURLに含めるか含めないほうが記事を長く使える
タグの表記大文字小文字を統一する

どうしてもURLを変える必要が出た場合は、ホスティングサービスのリダイレクト機能で旧URLから新URLへ301(恒久的な移動)リダイレクトを設定してください。読者のブックマークも検索エンジンの評価も、これで新URLへ引き継がれます。

canonicalURL は代わりになりません。あれは重複した内容を持つ複数のURLのうちどれを代表とみなすかを検索エンジンへ伝えるだけで、読者を転送する働きはありません。そもそも静的サイトではURLを変えた時点で旧ページが消えるので、旧URL側に canonicalURL を書くことすらできません。

2つは似て見えますが、担う役割はまったく別です。

301リダイレクトが旧URLから新URLへ読者と評価を引き継ぐのに対し、canonicalURLは検索エンジンへの申告にすぎず転送しないことを並べて比べた図

要点

参考資料


シリーズ目次 前章: 05. AstroPaperで記事を書く:フロントマターと公開制御 次章: 07. AstroPaperのMarkdown表現とアセットの扱い


Share this post:

Previous Post
07. AstroPaperのMarkdown表現とアセットの扱い
Next Post
05. AstroPaperで記事を書く:フロントマターと公開制御