01 · 项目初始化与最小架构
搭出 Chat Bot 的前端骨架,理解页面布局、组件边界和后续扩展落点。
课时资源
一、学习目标
本课所在阶段:第一阶段 · 最小可运行闭环。
学完这一课后,你应该能够:
- 理解为什么课程一开始要先搭出 Chat Bot 的前端壳子
- 看懂首页里侧边栏、主内容区、输入区和背景层分别承担什么职责
- 明白为什么这一课不急着接 API、Agent 和数据库
- 知道后续课程会在当前页面骨架的哪些位置继续叠加真实能力
二、问题背景
如果一开始就把聊天请求、流式输出、Agent、数据库全部塞进项目里,你通常会同时卡在这几件事上:
- 页面结构还没稳定,功能一增加就要反复改 UI
- 组件职责还没理顺,输入区、消息区、侧边栏的逻辑容易混在一起
- 还没看见完整产品长什么样,就已经被接口、状态和模型调用淹没
所以这一课真正要解决的问题不是"怎么马上让它能聊",而是:怎么先把一个 Chat Bot 的前端骨架搭出来,让后面的功能有稳定落点。
你可以把这一课理解成先搭舞台:
- 左侧侧边栏先把会话区的位置留出来
- 中间主内容区先把欢迎态和消息展示区的位置留出来
- 底部输入区先把交互入口留出来
- 全局样式先把产品氛围定下来
舞台先搭好,后面课程再把演员、灯光和剧情逐步补进去,学起来会更顺。
三、核心概念
这一课虽然没有真实聊天逻辑,但已经在解决一个很重要的工程问题:先把产品外壳和组件边界立住。
你可以把这几个文件理解成前端骨架的四层:
app/layout.tsx— 负责整个应用的最外层容器app/page.tsx— 负责首页的页面编排app/components/— 负责可独立演进的 UI 组件app/globals.css— 负责统一视觉语言、布局规则和氛围效果
为什么这一步要放在第一课?因为后面的每一课都会依赖这个骨架:
| 如果现在不先做 | 后面会发生什么 |
|---|---|
| 页面区域没定下来 | 每加一个功能都要回头改布局 |
| 组件边界不清楚 | UI 和业务逻辑会越写越混 |
| 视觉风格没立住 | 你会感觉每课都像不同项目 |
所以这一课的定位不是"功能实现课",而是"产品骨架课"。
这一课和 template 的关系
课程提供的 template/ 目录是工程基线,它包含的是 Next.js 初始化后的默认页面。lesson-01 在 template 基础上做了这些增量:
| 维度 | template | lesson-01 |
|---|---|---|
page.tsx | Next.js 默认欢迎页 | Chat Bot 产品骨架 |
layout.tsx | 含 Google Fonts 配置 | 精简为最小 HTML 容器 |
globals.css | 几行 CSS 变量 | 完整的深色主题 + 玻璃态 + 动画系统 |
components/ | 无 | 4 个独立组件 |
app/api/ app/agent/ 等 | 无 | 占位目录 + README |
四、整体流程
页面骨架是怎么组成的
这张图对应的不是"未来所有功能",而是这一课真实已经存在的页面结构。你先把这张图看懂,后面每课新增的内容就更容易定位。
这一课的前端分层
这张图的重点是让你知道:后续新增功能并不是往一个文件里不断堆代码,而是沿着现有分层继续补能力。
后续课程会在哪些位置扩展
五、运行过程
1. 先从 template/ 起步
这一课不是从空目录手写项目,而是先用 template/ 作为工程基线。template 里已经有完整的 package.json、tsconfig.json、next.config.ts 和 App Router 结构。lesson-01 直接沿用这套配置,所以你只需要关注真正变化的文件。
如果你想知道这个目录名是怎么来的,可以把它理解成执行下面这一步之后的产物:
pnpm create next-app
✔ What is your project named? … template
? Would you like to use the recommended Next.js defaults? › - Use arrow-keys. Return to submit.
❯ Yes, use recommended defaults - TypeScript, ESLint, Tailwind CSS, App Router
No, reuse previous settings
No, customize options也就是说,在 CLI 提问 What is your project named? 时输入了 template,并选择了推荐默认配置。因此这里的 template/ 本质上就是一个用 Next.js 官方推荐选项创建出来的初始工程目录,而不是框架额外内置的一种特殊模板类型。
2. 先搭能看懂的页面骨架
app/page.tsx 把页面分成几个非常明确的区域:
- 左侧侧边栏(
SessionSidebar) - 中间主内容区(
app-main > EmptyState + StaticChatInput) - 背景氛围层(
BackgroundEffects+tech-grid-bg+ambient-glow)
这样你一打开页面,就能先看到"最终产品大概长什么样"。
3. 先保留静态组件,再延后真实交互
这一步是教学上的关键取舍。SessionSidebar、EmptyState、StaticChatInput 现在都还是静态版本,但它们已经把各自的职责位置占住了。这样下一课只需要往这些组件对应的位置接逻辑,而不是重新拆页面。
4. 先把组件边界讲清楚
app/page.tsx 只负责组合,不负责业务逻辑。这样页面层和组件层的边界很清楚,后面课程加功能时也更容易理解"改动到底落在哪一层"。
5. 先把视觉系统定下来
app/globals.css 不只是"让页面更好看",而是在第一课就把产品风格定下来。后面课程持续往里加能力时,你会觉得这是同一个 Chat Bot,而不是每节课一个新项目。
6. 为后续课程预留结构
app/api/README.md、app/agent/README.md、app/services/README.md、app/database/README.md 这些占位文件,本质上是在告诉你:这个项目后面会逐步长出哪些层。
六、关键代码解析
app/page.tsx - 首页入口文件,负责把背景层、侧边栏、标题区域、欢迎态和输入区拼成完整页面。
app/layout.tsx - 应用根布局,负责页面最外层结构、语言环境和基础元信息。
app/components/SessionSidebar.tsx - 左侧侧边栏的静态版本,保留会话区的结构和风格。
app/components/EmptyState.tsx - 中间欢迎态区域,展示当前阶段重点是页面骨架。
app/components/StaticChatInput.tsx - 底部输入区的静态版本,保留交互外观。
app/globals.css - 全局样式文件,决定深色背景、玻璃态、网格感和整体视觉气质。
关键代码 1:首页是怎么把组件拼起来的
export default function Home() {
return (
<main className="app-shell">
<BackgroundEffects />
<div className="tech-grid-bg" />
<div className="ambient-glow" />
<SessionSidebar />
<section className="app-main">
<header className="app-header">
...
</header>
<div className="app-content">
<EmptyState />
<div className="app-input-wrap">
<StaticChatInput />
</div>
</div>
</section>
</main>
);
}代码解析:
- 背景层(
BackgroundEffects、tech-grid-bg、ambient-glow)先出现,用来决定整个应用的视觉氛围。这些元素用position: absolute铺满全屏,不参与布局流。 SessionSidebar作为左侧独立区域出现,在本课是纯静态展示,但它已经占住了侧边栏的组件位置。第六课的记忆系统会让它开始展示真实会话列表。- 主内容区
app-main内部再分成标题区(app-header)和内容区(app-content),内容区里又拆出欢迎态和输入区。 - 这说明"页面编排"的骨架已经成立:
page.tsx本身不包含任何业务逻辑或状态管理,它只负责声明"哪些组件放在哪个位置"。如果把业务逻辑写在这一层,后面每加一个功能都要改page.tsx,维护成本会急剧上升。
关键代码 2:静态侧边栏为什么要用假数据
const sessions = [
{ id: 'welcome', name: '欢迎了解 LangGraph.js' },
{ id: 'agent', name: 'Agent 架构拆解' },
{ id: 'tooling', name: '工具调用设计' },
];
export function SessionSidebar() {
return (
<aside className="sidebar glass-panel">
...
{sessions.map((session, index) => (
<div
key={session.id}
className={`sidebar-session ${index === 0 ? 'is-active' : ''}`}
>
<div className="sidebar-session-indicator" />
<span className="sidebar-session-text">{session.name}</span>
</div>
))}
...
</aside>
);
}代码解析:
sessions写死了三条假数据,不需要任何后端调用。它的作用不是"展示功能已经完成了",而是让你在第一课就能看到侧边栏布局有了真实的内容填充。index === 0 ? 'is-active' : ''让第一条会话显示为选中态。这是一种典型的"先让 UI 看上去是对的,再把行为接上"的教学方式。- 为什么不直接留空?因为一个空空的侧边栏会让你根本看不出这个区域将来要做什么。有了假数据,你才能理解"后面的记忆系统会把这些假数据换成真实线程"。
- 这个组件后面会在 lesson-06 被改造为接收
sessionsprops、支持onSelect回调的受控组件。但在这一课,先保持独立和简单是更好的教学选择。
关键代码 3:静态输入区为什么要保留
<textarea
placeholder="输入您的问题,开启 AI 之旅..."
rows={1}
readOnly
/>代码解析:
readOnly明确告诉你:这一课先保留交互位置,不执行真实发送。你可以看到输入框的视觉效果,但不会误以为它已经能工作。- 这是一种典型的课程拆解方式——先把"位置"和"角色"讲清楚,再接"行为"和"状态"。
- 下方工具栏里的
chat-input-hint也明确写着"这一课先保留输入区外观,下一课再接入真实消息发送"。这种在组件里直接放教学提示的做法,能让你即使不打开 README,也知道当前功能的边界在哪。 - 这个组件到 lesson-02 会整体替换为
ChatInput,后者接受onSend回调并真正触发发送。
七、常见问题
为什么第一课不直接接 API?
因为如果一上来就把页面、接口、Agent、状态管理都混在一起,你很容易在还没看清页面结构时就被实现细节拖走。先把骨架搭出来、把区域和组件边界讲清楚,后面加功能时每一步落在哪都更清楚。
为什么这些组件现在还是静态的?
因为这节课要先让你看清"页面结构和角色分工"。真实逻辑会在后续课程逐步接入:lesson-02 替换输入区为可发送的 ChatInput,lesson-06 替换侧边栏为真实会话列表。
为什么目录里要先放 api、agent、services、database?
因为这些目录本身就是课程地图的一部分。它们在告诉你:这个 Chat Bot 后面会往哪些层次继续演进。在参考项目中,这四个目录分别承载了完整的 API 路由、LangGraph 工作流、ChatService 业务逻辑以及 Supabase 数据层。
这一课和参考项目(langgraphjs-chat-app)有什么关系?
参考项目是一个完整的生产级 Chat Bot,拥有真实的 LangGraph Agent、Supabase 数据库、OAuth 认证和 Canvas 预览。这一课只取用了它的首页视觉风格和布局结构,把所有业务能力全部去掉,留下最小的前端壳子。后续课程会逐步把这些能力一层层加回来。
八、练习题
- 说明为什么课程第一课要先搭页面骨架,而不是先接聊天逻辑。
- 说出
app/page.tsx、app/components/SessionSidebar.tsx、app/globals.css三个文件各自承担什么职责。 - 画出这一课的页面区域结构图,并标出后续最可能优先接入业务逻辑的区域。
- 打开
StaticChatInput.tsx,把readOnly去掉,在输入框里输入一段文字然后按回车。观察页面有什么反应,想一想为什么什么都没发生——这正是 lesson-02 要解决的问题。
九、总结
这一课真正要学会的,不是某一段具体代码,而是:先把 Chat Bot 的页面骨架和组件边界搭出来,让后续所有真实功能都有稳定落点。
只要你已经能说清楚"页面分成哪些区域、为什么输入区和侧边栏现在还是静态的、后续功能会往哪里接",这一课就学到了点子上。
和参考项目的完整首页相比,这一课刻意只保留了最薄的一层前端壳子。后续课程做的事情,就是沿着这个骨架,逐步把真实能力一层层叠加进来。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。