Skip to content

Commit 0b87ce2

Browse files
committed
機能を大量に追加した。
1 parent b9eab1f commit 0b87ce2

File tree

4 files changed

+106
-14
lines changed

4 files changed

+106
-14
lines changed

src/app/[article_year]/[month]/[aid]/components/server.tsx

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,47 @@ import crypto from "crypto";
44
import ClientComponent from "./client";
55
import { AvailableLocales } from "@/i18n/request";
66

7+
// generateStaticParams は既存のまま
8+
79
export async function generateStaticParams() {
810
const indexes = getArticleIndexes();
911
return indexes.map((article) => {
10-
const [article_year, month, aid] = article.slug.split("/");
11-
return { article_year, month, aid };
12-
});
12+
// slugが "YYYY/MM/AID" の形式であることを前提とする
13+
const parts = article.slug.split("/");
14+
// partsの最後の3つの要素を取り出すことで、"lang/YYYY/MM/AID"のような形式にも対応できるようにする
15+
if (parts.length >= 3) {
16+
const [article_year, month, aid] = parts.slice(-3);
17+
return { article_year, month, aid };
18+
}
19+
// 予期しない形式のslugはスキップするか、ログを出す
20+
console.warn(`Skipping invalid slug format in generateStaticParams: ${article.slug}`);
21+
return null; // nullを返して後でfilter(Boolean)で除外する
22+
}).filter(Boolean); // nullを除外
1323
}
1424

