实战:Next.js 博客的列表 SSR、Markdown 详情与后台表单

Article

实战:Next.js 博客的列表 SSR、Markdown 详情与后台表单

管理员技术分享69 阅读

实战: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。

保存逻辑建议:

  1. 点保存 → 按钮 disabled,防止双戳
  2. POST/PUT /api/admin/articles 带 Bearer
  3. 成功后跳编辑页或列表,并提示成功
  4. 401 则清 Token 去登录

封面上传单独走 multipart/form-data。上传成功后把返回的 URL 填进表单的 cover_image,再随文章一起保存。不要假设「选了文件就等于封面已绑定」——分两步更清晰,失败也好提示。

Markdown 编辑可用 textarea + 预览分栏。先别上过重的富文本:博客作者通常能接受 Markdown,复杂度越低后台越不容易坏。

同源反代与环境变量

生产浏览器应只看到:

https://域名/
https://域名/api/...
https://域名/uploads/...

本地开发可用 Next rewrites 把 /api 转到本机后端,尽量与生产路径一致,减少「本地专属 bug」。切换 HTTPS 域名后,记得重建前端,让服务端/客户端拼 URL 的协议一致。

验收清单

  1. 禁用 JS 时,首页是否仍能看到文章标题(SSR 生效)
  2. 详情页标题是否进了 <title>
  3. 未登录访问 /admin/articles 是否被拦到登录
  4. 发布一篇短文后,首页是否在 revalidate 窗口内出现(或手动刷新策略符合预期)
  5. 上传封面后详情页图片是否同源可开、无混合内容

小结

Next.js 实战的关键切分是:公开页服务端读、管理页客户端写、Markdown 安全渲染、上传与表单分步、API 基址分内外。把这些钉住后,皮肤与动效可以慢慢打磨;反过来先堆组件库却理不清数据从哪来,站点会长期处在「能演示不能养」的状态。

评论

暂无评论,来聊聊这篇文章吧。