Nuxt SSR实践:本地课程目录完整项目 · Nuxt课程目录后续怎样安装和验收至练习:扩展公开课程
项目位于examples/web/p2-nuxt-ssr,以下源码直接包含同一目录中的真实文件,避免文档代码与项目分叉。示例未执行:未安装依赖、生成lock、调用Nuxt/nuxi、构建、启动、请求HTTP或打开浏览器。静态模板检查也不能证明框架集成正确。
Nuxt课程目录后续怎样安装和验收
Nuxt课程目录后续怎样安装和验收?后续由读者在独立项目目录安装固定直接依赖,审查实际依赖树和lock,再分别验收开发与Node构建产物。必须检查原始HTML、hydration、直接详情404、筛选返回历史以及两请求语言隔离,不能把模板语法检查当运行成功。test-spec给出逐项预期,README记录完整部署和限制。
本轮只允许JSON/文本/哈希与已有Vue模板工具做静态检查,不执行示例模块、Nuxt、服务端或浏览器。package脚本是未来操作入口,仓库文档的VitePress构建若通过,也仅验证文档站。没有lock,所以并未解决传递依赖复现;示例在根路径使用/api,子路径发布还需重新设计并实际验收。
包、配置、样式与运行说明
json
{
"name": "p2-nuxt-ssr-catalog",
"private": true,
"type": "module",
"engines": { "node": "22.14.0" },
"scripts": { "dev": "nuxt dev --host 127.0.0.1", "build": "nuxt build", "preview": "nuxt preview" },
"dependencies": { "nuxt": "4.2.2", "vue": "3.5.25", "vue-router": "4.6.3" }
}javascript
export default defineNuxtConfig({
compatibilityDate: '2025-12-09',
ssr: true,
devtools: { enabled: false },
css: ['~/assets/main.css'],
nitro: { preset: 'node-server' },
routeRules: {
'/**': { headers: { 'cache-control': 'no-store' } }
},
runtimeConfig: { public: { catalogTitle: 'SSR 课程目录' } }
})text
node_modules/
.nuxt/
.output/
.envcss
:root { font-family: system-ui, sans-serif; color: #192b3c; background: #eef3f7; }
body { margin: 0; }
main { max-width: 56rem; margin: 2rem auto; padding: 2rem; background: white; border-radius: 1rem; }
header { border-bottom: 1px solid #cad6df; padding-bottom: 1rem; }
footer { border-top: 1px solid #cad6df; margin-top: 2rem; padding-top: 1rem; }
form { display: flex; flex-wrap: wrap; align-items: end; gap: 1rem; }
label { display: block; }
input:not([type=checkbox]), select { display: block; padding: .5rem; }
button { cursor: pointer; padding: .55rem .9rem; }
a { color: #075daa; }
li { margin-block: 1rem; }
.compact li { margin-block: .3rem; }
.compact li p { margin-block: .25rem; }
:focus-visible { outline: 3px solid #a54808; outline-offset: 3px; }
@media (max-width: 40rem) { main { margin: .5rem; padding: 1rem; } }md
# Nuxt SSR 本地课程目录(示例未执行)
这是独立教学项目,不接外部 API,不要求账号或密钥。运行目标为 Node22.14.0,直接依赖精确固定 Nuxt4.2.2、Vue3.5.25、Vue Router4.6.3;不是最新版本推荐或生产安全审计。没有安装依赖、解析依赖树、生成 lock、运行 Nuxt/nuxi、构建、启动服务器、访问浏览器或发 HTTP 请求。
版本依据:[Nuxt4.2.2发布](https://github.com/nuxt/nuxt/releases/tag/v4.2.2)、[对应源码manifest](https://github.com/nuxt/nuxt/blob/v4.2.2/packages/nuxt/package.json)、[Node22.14.0发布](https://nodejs.org/en/blog/release/v22.14.0)。Nuxt该版本要求 Node ^20.19.0 或 >=22.12.0,所选22.14.0满足范围;传递依赖没有锁定,不能承诺今日重新安装产生完全相同构建。
## 文件与业务
`contract.json`是实现前手写的只读HTTP契约。`fixtures/catalog.json`有三条公开课程;`server/data/catalog.js`冻结公开数组和嵌套lessons;每次筛选生成新数组,没有全局用户对象。`server/api/courses`提供筛选和详情,`welcome.get.js`只返回当前请求归一化语言。无数据库、POST、假认证、cookie写入或任意URL转发。
Nuxt4使用`app/app.vue`和`app/pages`。首页表单提交到URL的q/level,默认all,标题检索词q最长40个UTF-16代码单元(不是课程标题字段的长度约束)。浏览器页面规范化异常query到默认值,API独立拒绝重复值或无效筛选并返回400。详情路径支持三个固定slug;不匹配或不存在均404。列表有pending、error、empty分支;详情错误进入app/error.vue,页面不展示原始服务端堆栈。
welcome通过相对地址useFetch,SSR时携带当前请求上下文,页面只显示en/zh与固定欢迎语。此简化规则不是完整Accept-Language协商。偏好是setup内useState的布尔值,初始false,跨客户端页面导航共享;硬刷新重置,不持久化、不表示用户身份。useAsyncData详情键包含id,列表键包含规范化筛选,固定序列化数据传给hydration。没有Date、随机数或localStorage首屏分叉。
## 读者后续运行步骤(本轮未执行)
在本目录准备Node22.14.0后自行执行`npm install`,审查真实依赖解析并生成lock;然后`npm run dev`,默认打开本地3000端口。第一次访问`/`应出现三条课程;提交q=Nuxt、level=advanced应只出现SSR课程;详情地址是`/courses/nuxt-ssr`。这些是预期,并非实测记录。静态验收与未来运行验收分见test-spec.md。
读者可以后执行`npm run build`和`npm run preview`;Node部署入口预期为`.output/server/index.mjs`。当前配置固定node-server preset;发布需要完整.output及Node进程,只有.output/public不包含运行中的API。相对`/api`写法面向根路径部署,不能仅改baseURL就声称子路径全部可用。反向代理需把页面/API/资源交给相同产物,正确保留状态码。
## 数据、缓存与安全边界
所有HTML和API配置no-store,没有prerender、SWR或共享响应缓存,保证欢迎语不被其他请求复用。公开fixture可以在模块中共享,但未来个性化数据不能放入模块顶层ref/对象,也不能被只按URL缓存。CDN必须尊重no-store,不能在边缘覆盖;本项目没有验证代理行为。Accept-Language不是可信身份,不能作为权限证据。
本例只公开catalogTitle,刻意不需要秘密。若未来增加上游凭据,应放runtimeConfig非public字段并只在server目录使用;API输出仍须显式选公开DTO。进入useFetch/useAsyncData/useState的返回数据可能序列化进HTML/payload,服务端执行不意味着其返回值保密。禁止向页面返回config、数据库连接、原始请求头或凭据。文本用插值,不能用v-html输出未信任内容。
## 练习
设计两次同时到达的请求A(en-US)和B(zh-CN),列出二者welcome、compact初始值与可共享的数据。再说明为什么CDN按路径缓存首页会破坏隔离。
<details><summary>参考答案(推演,未执行)</summary>
A是en与英文欢迎语,B是zh与中文欢迎语,compact都从各自请求的false初始化。公开课程冻结后可共享,欢迎语只能属于各自响应;CDN只按`/`缓存会把A的完整HTML和payload回给B,即使服务器没有全局变量也会泄漏请求相关内容。
</details>md
# 未来验收规范(全部未执行)
此文件描述读者安装并运行后的检查,不是自动测试或运行报告。当前仅审查源文件、JSON、链接、锚点与模板语法;没有框架构建或浏览器证明。
| 场景 | 后续操作 | 预期与失败判据 |
| --- | --- | --- |
| 直接SSR | 禁用JS后直接访问/与/courses/nuxt-ssr | HTML含列表/标题;checkbox不交互属预期,不能只靠浏览器最终DOM证明SSR |
| hydration | 恢复JS,完整刷新详情,观察控制台与Network | 无hydration警告;初次详情数据复用payload,不因setup裸$fetch重复请求 |
| 筛选URL | q=Nuxt、level=advanced提交,前进后退 | URL、输入草稿与结果一致;直接分享URL保留筛选 |
| 空结果 | q=不存在 | 200成功空数组与空结果提示;不得当作失败 |
| API验证 | /api/courses?level=other;q重复;41字符q | 400;接口不信任页面规范化 |
| 详情404 | /api/courses/missing与/courses/missing硬刷新 | API404;SSR页面404并展示公开错误页,不能回200空详情 |
| 路由切换 | 从基础详情切SSR详情再返回 | 标题与URL一致;页面路径key重建,数据key带id |
| 两请求隔离 | 独立请求Accept-Language en-US和zh-CN | welcome分别en/zh;不返回完整头,不设cookie;并发时也无交叉 |
| 偏好作用域 | 勾选compact后客户端导航,另一个浏览器打开,再硬刷新 | 同应用导航保留,另一浏览器false,刷新false;没有身份/持久化承诺 |
| 读取失败 | 开发时临时让课程API返回503 | 列表显示错误和重试;详情503错误页;不展示堆栈或原始错误消息 |
| payload审查 | 查看源HTML、payload及客户端资源 | 仅公开fixture/欢迎语/偏好/公开配置;无私钥、完整headers、服务器配置 |
| 部署 | 构建后Node启动,直接访问详情与API | 页面/API同源可用,404状态保留;仅上传public不满足此项 |
| 缓存 | 检查HTML、API、反向代理和CDN响应 | no-store有效,禁跨请求缓存欢迎语;仅有源码header不能证明边缘已遵守 |
## 练习
若浏览器正常但禁JS后详情为空,如何区分首屏没有服务端取数、服务端错误被吞掉与代理只返回SPA壳?
<details><summary>参考答案(推演,未执行)</summary>
保留原始HTTP状态与HTML,核对直接请求实际到达的产物;检查useAsyncData是否server:false、错误是否被默认空对象掩盖,再对比Node直连与代理响应。浏览器最终DOM只说明客户端完成了工作,不能证明SSR成功;本轮未执行这些实验。
</details>练习:扩展公开课程
不增加任何请求状态,把fixture增加一条唯一slug课程;列出需要同步修改的契约、手工验收和文案。若新增课程是某用户私有草稿,还能只改fixture吗?
参考答案(推演,未执行)
字段结构不变时Course schema不变,但最多三条、三条固定数据的说明及验收数量要同步,新增slug应唯一且符合正则。公开数据可保持冻结共享。私有草稿需要真实存储、身份和API授权,不能放进公共fixture或返回给所有人;此扩展超出当前只读教学范围。
继续阅读
- 响应式与组件
- 排错
- 响应式与组件