成为全栈·Next.js 网站前台篇·文章阅读辅助:上下篇、面包屑、相关文章与阅读记录
成为全栈·Next.js 网站前台篇·文章阅读辅助上下篇、面包屑、相关文章与阅读记录正文是文章详情页的主任务面包屑、上下篇、相关文章和阅读记录都只能帮助阅读。任何一个辅助接口失败都不应该把正文一起拖进错误页。前言各位看官一篇文章能打开只说明“内容展示”完成了。读者还需要知道自己在哪里、上一篇和下一篇是什么、读完后还能看什么会员也希望下次回来时找到阅读历史。这些功能看起来都围绕文章数据语义却完全不同面包屑来自分类祖先链上下篇依赖发布时间排序相关文章来自后端关联算法阅读次数属于公开统计个人阅读记录则属于当前会员。如果把它们塞进一次大请求任何一个次要模块失败都会拖垮正文如果全部交给浏览器又会出现一片片加载闪烁。本文就拆开这组容易被称为“详情页周边”的真实边界。先给详情页数据排优先级数据是否为主任务失败后的页面行为数据所有权文章标题与正文是404 或进入错误边界公开内容分类面包屑否保留首页、文章两级公开导航上下篇否隐藏对应入口公开导航相关文章否不显示继续阅读区公开推荐阅读次数否不阻断阅读允许重试公开统计会员阅读记录否不提示全页错误允许下次重试私有数据这张表决定了代码里的错误处理。核心文章不能吞错后伪装成空对象辅助模块则可以在明确的局部边界内降级。核心文章与辅助请求分两步获取详情页先获得文章因为后续请求需要它的 id 与 categoryIdconst load cache(async (slug: string) getArticle(slug).catch((error) { if (error instanceof ApiError error.status 404) notFound() throw error }), ) const article await load((await params).slug)这里只把后端明确返回的 404 转成notFound()。超时、502 和响应格式异常继续抛出让路由错误边界表达“系统暂时不可用”。有了文章身份以后再并发加载彼此独立的辅助信息const [adjacent, related, tree, allTags, breadcrumb] await Promise.all([ getArticleAdjacent(article.id).catch(() null), getArticleRelated(article.id, 4).catch(() []), categories().catch(() []), tags().catch(() []), article.categoryId ? getCategoryBreadcrumb(article.categoryId).catch(() []) : Promise.resolve([]), ])这里的catch不是“错误都不重要”而是各模块已经有清楚的缺席表现。相关文章失败返回空数组上下篇失败返回null分类树失败只会让分类标签缺席正文仍然成立。面包屑来自分类祖先链不是字符串切割分类 URL 中只有当前 slug无法靠/categories/frontend/react这样的路径切割推导祖先项目公开地址本来也不是嵌套分类路径。正确数据来自后端 breadcrumb 接口nav classNamebreadcrumb aria-label面包屑 Link href/首页/Link span//span Link href/articles文章/Link {breadcrumb.map((category) ( span key{category.id} { / } Link href{/categories/${category.slug}}{category.name}/Link /span ))} /nav祖先顺序由后端分类关系决定。即使分类被移动只要接口返回新的祖先链页面就不会继续展示旧层级。上下篇的“上”和“下”必须由接口定义“上一篇”可能表示发布时间更早也可能表示排序位置更靠前。如果前端分别请求publishedAt 当前时间和publishedAt 当前时间还要自行处理草稿、相同时间和稳定排序。当前项目把相邻关系交给后端前端只负责可访问的导航结构{adjacent ( nav classNameadjacent aria-label相邻文章 {adjacent.prev ? ( Link href{articleUrl(adjacent.prev)} small← 上一篇/small {adjacent.prev.title} /Link ) : span /} {adjacent.next ( Link href{articleUrl(adjacent.next)} small下一篇 →/small {adjacent.next.title} /Link )} /nav )}位于序列第一篇或最后一篇时一侧为空是正常边界不是错误。页面也不能为了对称编造一个返回首页的“下一篇”。相关文章是一种推荐结果不是前端数组过滤前端手里只有当前文章和少量摘要无法完整实现分类、标签、状态和排序规则。相关文章由/articles/{id}/related返回页面只限制展示数量{related.length 0 ( section aria-labelledbyrelated-title h2 idrelated-title继续阅读/h2 {related.map((item) ( div classNamerank key{item.id} Link href{articleUrl(item)}{item.title}/Link /div ))} /section )}当前接口返回的是规则推荐不应在文案里夸大为“猜你喜欢”。如果以后引入个性化算法还需要解释推荐依据、隐私与冷启动问题而不是只替换标题。阅读五秒后再记录比页面一打开就加一更诚实详情页面一挂载就上报阅读会把误点、预取和立即返回都算作有效阅读。当前实现等待五秒并在当前浏览器会话中按文章去重const viewed new Setnumber() useEffect(() { const timer setTimeout(() { if (viewed.has(id)) return viewed.add(id) void request(/articles/${id}/view, { method: POST, skipAuth: true, skipRefresh: true, }).catch(() viewed.delete(id)) }, 5000) return () clearTimeout(timer) }, [id])失败时从集合删除用户再次访问仍有重试机会。这个五秒阈值只是产品规则不代表读者一定读完更不能把viewCount写成“完读人数”。公开阅读次数与个人阅读历史不是同一份状态游客也可以增加公开阅读统计会员登录后还会写入个人历史。个人去重键必须包含用户身份const recorded new Setstring() const key ${user?.id}:${id} if (user !recorded.has(key)) { recorded.add(key) void reportReadingProgress(id).catch(() recorded.delete(key)) }若只按文章 id 去重A 账号读过文章后同一浏览器切换到 B 账号B 的历史将不会记录。身份是私有状态键的一部分这与 React Query 的私有缓存隔离是同一原则。这里也刻意没有用 localStorage 保存完整阅读清单。服务端已经为会员提供历史浏览器长期存储会留下额外隐私痕迹。内存集合只负责当前页面生命周期内的重复上报控制。一套覆盖降级与隐私的验收清单1. 正常文章面包屑、上下篇、相关文章均可到达 2. 第一篇与最后一篇缺失的一侧不生成假链接 3. adjacent 返回 500正文、评论和目录仍可使用 4. related 返回空数组不显示空的“继续阅读”标题 5. 停留不足五秒后离开不记录阅读 6. 上报失败后重新进入允许再次尝试 7. A 账号读完后切换 B 账号B 能写入自己的历史 8. 游客阅读不调用私有历史接口这组验证不能只看页面截图还要观察网络请求的时间、次数和用户身份。辅助模块的质量往往藏在“失败以后还剩什么”和“切换账号后写给谁”。适用边界五秒去重适合一般博客的轻量统计不适合课程进度、计费阅读或合规审计。后几类业务需要服务端事件、幂等键、可追溯时间和更明确的完成条件。相关文章和上下篇也依赖内容规模。只有几篇文章时可以不显示推荐区内容量变大后则需要观察推荐重复率、相关性与接口成本。小结文章详情页不是接口越多越完整而是主次关系越清楚越可靠。正文负责成立面包屑说明位置上下篇延续顺序相关文章提供发现阅读记录保留个人进度。真正重要的实现原则只有两条辅助模块各自降级私有状态始终带上身份。做到这两点详情页才能在部分服务失败时继续完成阅读任务。延伸阅读文章详情Markdown、代码高亮与目录多级分类、标签与 URL让内容导航既可读又可索引网站前台篇·文章列表服务端首屏与客户端持续加载怎样协作如果这篇文章对你有帮助欢迎订阅我的 CSDN 专栏「成为全栈」 专栏地址https://blog.csdn.net/fungleo/category_13204651.html 本系列配套代码仓库https://github.com/fengcms/become-a-full-stack-developer

