Files
ai-learning-platform/frontend/src/app/skills/[id]/page.tsx
T
TradeMate Dev 6850ceff56 fix(seo): 详情页服务端渲染写入静态HTML并修复skills预渲染报错
- 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
2026-07-11 17:46:31 +08:00

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} />
</>
);
}