25+
26+
// ArticleServer コンポーネントは既存のまま、または generateMetadata で取得した
27+
// データを再利用するようにリファクタリング可能ですが、ここでは generateMetadata の追加のみに留めます。
28+
// Next.js のApp Routerは、generateMetadata と ページコンポーネントで同じ
29+
// データ取得関数(getArticleIndexes, toHTMLなど)を呼び出した場合、
30+
// 可能であれば自動的に deduplicate (重複排除) してくれるため、
31+
// データの二重取得による大きなパフォーマンス低下は起きにくい設計になっています。
32+
1533
export default async function ArticleServer({
1634
params,
1735
}: {
1836
params: Promise<{ article_year: string; month: string; aid: string }>;
1937
}) {
2038
const resolvedParams = await params;
2139
const indexes = getArticleIndexes();
22-
const articles = await toHTML(indexes);
40+
const articles = await toHTML(indexes); // ここで再度データを取得
2341
const slug = `${resolvedParams.article_year}/${resolvedParams.month}/${resolvedParams.aid}`;
2442

25-
const headings_list = articles
26-
.filter((a) => a.slug === slug)
43+
// スラッグに一致する記事のみをフィルタリング
44+
const articlesForSlug = articles.filter((a) => a.slug === slug);
45+
46+
// TOC生成ロジック (既存のまま)
47+
const headings_list = articlesForSlug
2748
.map((article) => {
2849
return {
2950
lang: article.lang as AvailableLocales,
@@ -35,21 +56,25 @@ export default async function ArticleServer({
3556
toc: headings.toc.map((heading) => {
3657
const text = heading.replace(/<.*?>/g, "").trim();
3758
const id = crypto.createHash("sha512").update(text).digest("hex");
38-
const level = "index-" + heading.slice(1, 3);
59+
// headingタグからレベルを抽出する処理をより頑健に
60+
const levelMatch = heading.match(/^<h([1-6])>/);
61+
const level = levelMatch ? `index-h${levelMatch[1]}` : 'index-heading'; // 例: <h1> -> index-h1
3962
return { id, text, level };
4063
}),
4164
lang: headings.lang,
4265
};
4366
});
4467

45-
const processedContents = articles
46-
.filter((a) => a.slug === slug)
68+
// コンテンツ処理ロジック (既存のまま)
69+
const processedContents = articlesForSlug
4770
.map((article) => {
4871
return {
4972
content: article.content.replace(
5073
/<h([1-6])>(.*?)<\/h[1-6]>/g,
5174
(match, level, text) => {
75+
// 見出しレベルを1つ下げる(h1 -> h2, h2 -> h3など)
5276
const newLevel = Math.min(parseInt(level) + 1, 6);
77+
// IDは元の見出しテキストから生成
5378
const id = crypto.createHash("sha512").update(text).digest("hex");
5479
return `<h${newLevel} id="${id}">${text}</h${newLevel}>`;
5580
}
@@ -60,10 +85,13 @@ export default async function ArticleServer({
6085

6186
return (
6287
<ClientComponent
63-
articles={articles}
88+
// ClientComponentに渡すarticlesは、スラッグでフィルタリングしたものを渡すのが効率的ですが、
89+
// 元のコードに合わせてarticles全体(またはClientComponentが必要とするデータ構造)を渡します。
90+
// ここではarticles全体を渡す元のロジックを踏襲します。
91+
articles={articles} // 全記事データを渡す (元のコード通り)
6492
slug={slug}
65-
tocs={tocs}
66-
processedContents={processedContents}
93+
tocs={tocs} // フィルタリング済みのTOCs
94+
processedContents={processedContents} // フィルタリング済みのコンテンツ
6795
/>
6896
);
69-
}
97+
}

src/app/[article_year]/[month]/[aid]/page.tsx

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import ArticleServer from "./components/server";
22
import { getArticleIndexes } from "@/app/article/article";
3+
import { toHTML } from "@/app/article/article";
34
import "./page.css";
4-
5+
import { Metadata } from "next";
56
export async function generateStaticParams() {
67
const indexes = getArticleIndexes();
78

@@ -13,6 +14,57 @@ export async function generateStaticParams() {
1314
}
1415

1516

17+
// メタデータを生成する generateMetadata 関数を追加
18+
// この関数はページコンポーネントよりも先に実行されます
19+
export async function generateMetadata({
20+
params,
21+
}: {
22+
params: Promise<{ article_year: string; month: string; aid: string }>;
23+
}): Promise<Metadata> {
24+
const resolvedParams = await params;
25+
// 1. paramsからslugを再構築
26+
const slug = `${resolvedParams.article_year}/${resolvedParams.month}/${resolvedParams.aid}`;
27+
28+
// 2. 記事データを取得
29+
// toHTML関数が記事のコンテンツだけでなく、タイトルやディスクリプションなどのメタデータも
30+
// 含むオブジェクトの配列を返すことを前提とします。
31+
const indexes = getArticleIndexes();
32+
const articles = await toHTML(indexes); // articlesは[{ slug: '...', title: '...', description: '...', lang: '...', content: '...' }, ...] のような形式を想定
33+
34+
// 3. 現在のslugに一致する記事データを検索
35+
// generateStaticParams の設計から、slug に一致する記事は存在するはずですが、
36+
// toHTML が複数の言語バージョンを同じ slug で返す可能性があるため、
37+
// 最初に見つかったものをメタデータとして利用します。
38+
const article = articles.find((a) => a.slug === slug && a.lang === "en");
39+
40+
// 4. 記事データからタイトルとディスクリプションを取得し、Metadata オブジェクトを生成
41+
const title = article?.title || "記事が見つかりません"; // 記事が見つからない場合のデフォルトタイトル
42+
const description = article?.description || "要求された記事が見つかりませんでした。"; // 記事が見つからない場合のデフォルトディスクリプション
43+
44+
// 必要に応じて、サイト名などをタイトルに追加する
45+
const fullTitle = `${title} | The Infinity's`; // 例: "Next.jsでメタデータを生成する | あなたのサイト名"
46+
const og_image_url = `${slug}/${article?.thumbnail?.split("/").slice(-1)[0]}`;
47+
const metadata: Metadata = {
48+
metadataBase: new URL(process.env.BASE_URL || "https://the-infinitys.f5.si"),
49+
title: fullTitle, // ページのタイトルを設定
50+
description: description, // ページのディスクリプションを設定
51+
alternates: {
52+
canonical: `/${slug}`, //
53+
},
54+
// Open Graph メタデータ (SNS共有時に表示される情報)
55+
openGraph: {
56+
title: fullTitle,
57+
description: description,
58+
url: `/${slug}`, // 絶対URLが推奨
59+
type: 'article', // コンテンツタイプを記事に設定
60+
// 記事に画像がある場合は、imagesプロパティを追加
61+
images: [og_image_url || ""],
62+
locale: article?.lang || 'ja_JP', // 記事の言語に対応したロケールを設定
63+
}
64+
};
65+
return metadata;
66+
}
67+
1668
export default function ArticlePage({
1769
params,
1870
}: {

src/app/article/page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import "./article.css"; // CSSファイルをインポート
22
import "./page.css";
33
import Explains from "./components/explains";
44
import ArticlePage from "./components/articlepage"; // クライアントコンポーネントをインポート
5+
import { Metadata } from "next";
6+
7+
export const metadata: Metadata = {
8+
title: "The Infinity's Articles",
9+
description: "Much! A lot of! Ideas!",
10+
};
511

612
export default function Home() {
713
return (

src/app/music/page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { getMusicList, MusicCard } from "./music";
22
import styles from "./page.module.css";
33
import Explains from "./components/explains";
4+
import { Metadata } from "next";
45
export default function MusicPage() {
56
const musicList = getMusicList();
67

@@ -20,3 +21,8 @@ export default function MusicPage() {
2021
</>
2122
);
2223
}
24+
25+
export const metadata: Metadata = {
26+
title: "The Infinity's Musics",
27+
description: "Listen for the Infinite possibilities!",
28+
};

0 commit comments

Comments
 (0)