成为全栈·Next.js 网站前台篇·文章列表:服务端首屏与客户端持续加载怎样协作
成为全栈·Next.js 网站前台篇·文章列表服务端首屏与客户端持续加载怎样协作首屏内容适合由服务端直接交付继续加载需要浏览器管理交互状态。两者合在一起时真正难的是让第 2 页从第 1 页的下一步开始而不是重新发明一份列表。前言文章列表看起来是前端最熟悉的页面之一请求第 1 页循环渲染点击加载更多时请求第 2 页。可当第 1 页由 Server Component 获取后续页由 React Query 管理时列表同时有了两个执行环境。我们遇到的第一个问题不是请求失败而是登录状态恢复时清空整个 QueryClient。这个操作原本是为了防止上一个账号的私有数据残留却把正在展示的公开文章流也一起清掉。首屏已经渲染出来客户端接管后却突然回到加载状态。这个故障说明服务端首屏和客户端续页不只是两段代码它们共同管理一份用户正在阅读的数据。第 1 页是客户端查询的初始事实首页 Server Component 先请求最新 10 篇文章再传给Feedconst page await listArticles({ pageSize: 10, sort: -publishedAt }) return Feed initial{page} categories{tree} /Feed将这份结果作为“全部分类”查询的initialDatauseInfiniteQuery({queryKey:[public-feed,category],initialPageParam:1,initialData:category?undefined:{pages:[initial],pageParams:[1]},queryFn:({pageParam})request(/articles,{skipAuth:true,skipRefresh:true,query:{page:pageParam,pageSize:10,sort:-publishedAt,category},}),})这样 hydration 完成后浏览器不会立即再请求一次第 1 页来替换同样的内容。对服务端和客户端来说第 1 页是同一份初始事实客户端从它计算第 2 页。但切换到某个分类时不能继续使用“全部文章”的首页数据。queryKey包含 category分类非空时initialData变为undefinedReact Query 会为新键请求对应的第 1 页。否则切换页签时会短暂显示错误分类的文章。公开列表不应被会话恢复误伤公开文章不依赖当前账号。它的 query key 不应在登录、退出或恢复会话时被当作私有数据清理。当初直接调用queryClient.clear()虽然简单它实际上宣布了“所有客户端服务端状态都属于当前账号”。这和项目的数据所有权不符。更稳妥的方式是让会话切换只失效或移除me、favorites、notifications等私有 key保留public-feed、公开标签与文章摘要。这类错误之所以难发现是因为登录页和文章列表单独测都正常。只有“在首页滚动文章流同时恢复会话”这条组合路径才会暴露数据所有权判断错误。页码分页不是一份不变的快照文章 API 使用页码与 pageSize。当用户看完第 1 页之前如果后台又发布了新文章原第 1 页的最后一篇可能被挤到第 2 页。客户端接着请求第 2 页时就会再次收到这篇文章。因此展示前要按稳定 id 去重constitemsArray.from(newMap(pages.flatMap((page)page.list).map((article)[article.id,article]),).values(),)这段逻辑可以避免同一篇文章在页面里出现两次却无法保证零遗漏。若文章在分页间向前移动某篇还是可能在用户获取续页前跨过了页边界。要得到稳定快照接口需要游标、快照时间或稳定排序键支持不能由前端去重假装完成。所以本项目的承诺很明确防止重复展示不承诺动态发布过程中的绝对无遗漏。对博客文章流这个取舍可以接受对账单、订单和审计记录就应该改用更稳定的分页契约。自动加载要防止并发重入IntersectionObserver的回调可能在哨兵元素停留在视口时多次触发。isFetchingNextPage阻止前一页未返回时再发一次hasNextPage阻止越过最后一页query.isError让失败后的恢复交给明确按钮count 3则限制自动追加批次。这些条件不是“多写几个 if 更保险”而是四个独立不变量同一时刻只追加一页不请求不存在的下一页错误后不自动风暴重试页脚不被无限推走。下一页的计算也必须只相信服务端返回的分页信息getNextPageParam:(lastPage)lastPage.pagelastPage.pages?lastPage.page1:undefined不能用“这一页刚好有 10 条”推断还有下一页。总数恰好是 10 的倍数时它会多发一次服务端过滤或删除数据时也可能产生误判。观察器需要随查询状态正确建立与清理useEffect(() { const element sentinel.current if (!element || !hasNextPage || isFetchingNextPage || query.isError || autoPages 2) return const observer new IntersectionObserver(([entry]) { if (entry.isIntersecting) void fetchNextPage() }, { rootMargin: 240px }) observer.observe(element) return () observer.disconnect() }, [hasNextPage, isFetchingNextPage, query.isError, autoPages, fetchNextPage])rootMargin让请求在哨兵真正进入视口前启动减少读者看到加载空档的概率清理函数则防止分类切换后旧观察器继续触发。追加失败不应该删掉已读内容列表首次请求失败和第 3 页追加失败恢复策略并不相同。前者没有可展示内容重试时应该refetch()后者已经有两页可阅读内容页面应该保留它们只对失败的下一页再次调用fetchNextPage()。Failure retry{() { void (query.isFetchNextPageError ? fetchNextPage() : query.refetch()) }} /这条分支保护的是用户已经获得的阅读上下文。如果追加失败就把整个列表换成错误页用户不仅没拿到新内容连原来看到的也失去了。全部文章页为什么仍然保留传统分页首页文章流用于发现持续加载能降低每次点击翻页的中断感。全部文章页则用于定位用户可能要分享第 4 页、切换排序、返回原来位置搜索引擎也需要可追踪链接。因此/articles采用 Server Component 读取 URL 里的 page、sort 和 category渲染普通上下页链接。首页文章流底部也保留“按页浏览全部文章”入口。两种交互服务于不同任务没有必要为了界面统一二选一。nav aria-label文章分页 {page 1 Link href{withPage(page - 1)}上一页/Link} {visiblePages.map((number) ( Link key{number} href{withPage(number)} aria-current{number page ? page : undefined} {number} /Link ))} {page pages Link href{withPage(page 1)}下一页/Link} /nav服务端与客户端各自维护什么责任服务端首屏客户端续页第 1 页文章获取并写入 HTML作为initialData接管SEO 与无脚本可读负责不覆盖分类切换提供分类树query key 隔离并重新请求第 2 页以后不处理fetchNextPage()跨页重复无法预知后续变化按稳定 id 去重追加错误不处理保留已有页重试失败页验收时要覆盖“过程中发生变化”constscenarios[首屏水合后不重复请求第 1 页,第 2 页请求期间观察器重复触发,加载第 2 页前后台发布一篇新文章,第 3 页返回 500 后点击重试,自动追加两批后手动加载并抵达 Footer,会话从游客恢复为会员时公开列表仍保留,]静态数据库只能验证正常分页真正容易出错的是请求期间的发布、登录恢复与失败重试。把这些过程固定为回归清单才符合混合列表的实际风险。适用边界服务端首屏加客户端续页很适合公开内容流、商品列表和社区时间线。如果列表对绝对无重无漏有强要求需要先升级后端分页契约而不是在客户端继续加更多 Map 和补偿请求。如果列表数量很少或用户需要精确跳页传统分页仍然更简单。持续加载并不是更现代的默认答案它只是某种阅读任务的交互选项。小结这个列表的核心不是 React Query 或 IntersectionObserver而是服务端与浏览器对同一份数据达成了协议第 1 页是初始事实查询键表达筛选身份id 用于跨页去重追加失败不删除已有内容。各位看官做混合列表时先别急着写“加载更多”。先回答三个问题第 1 页由谁交付客户端用什么 key 接管数据在分页间移动时你能保证什么。延伸阅读内容门户首页多级分类、标签与 URLTanStack Query 不只是缓存失效、派生与失败恢复如果这篇文章对你有帮助欢迎订阅我的 CSDN 专栏「成为全栈」 专栏地址https://blog.csdn.net/fungleo/category_13204651.html 本系列配套代码仓库https://github.com/fengcms/become-a-full-stack-developer

