Article
实战:Next.js 博客的列表 SSR、Markdown 详情与后台表单
实战:Next.js 博客的列表 SSR、Markdown 详情与后台表单
前端博客体感取决于三件事:首屏列表是否快、详情是否可读、后台是否好发文。这篇文章按 Next.js App Router 的常见拆法,把「公开站服务端拉数」和「后台客户端表单」落到可执行细节,避免把整个站点做成纯 CSR 的单页。
页面怎么分
建议最少四类路由:
/:文章列表(服务端获取)/articles/[slug]:详情(服务端获取 + Markdown 渲染)/admin/login:登录/admin/articles:文章管理(客户端,带 Token)
公开页尽量在服务端拿数据,好处是首屏 HTML 里已经有标题与摘要,对 SEO 和无 JS 降级都更友好。后台则相反:强依赖鉴权与交互,用客户端组件更自然。
列表页:服务端拉已发布文章
关键点是 只请求已发布,并带分页。示例:
// app/page.tsx
async function getArticles() {
const res = await fetch(`${process.env.INTERNAL_API_URL}/api/articles?page=1&page_size=10`, {
next: { revalidate: 60 },
});
if (!res.ok) throw new Error("failed to load articles");
return res.json();
}
export default async function HomePage() {
const data = await getArticles();
const list = data.data?.list ?? [];
return (
<main>
{list.map((a: any) => (
<a key={a.id} href={`/articles/${a.slug}`}>
<h2>{a.title}</h2>
<p>{a.summary}</p>
</a>
))}
</main>
);
}
注意两类 API 基址:
- 服务端(RSC / route handler):优先打内网或本机后端,例如
http://127.0.0.1:18080,避免绕公网 - 浏览器:打同源
/api,由 Nginx 反代
NEXT_PUBLIC_* 会进浏览器包体,不要把内网地址或密钥放进去。revalidate 按更新频率调:个人博客 60–300 秒通常够用;发文后若要立刻可见,可在后台保存成功后触发按需再验证(或接受短暂延迟)。
列表不要服务端拉正文 Markdown。字段裁剪能减少 JSON 体积与渲染成本;封面用相对路径 /covers/... 或 /uploads/...,由当前站点域名加载。
详情页:Markdown 渲染与 XSS 边界
import ReactMarkdown from "react-markdown";
async function getArticle(slug: string) {
const res = await fetch(`${process.env.INTERNAL_API_URL}/api/articles/${slug}`, {
next: { revalidate: 60 },
});
if (!res.ok) return null;
return res.json();
}
export default async function ArticlePage({ params }: { params: { slug: string } }) {
const data = await getArticle(params.slug);
const article = data?.data;
if (!article) return <div>Not Found</div>;
return (
<article>
<h1>{article.title}</h1>
<ReactMarkdown>{article.content}</ReactMarkdown>
</article>
);
}
实战上建议:
- 默认不渲染任意原始 HTML;需要的话用 rehype-sanitize 白名单
- 代码块加高亮可以后加,先保证段落、列表、链接、代码正确
generateMetadata用文章标题与摘要填title/description,分享卡片会好看很多
详情 404 要返回真正的 notFound(),而不是 200 + 「没有这篇文章」——对爬虫更诚实。
后台表单:登录态与保存闭环
登录表单提交到 /api/auth/login,存 Token。发文表单字段至少:title、slug(可空自动生成)、summary、content、category、tags、status、cover。
保存逻辑建议:
- 点保存 → 按钮 disabled,防止双戳
POST/PUT /api/admin/articles带 Bearer- 成功后跳编辑页或列表,并提示成功
- 401 则清 Token 去登录
封面上传单独走 multipart/form-data。上传成功后把返回的 URL 填进表单的 cover_image,再随文章一起保存。不要假设「选了文件就等于封面已绑定」——分两步更清晰,失败也好提示。
Markdown 编辑可用 textarea + 预览分栏。先别上过重的富文本:博客作者通常能接受 Markdown,复杂度越低后台越不容易坏。
同源反代与环境变量
生产浏览器应只看到:
https://域名/
https://域名/api/...
https://域名/uploads/...
本地开发可用 Next rewrites 把 /api 转到本机后端,尽量与生产路径一致,减少「本地专属 bug」。切换 HTTPS 域名后,记得重建前端,让服务端/客户端拼 URL 的协议一致。
验收清单
- 禁用 JS 时,首页是否仍能看到文章标题(SSR 生效)
- 详情页标题是否进了
<title> - 未登录访问
/admin/articles是否被拦到登录 - 发布一篇短文后,首页是否在 revalidate 窗口内出现(或手动刷新策略符合预期)
- 上传封面后详情页图片是否同源可开、无混合内容
小结
Next.js 实战的关键切分是:公开页服务端读、管理页客户端写、Markdown 安全渲染、上传与表单分步、API 基址分内外。把这些钉住后,皮肤与动效可以慢慢打磨;反过来先堆组件库却理不清数据从哪来,站点会长期处在「能演示不能养」的状态。