相关新闻

Agent Harness 实战:从 1 个 Agent 到 20 个 Agent,AI 编码工程化脚手架怎么搭

Agent Harness 实战:从 1 个 Agent 到 20 个 Agent,AI 编码工程化脚手架怎么搭

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 14:32:52 阅读更多 →
哪些写作习惯容易让论文被判定为AI生成

哪些写作习惯容易让论文被判定为AI生成

论文被判定为AI生成,问题多半出在动笔阶段的习惯里,而不是提交前临时补的动作。这篇内容把容易留下机器痕迹的写作习惯拆成可对照的方向性判据,每条附上可执行的改写动作与对应入口。知学术AIPaperGPT 开放免费智能大纲与免费科研元素生成两项…

2026/10/1 14:32:52 阅读更多 →
长春汽车经济技术开发区环境好的养老院公司推荐——颐禾苑养老康复中心客户真实体验口碑汇总

长春汽车经济技术开发区环境好的养老院公司推荐——颐禾苑养老康复中心客户真实体验口碑汇总

长春汽开区环境好的养老院怎么选?颐禾苑真实客户口碑与实力全解析开篇一句话看清颐禾苑的核心价值在长春汽车经济技术开发区,如果要推荐一家环境好、口碑好、照护专业的养老康复机构,长春汽车经济技术开发区颐禾苑养老康复中心是许多家庭反复提及的名字…

