@shinyaz

Velite × Next.js でトップページに注目記事セクションを追加する

目次

はじめに

トップページに選んだ記事をピン留めできるセクションがほしかった。「最新記事」フィードではなく、どの記事をハイライトするかを自分でコントロールできるキュレーションセクションだ。記事のフロントマターに featured: true を書くだけで動くのが理想。

実装自体はシンプルだが、この機能を超えて使える3つのパターンが浮かび上がった。

パターン 1: default(false) による安全なスキーマ進化

Velite の posts スキーマに featured フィールドを追加する:

velite.config.ts
const posts = defineCollection({
  schema: s.object({
    // ...既存フィールド
    featured: s.boolean().default(false), // 追加
  }),
});

ポイントは default(false) だ。既存ファイルの変更がゼロで済む。すべての既存記事は暗黙的に featured: false として扱われ、featured: true を明示的に書いた記事だけが対象になる。

コンテンツスキーマを拡張するときに覚えておくべきパターンだ。現在の挙動を保つデフォルト値を設定すれば、新しいフィールドは純粋に追加的になる。マイグレーションスクリプトも、ファイルの一括編集も、既存コンテンツが壊れるリスクもない。

クエリ関数は既存の getPublishedPosts() の上に1行足すだけ:

src/lib/posts.ts
export function getFeaturedPosts(locale?: Locale) {
  return getPublishedPosts(locale).filter((post) => post.featured);
}

getPublishedPosts() が非公開記事のフィルタと日付降順ソートを済ませているため、両方の挙動を無料で継承する。published: false かつ featured: true の記事は正しく除外される。

パターン 2: MAX_TAGS によるタグ溢れ対策

タグが多い記事はカードレイアウトを壊す。対策:最大 N 件を表示し、超過分は +N で示す。

src/components/blog/featured-post-card.tsx
const MAX_TAGS = 3;
 
{
  tags
    .slice(0, MAX_TAGS)
    .map((tag) => <TagBadge key={tag} slug={tag} locale={locale} />);
}
{
  tags.length > MAX_TAGS && (
    <span className="text-xs text-muted-foreground">
      +{tags.length - MAX_TAGS}
    </span>
  );
}

上限値を定数にまとめることで、変更が1箇所で済む。同じパターンを通常の PostCard にも適用し、挙動を統一した。

パターン 3: 自己非表示セクション

トップページの注目記事セクションは、1件以上の注目記事があるときだけレンダリングされる:

src/app/[locale]/page.tsx
{
  featuredPosts.length > 0 && (
    <section className="mt-8 md:mt-12">
      <h2 className="text-xl font-semibold mb-4">{t.home.featuredPosts}</h2>
      <div className="grid gap-3 sm:grid-cols-2">
        {featuredPosts.map((post) => (
          <FeaturedPostCard key={post.permalink} {...post} locale={locale} />
        ))}
      </div>
    </section>
  );
}

つまり、すべての featured: true マーカーを外せばセクションは自動的に消える — コード変更不要、UI が壊れることもない。機能は純粋に追加的だ:コンテンツがあれば存在し、なければ消える。

まとめ

  • default(false) でスキーマ変更を安全に — 既存コンテンツに影響なし、新フィールドは純粋に追加的。Velite(や他のコンテンツスキーマ)をオプショナルフィールドで拡張するときのパターンとして覚えておく。
  • MAX_TAGS でレイアウト崩れを防止 — 定数ベースの slice と +N 表示で、タグ数に関係なくカードレイアウトが安定する。
  • 条件付きレンダリングで自己管理型セクションを作る{items.length > 0 && <Section />} で、機能が自身の表示・非表示を管理する。フィーチャーフラグも個別の設定も不要。

共有する

田原 慎也

田原 慎也

ソリューションアーキテクト @ AWS

AWS ソリューションアーキテクトとして金融業界のお客様を中心に技術支援を行っています。クラウドアーキテクチャや AI/ML に関する学びをこのブログで発信しています。

関連記事