本章定位
上一章,我们已经把第五阶段综合实战从“项目骨架搭建”推进到了“内容主线真正开始跑起来”的阶段。
你已经完成了这些非常关键的事情:
- 博客项目已经有了首页、文章列表页、关于页、联系页和基础布局。
- 本地文章数据已经开始接入项目。
- 首页已经能展示推荐文章和最近文章。
- 文章列表页已经能展示全部文章。
slug、文章数据和getPostBySlug这条主线已经准备好了。
也就是说,到现在为止,你的博客项目已经不是“空壳网站”了,而是已经开始有内容入口、有阅读入口、有站点感。
接下来,我们要继续推进一条非常关键的主线:
让用户真的点进一篇文章,并把这篇文章完整读下来。
这一章非常重要。
因为博客项目和很多工具型项目不一样,它有一个天然非常核心的页面:
文章详情页
如果没有详情页,用户只能:
- 看到首页入口
- 看到文章列表
- 看到标题和摘要
但还不能真正进入:
阅读
所以这一篇我们要解决的核心问题是:
- 动态路由参数到底在解决什么问题?
- 详情页怎样通过
slug找到对应文章? - 如果这篇文章不存在,页面应该怎么办?
- 文章标题、分类、日期、封面和正文,应该怎样组织到详情页里?
- 为什么正文第一版先用
string[]渲染就是一个很稳的方案?
你可以把这一章理解成:
第五阶段综合实战里,第一次让博客项目真正具备“可点击进入阅读”的主线篇。
本章学习目标
学完这一章后,你应该能做到:
- 理解为什么文章详情页是博客项目里最核心的阅读页面之一。
- 理解动态路由参数在博客详情页里的作用。
- 学会把路由地址、
slug和文章数据之间的关系真正串起来。 - 知道
src/app/posts/[slug]/page.tsx在当前项目里扮演什么角色。 - 学会在详情页中通过
getPostBySlug查找对应文章。 - 理解为什么文章不存在时更适合走 404 流程,而不是默默显示空白页。
- 学会使用
notFound()处理不存在文章的场景。 - 学会把标题、分类、日期、封面、正文段落组织成第一版详情页结构。
- 理解为什么正文第一版先用
string[]渲染是一个适合当前阶段的折中方案。 - 知道
generateStaticParams在本地内容型博客里可以先怎么理解。 - 理解详情页和列表页的职责区别。
- 为下一篇继续进入关于页、联系页与表单交互做好准备。
一、这一篇到底要打通哪条阅读主线
上一章,我们已经把:
- 数据
- 首页
- 列表页
这条主线接起来了。
这一章要接的,是博客项目里另一条同样关键的主线:
阅读主线
它可以先被理解成这样一条路径:
- 用户在首页或列表页看到某篇文章。
- 用户点击这篇文章的标题或“阅读全文”入口。
- 浏览器进入这篇文章的详情地址。
- 页面根据
slug找到对应文章。 - 页面把这篇文章的完整内容渲染出来。
这条主线一旦成立,博客项目就会从:
有内容入口的网站
继续走向:
有完整阅读路径的网站
二、为什么文章详情页在博客项目里这么关键
很多内容型网站真正最核心的价值,往往不在首页。
首页更像:
入口
文章列表页更像:
总览
而文章详情页更像:
真正承载内容价值的地方
1. 列表页负责“让你看见”
它会告诉用户:
- 有哪些内容
- 这些内容大概是什么
- 哪篇文章值得点进去
2. 详情页负责“让你读进去”
它会真正承载:
- 标题
- 摘要
- 正文
- 阅读节奏
- 深入理解
3. 为什么博客项目不能没有它
因为如果没有详情页,博客项目很容易停留在:
一组文章卡片展示页
而不是一个真正的内容网站。
三、动态路由参数到底在解决什么问题
这一节非常重要。
很多初学者第一次看到:
[slug]时,会觉得它只是个“文件夹写法”。
其实它解决的是一个很实在的问题:
多篇文章如何共用一套详情页结构,但又显示不同内容?
1. 如果不用动态路由会怎样
你可能就要为每篇文章都写一份页面文件。
例如:
react-basic/page.tsxhtml-semantic/page.tsxtypescript-generics/page.tsx
这显然既重复,也很难维护。
2. 动态路由在做什么
它的核心作用是:
用一个“变量位置”去匹配不同文章地址。
例如:
/posts/react-basic /posts/html-semantic-basics /posts/typescript-generics这些地址虽然最后一段不同,但它们都可以共用同一套详情页模板。
3. 当前阶段最值得先记住什么
你可以先记住一句话:
动态路由参数解决的不是“怎么写目录更炫”,而是“怎么让一类内容共用一套页面结构”。
四、先把目录结构和地址关系再看一遍
当前阶段博客详情页最关键的目录关系,是这一段:
src/app/ └── posts/ ├── page.tsx └── [slug]/ └── page.tsx它可以先这样理解:
src/app/posts/page.tsx对应文章列表页/postssrc/app/posts/[slug]/page.tsx对应文章详情页/posts/某篇文章标识
例如:
/posts/html-semantic-basics /posts/react-state-basics /posts/nextjs-routing-introduction1. 为什么这个结构很适合当前博客项目
因为它正好对应了两类页面:
- 一组文章的总览页
- 单篇文章的详情页
2. 这和上一章的数据设计为什么刚好能接上
因为我们上一章已经给每篇文章准备了:
slug所以现在路由层和数据层已经开始自然对上了。
五、params.slug可以先怎么理解
当用户访问:
/posts/html-semantic-basics时,详情页可以拿到这样一份参数:
params.slug==="html-semantic-basics"你可以先把它理解成:
当前 URL 里,属于文章标识的那一段内容。
1. 为什么这个值非常关键
因为它就是详情页去查文章的钥匙。
详情页不是随便显示一篇文章,而是要根据:
当前访问的是哪一个
slug
来决定显示哪一篇。
2. 不同版本的 Next.js 看起来会不会略有差异
会有可能。
例如某些版本在类型提示上会让params的写法稍微不一样。
但当前阶段最重要的核心理解并不会变:
详情页会拿到一个
slug,再用这个slug去找到对应文章。
只要这条主线你抓住了,后面版本差异就不会太难适应。
六、详情页第一版至少应该展示哪些内容
在开始写页面前,先别急着立刻敲代码。
先想清楚:
第一版详情页最值得先展示什么。
当前阶段我建议至少先包含下面这些内容:
- 返回文章列表入口
- 文章分类和发布日期
- 文章标题
- 文章摘要
- 文章封面图
- 文章正文段落
1. 为什么这几个部分已经很够用了
因为它们已经能撑起一条完整阅读主线:
- 用户知道自己在哪
- 用户知道这篇文章是什么
- 用户能看到正文
2. 当前阶段先不必急着加什么
例如:
- 评论
- 目录锚点
- 上一篇 / 下一篇
- 阅读时长
- 代码高亮
这些后面都可以扩展。
现在更重要的是:
阅读主线先成立。
七、为什么getPostBySlug是这一篇的核心桥梁
上一章我们已经在:
src/lib/posts.ts准备了这个函数:
exportfunctiongetPostBySlug(slug:string){returnblogPostList.find(function(postItem){returnpostItem.slug===slug;});}这一章它会成为真正的关键桥梁。
为什么?
因为它刚好把两层内容连起来了:
- 路由层拿到的
slug - 数据层维护的文章数组
1. 如果没有这个函数会怎样
页面里就会开始自己写很多:
find- 判空
- 字段处理
这会让页面越来越像数据脚本。
2. 现在把它单独抽出来的价值是什么
它让详情页可以更专注地表达:
当前页面怎样展示当前文章
而不是一边写页面,一边到处散落数据查找逻辑。
八、先从最小的详情页查找逻辑开始
在真正写完整详情页之前,我们可以先把最小逻辑跑通。
当前页面文件是:
src/app/posts/[slug]/page.tsx它最小可以先写成这样:
import { getPostBySlug } from "@/lib/posts"; interface PostDetailPageProps { params: { slug: string; }; } export default function PostDetailPage({ params }: PostDetailPageProps) { const post = getPostBySlug(params.slug); return <section>{post?.title}</section>; }1. 这一段代码在做什么
它只做了两件事:
- 拿到当前访问地址里的
slug - 用
slug去文章数组里找对应内容
2. 为什么要先跑最小逻辑
因为这样你可以先确认:
路由参数到底有没有真的和文章数据接上。
这一步跑通以后,再补页面结构会稳很多。
九、文章找不到时,为什么更适合 404,而不是普通空状态
这一点非常值得现在就建立。
列表页和详情页虽然都可能“没有内容”,但它们表达的问题通常不同。
1. 列表页为空,更适合空状态
因为它往往在表达:
当前还没有任何文章,或者当前筛选结果为空。
2. 详情页找不到文章,更适合 404
因为它往往在表达:
用户访问了一个本来应该存在、但当前并不存在的具体地址。
3. 为什么这两者不要混为一谈
因为它们背后的场景完全不同。
当前阶段更稳的理解是:
- 列表页“没有一组内容”更像空状态
- 详情页“没有这一个具体对象”更像 404
十、先用notFound()处理不存在文章
Next.js 提供了一个非常适合当前场景的能力:
notFound()你可以先把它理解成:
当当前详情页对应的文章不存在时,直接交给 Next.js 走 404 页面流程。
一个很典型的写法是:
import { notFound } from "next/navigation"; import { getPostBySlug } from "@/lib/posts"; interface PostDetailPageProps { params: { slug: string; }; } export default function PostDetailPage({ params }: PostDetailPageProps) { const post = getPostBySlug(params.slug); if (!post) { notFound(); } return <section>{post.title}</section>; }1. 为什么这种处理方式很合适
因为它更符合详情页的语义:
这不是“当前没有内容可以展示”,而是“你访问的这篇文章不存在”。
2. 后面还能不能做更友好的 404 页面
当然可以。
后面你完全可以继续补:
src/app/not-found.tsx让 404 页面更友好。
但当前阶段先把:
找不到文章 -> 正确走 404
这条主线跑通,就已经很有价值了。
十一、第一版详情页完整结构可以怎么写
当最小查找逻辑和 404 主线都成立以后,就可以开始把详情页真正写完整了。
一个适合当前阶段的第一版详情页,可以先写成下面这样:
import Image from "next/image"; import Link from "next/link"; import { notFound } from "next/navigation"; import { getPostBySlug, getSortedPostList } from "@/lib/posts"; interface PostDetailPageProps { params: { slug: string; }; } // 这里提前把所有本地文章的 slug 告诉 Next.js,方便当前阶段的内容型路由更稳地工作 export function generateStaticParams() { return getSortedPostList().map(function (postItem) { return { slug: postItem.slug }; }); } export default function PostDetailPage({ params }: PostDetailPageProps) { const post = getPostBySlug(params.slug); // 当前 slug 找不到文章时,直接交给 Next.js 走 404 流程 if (!post) { notFound(); } return ( <article className="site-shell post-detail-page"> <Link href="/posts" className="back-link"> 返回文章列表 </Link> <p className="post-detail-meta"> {post.category} · {post.publishedAt} </p> <h1>{post.title}</h1> <p className="post-detail-summary">{post.summary}</p> <Image src={post.coverImage} alt={`${post.title} 的封面图`} width={1200} height={630} /> <div className="post-content"> {/* 这里把正文数组稳定渲染成段落列表,先让阅读主线跑通 */} {post.content.map(function (paragraph, index) { return <p key={`${post.slug}-${index}`}>{paragraph}</p>; })} </div> </article> ); }1. 为什么这份结构已经很够用了
因为它已经把当前阶段最关键的阅读元素都接起来了:
- 返回入口
- 元信息
- 标题
- 摘要
- 封面图
- 正文内容
2. 这一整段代码最关键的主线是什么
你可以把它压缩理解成:
路由参数拿到
slug-> 用slug找文章 -> 找不到就 404 -> 找到了就渲染整篇文章
这就是本章最核心的主线。
十二、为什么generateStaticParams值得现在就加上
这一节对当前这种“本地内容型博客项目”来说很有价值。
你可以先把:
generateStaticParams理解成:
先把所有已知文章的
slug告诉 Next.js。
1. 这在当前项目里为什么特别自然
因为我们现在的文章数据是本地可知的。
也就是说:
- 有哪些文章
- 每篇文章的
slug是什么
在项目构建时就已经能知道。
2. 它和详情页是什么关系
它们的关系很简单:
generateStaticParams()先给出一组可用slug- 详情页再根据这些
slug渲染不同页面
3. 当前阶段如果你一时没完全吃透怎么办
也完全正常。
你先记住这句就够了:
对本地内容型页面来说,
generateStaticParams可以帮助 Next.js 提前知道会有哪些详情页地址。
十三、为什么正文第一版先用string[]渲染就够用了
这一点我们上一章其实已经埋过伏笔。
但到了详情页,这件事会真正落地。
1. 为什么它对当前阶段特别合适
因为它足够简单,同时又足够有用。
你可以很自然地做这件事:
一个字符串数组项,渲染成一个正文段落。
例如:
content:["HTML 标签不只是长得不一样,它们本身就在表达结构。","当你理解了结构,页面和内容的关系就会更清楚。"]然后详情页里直接:
{post.content.map(function (paragraph) { return <p>{paragraph}</p>; })}2. 这样做的核心价值是什么
它能让你先把:
- 正文来源
- 正文渲染
- 正文结构
这三件事跑顺。
3. 现在为什么还不急着上 Markdown
不是 Markdown 不重要,而是当前阶段更重要的是:
先把阅读主线跑通。
如果你一上来就把重点切到 Markdown 解析、插件配置、样式适配,反而容易把详情页主线打散。
十四、详情页里的元信息在解决什么问题
元信息虽然看起来只是几行小字,但它对阅读体验很重要。
例如:
- 分类
- 日期
这类内容会让用户更快知道:
- 这篇文章大概属于哪个主题
- 它是什么时候发布的
1. 为什么这不是装饰
因为内容型页面里,读者经常会先判断:
- 主题是否相关
- 时间是否新近
这些信息能帮助用户快速建立阅读预期。
2. 当前阶段元信息先做到什么程度就够了
先做到:
- 分类
- 日期
这两个基础项就已经很有价值。
后面可以再逐步扩展:
- 阅读时长
- 标签
- 作者
十五、返回列表入口为什么现在就值得加上
很多初学者写详情页时,会先把返回入口忘掉。
但从真实阅读路径看,这个入口非常自然。
1. 它解决什么问题
它解决的是:
用户看完一篇文章后,怎么方便地回到文章总览页。
2. 为什么它很适合现在就加
因为它既简单,又很符合真实使用路径。
你只要先在详情页顶部加一个:
<Link href="/posts">返回文章列表</Link>阅读路径就会顺很多。
3. 当前阶段先不用把它做得太花
不需要一开始就:
- 带图标
- 带复杂悬停动画
- 带面包屑导航
先把阅读路径理顺最重要。
十六、详情页和列表页的职责为什么不能混掉
这一点在博客项目里非常关键。
1. 列表页更像什么
更像:
内容总览页
它的重点是:
- 看有哪些内容
- 快速浏览摘要
- 决定下一步点哪篇
2. 详情页更像什么
更像:
内容沉浸页
它的重点是:
- 读完整内容
- 理解一篇文章的展开逻辑
- 获得完整信息
3. 为什么这两个角色一定要分开
因为如果详情页还像列表页一样只讲摘要,或者列表页像详情页一样塞满正文,整个阅读结构就会混乱。
当前阶段更稳的理解是:
- 列表页负责帮你选
- 详情页负责帮你读
十七、第一版详情页样式应该先追求什么
这章的重点虽然不是样式,但详情页一旦开始显示正文,页面节奏就已经开始变重要了。
当前阶段我建议第一版详情页样式先追求三件事:
- 文字容易读
- 信息层级清楚
- 内容宽度舒服
1. 什么叫“文字容易读”
例如:
- 段落不要太挤
- 行高不要太小
- 正文宽度不要过长
2. 什么叫“层级清楚”
例如:
- 标题最突出
- 元信息更轻
- 摘要和正文有明确区分
3. 当前阶段不必急着做到什么程度
不必一开始就追求:
- 杂志级排版
- 很复杂的目录锚点
- 丰富的富文本样式系统
现在先让:
阅读舒服、主次分明
就已经很有价值。
十八、这一章先不急着做哪些细节
虽然详情页现在已经开始真正成立,但依然有很多内容可以先放到后面。
例如:
- Markdown / MDX 渲染
- 代码高亮
- 目录导航
- 上一篇 / 下一篇
- 相关文章推荐
- 评论系统
1. 为什么现在先不急
因为这一章真正要解决的是:
详情页能不能根据
slug稳定找到文章,并把正文稳稳渲染出来。
只要这条主线成立了,后面的增强能力就都有了落点。
2. 当前阶段最稳的节奏是什么
先跑通:
- 动态路由参数
- 单篇文章查找
- 404 处理
- 正文渲染
再继续往上叠:
- 样式优化
- 阅读体验增强
- 内容管理升级
十九、开发后怎么验证这一篇有没有搭好
这一篇完成后,非常适合做一轮小验证。
你可以按下面顺序检查。
1. 先检查一篇存在的文章
例如访问:
/posts/html-semantic-basics确认:
- 页面能正常打开
- 标题和摘要正确显示
- 正文段落正确显示
- 返回列表入口可点击
2. 再检查另一篇存在的文章
例如访问:
/posts/react-state-basics确认:
页面内容确实会随着
slug变化。
3. 再检查一个不存在的地址
例如访问:
/posts/not-exist-post确认:
页面不是静默空白,而是走到了 404 流程。
4. 为什么这种验证特别重要
因为它能直接帮你确认:
详情页最关键的“存在时可读、不存在时有反馈”两条主线,是否都成立了。
二十、这一章最容易踩的几个坑
这一节建议你认真看。
因为详情页阶段很容易出现一些“现在能跑、后面很难改”的问题。
1. 坑一:详情页不读slug,而是直接写死某篇文章
这样页面看起来能打开,但其实动态路由根本没有真正成立。
2. 坑二:找不到文章时什么都不处理
这样用户访问错误地址时,页面可能是:
- 空白
- 报错
- 显示半截结构
这都不够稳。
3. 坑三:正文和摘要混成一层
这样详情页就会很像放大版列表卡片,而不是一个真正的阅读页。
4. 坑四:正文数据结构还没稳,就急着做复杂富文本系统
这会让你过早把注意力从:
阅读主线
转移到:
内容系统技术细节
5. 坑五:详情页没有返回路径
这样用户看完一篇文章后,路径会有点断。
6. 坑六:不同文章的slug命名不统一
这样后面数据、图片、链接和路由很容易对不上。
二十一、本章实践练习
这一章的练习重点,是把“动态路由详情页”这条主线真正跑顺。
1. 练习 1:完成详情页动态路由页面
请你完成:
src/app/posts/[slug]/page.tsx并做到:
- 能读取
params.slug - 能通过
getPostBySlug找到对应文章 - 能把文章标题渲染出来
这个练习的重点是:
让路由参数和数据查找真正接上。
2. 练习 2:给不存在的文章加上 404 处理
请你在详情页里补上:
notFound()并测试一个不存在的地址。
这个练习会帮助你真正理解:
详情页找不到内容时,更适合走 404 语义。
3. 练习 3:把正文段落真正渲染出来
请你让详情页至少展示:
- 标题
- 分类与日期
- 摘要
- 正文段落列表
这个练习的重点是:
让详情页真正开始承担“阅读页”的角色。
4. 练习 4:加上返回列表入口
请你为详情页补一个:
<Link href="/posts">返回文章列表</Link>这个练习会帮助你真正提升:
阅读路径的完整度。
二十二、学习重点提示
这一章请你重点记住下面这些话:
- 文章详情页是博客项目真正承载内容价值的关键页面之一。
- 动态路由参数的核心价值,是让一类内容共用一套详情页结构。
slug是路由层和数据层之间最重要的连接点。- 列表页更像内容总览页,详情页更像内容沉浸页,两者职责不能混掉。
- 详情页文章不存在时,更适合走 404 语义,而不是普通空状态。
notFound()是当前阶段处理不存在详情内容的很稳方案。- 正文第一版先用
string[]渲染,是一个简单但有效的起点。 - 当前阶段最重要的不是立刻做复杂内容系统,而是先把详情页阅读主线跑通。
如果你只记一句话,请记住:
第五阶段综合实战真正开始具备“可阅读性”的那一刻,往往不是你加了多少高级功能,而是你先把
slug、文章查找、404 处理和正文渲染这条详情页主线稳定地跑通了。
二十三、本章小结
这一章,我们正式把第五阶段综合实战从“首页和列表页内容接入”推进到了“详情阅读主线真正成立”的阶段。
你已经理解了:
- 为什么文章详情页在博客项目里这么关键
- 动态路由参数到底在解决什么问题
params.slug、文章数据和getPostBySlug之间怎样真正串起来- 为什么文章不存在时更适合用 404 处理
notFound()在当前阶段可以怎样使用- 第一版详情页最值得先展示哪些内容
- 为什么正文第一版先用
string[]渲染就已经很有价值 generateStaticParams在本地内容型项目里可以先怎样理解
更重要的是,你开始真正建立一种非常关键的阅读型项目意识:
内容网站真正成立,不只是因为有内容入口,还因为它能把用户顺利带进一篇文章、稳稳读完整篇文章。
这一步非常关键。
因为从这里开始,你已经不只是在搭内容展示网站,而是在开始真正进入:
个人博客项目第一版完整阅读路径的实现阶段。
二十四、课后思考题
请你认真思考下面这些问题:
- 为什么动态路由特别适合博客文章详情页?
- 为什么
slug是详情页里最关键的连接点之一? - 为什么详情页找不到文章时,更适合走 404,而不是普通空状态?
- 为什么正文第一版先用
string[]渲染,是一个适合当前阶段的折中方案? - 为什么详情页和列表页的职责一定要分开?
- 为什么
generateStaticParams对本地内容型项目尤其自然? - 你觉得详情页里最容易在一开始被忽略的细节是什么?为什么?
建议你把这些问题用自己的话写下来。
只要你能把这些问题讲清楚,说明你已经真正开始进入第五阶段综合实战的详情阅读主线了。
二十五、下一篇预告
下一篇我们会继续推进第五阶段综合实战,进入:
第五阶段综合实战:关于页、联系页与表单提交流程
到那时,你会开始真正把博客项目从“能读内容”继续推进到:
- 能更清楚地介绍作者和站点
- 能让别人和你建立联系
- 能跑通最基础的表单交互与提交反馈
也就是说,下一篇开始,我们会从“文章详情页、动态路由参数与正文渲染”继续走到:
个人博客项目第一版站点完整度和联系交互主线的实现阶段。