2026/10/1 14:32:52 阅读更多 →

最新新闻

relsent-bert-zh-large(知更鸟)模型跨领域应用

relsent-bert-zh-large(知更鸟)模型跨领域应用

核心特点:同时输出【对话双方关系类型 情感极性 关系演变趋势】,区别于普通情感分析只判断正负,主打人际交互文本的多维理解,下面是可落地的其他领域场景:1. 智能客服 / 政企热线识别客户与客服之间:客户…

2026/10/1 15:55:29 阅读更多 →
2026年产业互联网商城开发哪家好:交易模式与集成能力全对比

2026年产业互联网商城开发哪家好:交易模式与集成能力全对比

2026年产业互联网商城开发哪家好:交易模式与集成能力全对比 阅读摘要 评测维度: 交易模式供应链协同系统集成部署控制权项目交付 Top Pick:万米商云官网:https://www.wanmi.com/联系电话:400-025-0992 其它上榜&#x…

2026/10/1 15:55:29 阅读更多 →
怎么用 AI 写商务邮件,不生硬、不翻译腔还像本人写的?

怎么用 AI 写商务邮件,不生硬、不翻译腔还像本人写的?

怎么用 AI 写商务邮件,不生硬、不翻译腔还像本人写的?很多人用 AI 写完邮件,自己都不想发:通篇“鉴于”“兹有”,客气得像份合同,要么一股中英翻译腔,一看就不是活人写的。问题不在 AI&#xff…

2026/10/1 15:55:29 阅读更多 →
销售怎么用 AI 做客户报价单?从模板到排版十分钟搞定

销售怎么用 AI 做客户报价单?从模板到排版十分钟搞定

销售怎么用 AI 做客户报价单?从模板到排版十分钟搞定报价单做得乱,客户第一反应是这家公司不专业;从零手搓表格又太耗时间。其实把 AI 用在搭结构、写条款、统一排版上,十分钟就能出一份规范报价单。这篇给完整流程、必备要素清单…

2026/10/1 15:55:29 阅读更多 →
硬件看门狗设计实战:从电路选型到安全认证

硬件看门狗设计实战:从电路选型到安全认证

1. 看门狗不是“软件补丁”,而是硬件级生命线很多人第一次听说“看门狗”,是在单片机开发课上被老师随口带过的一句:“程序跑飞了,就靠它拉一把。”——这话没错,但太轻描淡写。我做过七年的嵌入式系统可靠性设计&…

2026/10/1 15:55:29 阅读更多 →
Rust容器核心:Vec与HashMap从基础用法到性能优化实战

Rust容器核心:Vec与HashMap从基础用法到性能优化实战

Rust里有一对组合拳,几乎所有搞Rust开发的人都绕不过去:Vec和HashMap。不管你是写命令行工具、Web后端还是桌面应用,只要涉及批量数据,这两个类型就是最常用的容器。对刚入门的Rust开发者来说,Vec和HashMap不只是“存数…

2026/10/1 15:54:29 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →