6850ceff56
- courses/skills/contents/practices 详情页改为服务端取数 + initialData 渲染,正文进入静态 HTML(解决百度弱 JS 收录盲点) - contents 的 markdown 由 useEffect+setState 改为 useMemo 同步输出 - 四个 generateStaticParams 改用 API 真实 id;getX 校验 data.id 防后端 200+error 体被当有效数据导致 undefined.map 报错 - 更新进度文档至 v1.3.0
67 lines
2.1 KiB
TypeScript
Executable File
67 lines
2.1 KiB
TypeScript
Executable File
import type { Metadata } from 'next';
|
|
import { API_BASE, SITE_URL } from '@/lib/config';
|
|
import { articleJsonLd, breadcrumbJsonLd, absoluteUrl } from '@/lib/seo';
|
|
import SkillDetailClient from './client';
|
|
|
|
async function getSkill(id: string) {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/skills/${id}`, { next: { revalidate: 3600 } });
|
|
if (!res.ok) return null;
|
|
const data = await res.json();
|
|
if (!data || !data.id) return null;
|
|
return data;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function generateStaticParams() {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/skills?pageSize=200`, { next: { revalidate: 3600 } });
|
|
if (!res.ok) return [];
|
|
const data = await res.json();
|
|
const list = Array.isArray(data) ? data : data?.items ?? [];
|
|
return list.map((s: { id: string }) => ({ id: s.id }));
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
|
|
const skill = await getSkill(params.id);
|
|
if (!skill) return { title: '技能未找到' };
|
|
const title = skill.name;
|
|
const description = skill.description || `${skill.name} - 宇之然 AI 技能,免费学习与实践`;
|
|
const url = absoluteUrl(`/skills/${skill.id}`);
|
|
return {
|
|
title,
|
|
description,
|
|
alternates: { canonical: `/skills/${skill.id}` },
|
|
openGraph: { type: 'article', title, description, url },
|
|
};
|
|
}
|
|
|
|
export default async function SkillDetailPage({ params }: { params: { id: string } }) {
|
|
const skill = await getSkill(params.id);
|
|
const url = absoluteUrl(`/skills/${params.id}`);
|
|
const jsonLd = skill
|
|
? [
|
|
articleJsonLd({ headline: skill.name, description: skill.description || undefined, url }),
|
|
breadcrumbJsonLd([
|
|
{ name: '首页', url: SITE_URL },
|
|
{ name: '技能库', url: absoluteUrl('/skills') },
|
|
{ name: skill.name, url },
|
|
]),
|
|
]
|
|
: null;
|
|
|
|
return (
|
|
<>
|
|
{jsonLd && (
|
|
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
|
)}
|
|
<SkillDetailClient initialSkill={skill} />
|
|
</>
|
|
);
|
|
}
|