相关新闻

【Redis初阶】(篇二)《一文吃透 Redis:特性、应用场景、版本演进与安装配置全解析》

【Redis初阶】(篇二)《一文吃透 Redis:特性、应用场景、版本演进与安装配置全解析》

一.了解 Redis Redis 官网:Redis - Real-time data for agents & apps Redis(Remote Dictionary Server)是一个开源的、基于内存的键值对(Key-Value)NoSQL 数据库。 在数据结构上,Redis 的 key 永远是…

2026/9/30 22:29:30 阅读更多 →
工业配电开关三大核心指标:电气参数、公差范围与机械寿命协同解析

工业配电开关三大核心指标:电气参数、公差范围与机械寿命协同解析

1. 工业级配电开关控制设备不是“能通电就行”的玩具——它是一套精密协同的机电系统很多人第一次接触工业级配电开关控制设备时,下意识会把它和家里墙上那个咔哒一声就跳闸的空气开关画等号。这种认知偏差,在项目现场已经酿成过三次非计划停机事故&…

2026/9/30 22:29:30 阅读更多 →
化学智能体的闭环时刻:从自然语言到机器人实验,走通了哪条路

化学智能体的闭环时刻:从自然语言到机器人实验,走通了哪条路

一个化学工作者用自然语言说出"帮我做这组样品的梯度稀释",机器人液体工作站自己产出可执行协议文件,中途发现计量矛盾还会自己改——这个场景在 2026 年之前属于演示视频,现在有了可量化的一致性证据。美国太平洋西北国家实验室&a…

