CONTENT
ここから
5. 型定義を作る
まず、ブログ記事の形を決めます。
types/blogs.ts を作成してください。
// types/blogs.ts
export type BlogCategory =
| 'Entertainment'
| 'Culture'
| 'Music'
| 'Design'
| 'Tourism';
export type BlogPost = {
id: string;
slug: string;
title: string;
description: string;
category: BlogCategory;
tags: string[];
publishedAt: string;
readingMinutes: number;
body: string[];
};
ここでやっていること
BlogPost は、記事1件分の設計図です。
title → 記事タイトル
description → 記事の短い説明
category → 記事カテゴリ
tags → タグ一覧
body → 本文
型を作っておくと、記事データの書き間違いに気づきやすくなります。
地味ですが、後でかなり助かります。
6. 記事データを作る
次に、data/posts.ts を作成します。コピーして活用してください。
// data/posts.ts
import type { BlogPost } from '@/types/blogs';
export const posts: BlogPost[] = [
{
id: 'post-001',
slug: 'gifu-riverside-night-market',
title: '長良川沿いに一夜限りのナイトマーケットが登場。音楽と屋台で2,000人が来場',
description:
'岐阜市の長良川河川敷で、地元アーティストと飲食店が集まるナイトマーケットが開催されました。',
category: 'Entertainment',
tags: ['岐阜市', '長良川', '音楽イベント', '屋台'],
publishedAt: '2026-06-01',
readingMinutes: 4,
body: [
'岐阜市の長良川河川敷で、地元の若手アーティストや飲食店が参加する一夜限りのナイトマーケット「Nagara River Night」が開催されました。',
'会場には、アコースティックライブ、キッチンカー、手作り雑貨のブースが並び、夕方から夜にかけて約2,000人が来場。川風に揺れる照明と音楽が、普段の河川敷を少しだけ映画のワンシーンのように変えていました。',
'主催者は、今後も季節ごとに開催を検討しているとのことです。地元の飲食店やクリエイターが自然に集まる場として、岐阜市の新しい夜の楽しみ方になりそうです。',
],
},
{
id: 'post-002',
slug: 'ogaki-retro-cinema-festival',
title: '大垣の商店街でレトロ映画祭。空き店舗を使った小さな上映会が話題に',
description:
'大垣市の商店街で、昭和風の雰囲気を活かしたレトロ映画祭が開催されました。',
category: 'Culture',
tags: ['大垣市', '映画祭', '商店街', 'レトロ'],
publishedAt: '2026-05-28',
readingMinutes: 5,
body: [
'大垣市中心部の商店街で、空き店舗を活用した小規模な映画イベント「大垣レトロシネマ横丁」が開催されました。',
'上映会場は、かつて洋品店だった店舗を改装したスペース。古い看板や木製の棚をそのまま残し、来場者がまるで昔の映画館に迷い込んだような空気を楽しめる演出が行われました。',
'イベントでは、岐阜県内で活動する映像作家の短編作品も上映され、観客とのトークセッションも実施されました。商店街のにぎわいづくりとして、今後の継続開催に期待が集まっています。',
],
},
{
id: 'post-003',
slug: 'takayama-street-music-weekend',
title: '高山の古い町並みにストリートミュージック。観光客と地元住民が足を止める週末に',
description:
'飛騨高山の古い町並みで、地元ミュージシャンによる週末限定の音楽イベントが行われました。',
category: 'Music',
tags: ['高山市', '飛騨高山', 'ストリートライブ', '観光'],
publishedAt: '2026-05-20',
readingMinutes: 6,
body: [
'高山市の古い町並み周辺で、地元ミュージシャンによる週末限定イベント「Takayama Street Music Weekend」が開催されました。',
'三味線、ギター、ハンドパンなど、ジャンルの異なる演奏者が通り沿いに登場。観光客だけでなく、買い物帰りの地元住民も足を止め、普段より少しにぎやかな週末となりました。',
'会場周辺のカフェや土産店では、音楽イベントに合わせた限定メニューも提供されました。観光地としての魅力に、地域の表現者が加わることで、新しい高山の楽しみ方が生まれています。',
],
},
{
id: 'post-004',
slug: 'seki-knife-fashion-show',
title: '関市で“刃物とファッション”の異色ショー。職人技をランウェイで表現',
description:
'刃物の町として知られる関市で、伝統工芸とファッションを組み合わせたショーが開催されました。',
category: 'Design',
tags: ['関市', '刃物', 'ファッションショー', '伝統工芸'],
publishedAt: '2026-05-10',
readingMinutes: 3,
body: [
'関市で、刃物職人の技術とファッションを組み合わせたイベント「SEKI EDGE COLLECTION」が開催されました。',
'会場では、包丁やはさみそのものを見せるのではなく、金属の輝き、刃の曲線、職人の手仕事から着想を得た衣装が披露されました。少し尖った企画ですが、そこが妙に関市らしい。静かな熱量がありました。',
'地元の高校生や若手デザイナーも制作に参加し、伝統産業を次の世代へどう伝えるかを考える場にもなりました。工芸とエンタメの接点として、今後の展開が注目されます。',
],
},
];
/**
* 役割: 公開日が新しい順に記事一覧を取得する
* 入力: なし
* 出力: 公開日の降順に並んだブログ記事配列
*/
export function getSortedPosts(): BlogPost[] {
return [...posts].sort((firstPost, secondPost) => {
return (
new Date(secondPost.publishedAt).getTime() -
new Date(firstPost.publishedAt).getTime()
);
});
}
/**
* 役割: slugに一致する記事を取得する
* 入力: 記事slug
* 出力: 一致したブログ記事、存在しない場合はundefined
*/
export function getPostBySlug(slug: string): BlogPost | undefined {
return posts.find((post) => post.slug === slug);
}
/**
* 役割: 検索キーワードに一致する記事一覧を取得する
* 入力: 検索キーワード
* 出力: タイトル・説明文・タグに一致したブログ記事配列
*/
export function searchPosts(keyword: string): BlogPost[] {
const normalizedKeyword = keyword.trim().toLowerCase();
if (normalizedKeyword.length === 0) {
return getSortedPosts();
}
return getSortedPosts().filter((post) => {
const searchableText = [
post.title,
post.description,
post.category,
...post.tags,
]
.join(' ')
.toLowerCase();
return searchableText.includes(normalizedKeyword);
});
}
ここで大事な考え方
記事をただ並べるだけではなく、3つの関数を作っています。
| 関数 | 役割 |
|---|---|
getSortedPosts | 新しい順に記事を並べる |
getPostBySlug | URL用のslugから記事を探す |
searchPosts | 検索キーワードで記事を絞り込む |
この3つがあると、トップページ、詳細ページ、検索機能が作りやすくなります。