2026/9/30 22:29:30 阅读更多 →

最新新闻

STM32按键消抖本质:GPIO输入读到的不是电平,而是时序采样快照

STM32按键消抖本质:GPIO输入读到的不是电平,而是时序采样快照

1. 这不是“读高电平还是低电平”的简单问题你把按键焊到STM32的PA0引脚上,写好初始化代码,while(1)里用HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)轮询——结果发现:按键明明没按,串口却疯狂打印“KEY PRESSED”;或者按下去…

2026/9/30 23:01:59 阅读更多 →
OpenClaw 本地部署 Windows 实操:用 TaoToken 统一 Key 打通自动化配置

OpenClaw 本地部署 Windows 实操:用 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/9/30 23:00:58 阅读更多 →
OpenClaw 本地优先架构拆解:从本地网关到混合推理的配置骨架与验证路径

OpenClaw 本地优先架构拆解:从本地网关到混合推理的配置骨架与验证路径

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

2026/9/30 22:59:57 阅读更多 →
Hermes Agent 记忆矩阵拆解:MEMORY.md 文件、Hindsight 向量库与 SQLite 会话状态的三层协同与冲突

Hermes Agent 记忆矩阵拆解:MEMORY.md 文件、Hindsight 向量库与 SQLite 会话状态的三层协同与冲突

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

2026/9/30 22:59:57 阅读更多 →
嵌入式驱动量产工程化:从能跑到敢用的跨越

嵌入式驱动量产工程化:从能跑到敢用的跨越

1. “能跑”和“会崩”之间,隔着整整一个量产工程体系你写完一个GPIO驱动,烧进板子,按下按键,LED亮了——恭喜,你完成了“能跑”阶段。你把SPI Flash驱动加进RTOS固件,读写测试循环100次全通过——好&#…

2026/9/30 22:59:57 阅读更多 →
一文讲清楚 Claude Code Skills、MCP工具,看完小白变大神!(附保姆级教程)

一文讲清楚 Claude Code Skills、MCP工具,看完小白变大神!(附保姆级教程)

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

2026/9/30 22:58:57 阅读更多 →

日新闻

Base64 图片头部特征识别:从文件头到格式判断的完整指南

Base64 图片头部特征识别:从文件头到格式判断的完整指南

1. 项目概述:为什么说看懂 base64 图片头部是基本功这几年跟 base64 打交道的机会越来越多,后端接口返回图片、前端渲染验证码、小程序里存小图、还有一些老系统导出报表,动不动就给你一段长到怀疑人生的 base64 字符串。很多人拿到字符串就直…

2026/9/30 0:00:35 阅读更多 →
Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

简介:本资源是一份面向Java初学者与课程设计学生的公交站牌广告灯箱管理系统毕业设计文档,聚焦城市公共广告资源信息化管理痛点,提供从需求分析到技术实现的完整方案。文档采用标准学术论文结构,含摘要、英文摘要、目录及五章正文…

2026/9/30 0:00:35 阅读更多 →
用 Redis Lua 构建大模型 API 多租户原子配额治理体系

用 Redis Lua 构建大模型 API 多租户原子配额治理体系

我去年年底接了一个内部 AI 平台的治理需求,背景很直接:公司把 DeepSeek、MiniMax 这类大模型 API 统一封装成内部网关,开放给几个业务团队用。结果第一个月账单出来,额度直接超了 4 倍。仔细查日志,发现原因并不复杂—…

2026/9/30 0:00:35 阅读更多 →

周新闻

如何划分训练/验证集: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 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/29 19:29:29 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/29 5:58:00 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/30 15:27:04 阅读更多 →