[{"data":1,"prerenderedAt":2644},["ShallowReactive",2],{"article-95-ssr-direct-access-article-page-502-error-root-cause-and-fix":3},{"id":4,"title":5,"slug":6,"contentMarkdown":7,"coverImage":8,"category":9,"tags":10,"aiSummary":13,"createdAt":14,"updatedAt":15,"_mdcAst":16,"_mdcToc":2620},95,"SSR 直接访问文章内容页 502 错误：根因分析与修复记录","ssr-direct-access-article-page-502-error-root-cause-and-fix","\n## 1. 问题描述\n\n**现象**：在生产环境中，直接通过浏览器地址栏访问任意文章详情页（如 `https://yourdomain.com/article/77`），Cloudflare 返回 **502 Host Error**。但从首页点击文章链接导航进入同一页面**完全正常**。\n\n![502报错](https://cfimg.wasd09090030.top/file/blogS/1771394527470_image2-2-1024x829.webp)\n\n\n**影响范围**：所有文章详情页的直接访问（包括 SEO 爬虫抓取、外部链接分享、用户直接粘贴 URL）。\n\n**复现条件**：\n- 在浏览器中直接输入任意 `/article/{id}-{slug}` URL 并回车\n- 或在无缓存状态下刷新文章详情页\n\n## 2. 架构背景\n\n理解这个问题需要先了解请求在生产环境中的完整路径：\n\n```\n用户浏览器\n    ↓\nCloudflare CDN\n    ↓\nNginx 反向代理（服务器）\n    ├── /api/**  →  .NET 后端 (:5000)\n    └── /**      →  Nuxt SSR 服务器 (:3000)\n                        ↓ (SSR 期间需要获取文章数据)\n                        → 向 .NET 后端发起 HTTP 请求\n```\n\n当用户直接访问 `/article/77-xxx` 时，Nginx 将请求转发到 Nuxt SSR 服务器（`:3000`）。Nuxt 在服务端执行 Vue 组件的 `useAsyncData` 钩子，**在服务端发起 API 请求**获取文章数据，渲染完成后返回完整 HTML。\n\n**关键区分**：\n- **直接访问（SSR）**：Nuxt 服务器在 Node.js 进程中执行数据加载 → 需要从服务端向后端 API 发送 HTTP 请求\n- **客户端导航（CSR）**：已有的 Vue 应用在浏览器端执行数据加载 → 浏览器直接向 `https://yourdomain.com/api/...` 发请求，Nginx 正确代理\n\n## 3. 根因分析\n\n### 3.1 直接原因：`$fetch` 使用了相对路径\n\n`articleDetail.repository.ts`（文章详情数据仓库）中使用了裸 `$fetch` 调用：\n\n```typescript\n// ❌ 修改前 — 使用相对路径的裸 $fetch\nexport const createArticleDetailRepository = () => {\n  const getArticleById = async (id: string | number) => {\n    return await $fetch\u003CRecord\u003Cstring, unknown>>(`/api/articles/${id}`)\n  }\n  return { getArticleById }\n}\n```\n\n### 3.2 为什么相对路径在 SSR 中失败\n\nNuxt 3 基于 Nitro 服务引擎。当 `$fetch` 在**服务端**使用相对路径时，Nitro 的行为与浏览器完全不同：\n\n| 环境 | `$fetch('/api/articles/77')` 的行为 |\n|------|-------------------------------------|\n| **浏览器（CSR）** | 浏览器发送 `GET https://yourdomain.com/api/articles/77` → Nginx 代理到 `:5000` → ✅ 成功 |\n| **服务端（SSR）** | Nitro **直接在内部查找** `/api/articles/77` 对应的 server route → 找不到（Nuxt 中没有定义此路由） → ❌ 失败 |\n\n**Nitro 内部路由机制**：在服务端执行 `$fetch('/api/...')` 时，Nitro 会先检查 `server/api/` 目录下是否有匹配的处理器。这是一个**进程内直调**（direct call），不经过网络。项目中 `server/api/` 只有 `__sitemap__/urls.get.ts`，完全没有 `/api/articles/[id]` 对应的处理器。\n\n**为什么不会回退到网络请求**：Nitro 的相对路径 `$fetch` 设计上就是用来调用同进程的 server routes 的。当路径不匹配任何 server route 时，返回错误（通常是 404 或内部异常），而不是尝试通过网络请求。\n\n**为什么 `routeRules` 没起到代理作用**：`nuxt.config.ts` 中的 `/api/**` routeRules 只设置了 CORS 响应头，**没有配置 `proxy`**。所以即使请求路径匹配 `/api/**`，也不会被代理到后端。\n\n### 3.3 完整的错误链路\n\n```\n用户直接访问 /article/77-xxx\n    ↓\nNginx → Nuxt SSR (:3000)\n    ↓\nVue 组件 SSR 执行 useAsyncData\n    ↓\n调用 repository.getArticleById(77)\n    ↓\n执行 $fetch('/api/articles/77')        ← 相对路径\n    ↓\nNitro 内部查找 server route '/api/articles/77'\n    ↓\n未找到匹配的 server handler           ← 失败点\n    ↓\n$fetch 抛出异常\n    ↓\nuseAsyncData 错误 → SSR 渲染失败\n    ↓\nNuxt 无法返回有效响应给 Nginx\n    ↓\nNginx upstream 超时/错误 → 返回 502\n    ↓\nCloudflare 显示 \"502 Host Error\"\n```\n\n### 3.4 为什么从首页导航正常\n\n首页本身的 SSR 不依赖 `/api/articles/{id}`（首页使用文章列表 API，其 repository **已经**使用了共享 API 客户端）。首页 SSR 成功后，后续的文章导航走的是 **客户端路由（Vue Router CSR transition）**：\n\n1. 首页 SSR → 浏览器收到完整 HTML + Vue 应用 hydrate\n2. 用户点击文章链接 → Vue Router 客户端导航（不刷新页面）\n3. `useAsyncData` 在浏览器端执行 `$fetch('/api/articles/77')`\n4. 浏览器将请求发送到 `https://yourdomain.com/api/articles/77`\n5. Nginx 代理到 `:5000` → 成功获取数据 → 页面正常渲染\n\n## 4. 修复方案\n\n### 4.1 核心修改：使用共享 API 客户端\n\n**文件**：`nuxt/app/features/article-detail/services/articleDetail.repository.ts`\n\n```typescript\n// ✅ 修改后 — 使用共享 API 客户端\nimport { createApiClient } from '~/shared/api/client'\n\nexport const createArticleDetailRepository = () => {\n  const getArticleById = async (id: string | number) => {\n    const client = createApiClient()\n    return await client.get\u003CRecord\u003Cstring, unknown>>(`/articles/${id}`)\n  }\n  return { getArticleById }\n}\n```\n\n**文件**：`nuxt/app/composables/useArticleNavigation.ts`（预加载逻辑）\n\n```typescript\n// ✅ 修改后 — 同样使用共享 API 客户端\nimport { createApiClient } from '~/shared/api/client'\n\n// 在预加载代码中:\nconst client = createApiClient()\nvoid client.get\u003CArticleApiResponse>(`/articles/${articleId}`)\n  .then(...)\n```\n\n### 4.2 修复生效原理\n\n共享 API 客户端 (`createApiClient`) 通过 `resolveApiBaseURL()` 在不同环境解析到正确的**绝对地址**：\n\n```typescript\n// shared/api/base-url.ts — URL 解析逻辑\nexport function resolveApiBaseURL(): string {\n  const config = useRuntimeConfig()\n  const apiBaseServer = config.apiBaseServer  // 服务端私有配置\n\n  // SSR 时：使用服务端专用地址（内网直连后端）\n  if (process.server && apiBaseServer) {\n    return apiBaseServer  // → 'http://127.0.0.1:5000/api'\n  }\n\n  // CSR 时：使用公共地址（经过 Nginx 代理）\n  return config.public.apiBase  // → 'https://yourdomain.com/api'\n}\n```\n\n这样 `createApiClient().get('/articles/77')` 拼接出的完整 URL 为：\n\n| 环境 | 解析结果 | 请求方式 |\n|------|---------|---------|\n| SSR（服务端） | `http://127.0.0.1:5000/api/articles/77` | Node.js 通过 HTTP 直接请求本机后端 |\n| CSR（浏览器） | `https://yourdomain.com/api/articles/77` | 浏览器通过 Nginx 代理到后端 |\n\n**关键差异**：使用绝对 URL 的 `$fetch` 会走**真实 HTTP 网络请求**，绕过 Nitro 的内部路由机制，直接连接到 .NET 后端。\n\n### 4.3 配套基础设施（已在此前配置完成）\n\n修复能生效还依赖以下前置配置：\n\n| 配置项 | 位置 | 值 | 作用 |\n|--------|------|---|------|\n| `NUXT_API_BASE_URL` | `ecosystem.config.js` (PM2 环境变量) | `http://127.0.0.1:5000/api` | SSR 时的后端内网地址 |\n| `runtimeConfig.apiBaseServer` | `nuxt.config.ts` | 读取 `NUXT_API_BASE_URL` | 将环境变量暴露给 `resolveApiBaseURL()` |\n| `UseHttpsRedirection` 改为 dev-only | `Program.cs` | 仅在 Development 环境启用 | 避免 SSR 的 HTTP 请求被后端 301 重定向到 HTTPS |\n\n### 4.4 数据流对比\n\n**修复前**（SSR 失败路径）：\n```\nNuxt SSR → $fetch('/api/articles/77')\n         → Nitro 内部查找 server route\n         → 404/异常 → SSR 崩溃 → 502\n```\n\n**修复后**（SSR 正常路径）：\n```\nNuxt SSR → createApiClient().get('/articles/77')\n         → resolveApiBaseURL() → 'http://127.0.0.1:5000/api'\n         → $fetch('http://127.0.0.1:5000/api/articles/77')\n         → Node.js HTTP 请求 → .NET 后端\n         → 返回文章数据 → SSR 正常渲染 → 200\n```\n\n## 5. 其他同类文件现状\n\n排查结果：项目中其他使用 API 的 repository **已经正确使用**了共享客户端：\n\n| 文件 | 使用方式 | 状态 |\n|------|---------|------|\n| `features/article-list/services/articles.repository.ts` | `createApiClient()` | ✅ 正确 |\n| `features/article-list/composables/useArticleCacheFeature.ts` | `createApiClient()` | ✅ 正确 |\n| `features/gallery-public/` | `createApiClient()` | ✅ 正确 |\n| `features/gallery-admin/` | `createApiClient()` | ✅ 正确 |\n| **`features/article-detail/services/articleDetail.repository.ts`** | 裸 `$fetch` → **已修复** | ✅ 已修复 |\n| **`composables/useArticleNavigation.ts`** | 裸 `$fetch` → **已修复** | ✅ 已修复 |\n\n## 6. 验证结果\n\n本地同时启动后端（`:5000`）和 Nuxt dev（`:3000`），模拟 SSR 直接访问：\n\n| 测试 URL | 状态码 | 响应大小 | 结果 |\n|----------|--------|---------|------|\n| `http://127.0.0.1:3000/article/77` | 200 | 166,670 chars | ✅ SSR 正常 |\n| `http://127.0.0.1:3000/article/77` | 200 | 207,752 chars | ✅ SSR 正常 |\n| `http://127.0.0.1:3000/tutorials` | 200 | 34,393 chars | ✅ SSR 正常 |\n| `http://127.0.0.1:3000/` | 200 | 59,534 chars | ✅ SSR 正常 |\n\n## 7. 经验总结\n\n### 核心教训\n\n> **在 Nuxt 3 SSR 中，`$fetch` 的相对路径和绝对路径行为截然不同。**\n\n- **相对路径** `$fetch('/api/xxx')`：SSR 时走 Nitro 内部路由（进程内直调），**不** 产生网络请求\n- **绝对路径** `$fetch('http://xxx/api/xxx')`：SSR 时走真实 HTTP 网络请求，连接到目标服务\n\n### 防范措施\n\n1. **所有 API 调用统一通过共享客户端**（`createApiClient()`），禁止在业务代码中直接使用裸 `$fetch('/api/...')` + 相对路径\n2. 新增 repository 时参照 `articles.repository.ts` 等已有文件的模式\n3. 可考虑通过 ESLint 自定义规则检测裸 `$fetch('/api/...)` 模式，在 CI 中拦截\n\n\n### 站长总结\n\n说白了就是Nitro服务器的路由处理逻辑的问题，直接访问文章链接`https://yourdomain.com/article/77`，触发了Nitro 内部的fetch处理，其将`$fetch('/api/articles/${id}')`这个相对路径当作内部的路由处理处理\n\n**为什么会这样呢？**\n\n首先是我的Nginx的代理处有以下逻辑：\n\n```\n    location /api/ {\n        proxy_pass http://127.0.0.1:5000;\n        }\n```\n\n正常能拿到数据的请求应该是\n```\nhttps://yourdomain.com/api/article/77\n```\n\n这样才能触发Nginx将请求代理到后端\n\n而直接请求则是\n\n\n```\nhttps://yourdomain.com/article/77\n```\n\n由于缺失了`/api/ `导致了数据请求没有被Nginx转发至后端，而是直接发送到了Nuxt的Nitro服务器。\n\n但 Nuxt 没有对应的 `/api/articles/[id]` server route，也没有代理规则，导致请求失败 → SSR 渲染崩溃 → Nginx 拿不到响应 → Cloudflare 返回 502。\n\n解决方式就是很简单的解析API从相对直接改为绝对路径。\n\n这个问题很可笑吧，我是靠AI才发现了这个严重的问题，当时一度以为是后端的问题，结果是请求根本没有发到后端。\n\n说实话，当时检查一下后端日志应该就能发现问题了。\n\n我是第一次使用SSR框架，完全不知道 SSR 直接访问文章内容页 502 错误的原因，只觉得有点懵，直到我尝试把问题描述发给AI agent（CloudCode）让其尝试解决。\n\n然后Agent直接发现关键问题，连解决带测试只花了短短5分钟，还生成了上述详细的报告（原因+解决方式+防范措施），不得不感叹AI的进步速度。","https://cfimg.wasd09090030.top/file/gallery/1771161656126_20260105_220810.webp","study",[11,12],"前端","网络代理","文章分析了生产环境中直接访问文章详情页时出现502错误的根本原因。问题源于Nuxt SSR服务端使用相对路径调用API，导致Nitro引擎在内部查找路由失败，而非通过网络请求后端。修复方案是改用共享API客户端，确保在服务端和客户端都能正确解析绝对地址，从而解决SSR渲染失败的问题。","2026-02-18T05:57:15.5422187","2026-02-18T09:58:52.796537",{"data":17,"body":19,"toc":2620},{"title":18,"description":18},"",{"type":20,"children":21},"root",[22,31,66,75,85,95,118,124,129,140,176,185,216,222,237,255,526,532,551,648,704,721,774,780,789,795,821,873,879,885,900,1201,1217,1400,1406,1433,1679,1692,1761,1785,1791,1796,1938,1944,1954,1963,1973,1982,1988,2000,2194,2200,2219,2352,2358,2363,2382,2426,2431,2484,2489,2509,2517,2522,2531,2536,2545,2550,2555,2564,2577,2589,2594,2599,2604,2609,2614],{"type":23,"tag":24,"props":25,"children":27},"element","h2",{"id":26},"_1-问题描述",[28],{"type":29,"value":30},"text","1. 问题描述",{"type":23,"tag":32,"props":33,"children":34},"p",{},[35,41,43,50,52,57,59,64],{"type":23,"tag":36,"props":37,"children":38},"strong",{},[39],{"type":29,"value":40},"现象",{"type":29,"value":42},"：在生产环境中，直接通过浏览器地址栏访问任意文章详情页（如 ",{"type":23,"tag":44,"props":45,"children":47},"code",{"className":46},[],[48],{"type":29,"value":49},"https://yourdomain.com/article/77",{"type":29,"value":51},"），Cloudflare 返回 ",{"type":23,"tag":36,"props":53,"children":54},{},[55],{"type":29,"value":56},"502 Host Error",{"type":29,"value":58},"。但从首页点击文章链接导航进入同一页面",{"type":23,"tag":36,"props":60,"children":61},{},[62],{"type":29,"value":63},"完全正常",{"type":29,"value":65},"。",{"type":23,"tag":32,"props":67,"children":68},{},[69],{"type":23,"tag":70,"props":71,"children":74},"img",{"alt":72,"src":73},"502报错","https://cfimg.wasd09090030.top/file/blogS/1771394527470_image2-2-1024x829.webp",[],{"type":23,"tag":32,"props":76,"children":77},{},[78,83],{"type":23,"tag":36,"props":79,"children":80},{},[81],{"type":29,"value":82},"影响范围",{"type":29,"value":84},"：所有文章详情页的直接访问（包括 SEO 爬虫抓取、外部链接分享、用户直接粘贴 URL）。",{"type":23,"tag":32,"props":86,"children":87},{},[88,93],{"type":23,"tag":36,"props":89,"children":90},{},[91],{"type":29,"value":92},"复现条件",{"type":29,"value":94},"：",{"type":23,"tag":96,"props":97,"children":98},"ul",{},[99,113],{"type":23,"tag":100,"props":101,"children":102},"li",{},[103,105,111],{"type":29,"value":104},"在浏览器中直接输入任意 ",{"type":23,"tag":44,"props":106,"children":108},{"className":107},[],[109],{"type":29,"value":110},"/article/{id}-{slug}",{"type":29,"value":112}," URL 并回车",{"type":23,"tag":100,"props":114,"children":115},{},[116],{"type":29,"value":117},"或在无缓存状态下刷新文章详情页",{"type":23,"tag":24,"props":119,"children":121},{"id":120},"_2-架构背景",[122],{"type":29,"value":123},"2. 架构背景",{"type":23,"tag":32,"props":125,"children":126},{},[127],{"type":29,"value":128},"理解这个问题需要先了解请求在生产环境中的完整路径：",{"type":23,"tag":130,"props":131,"children":135},"pre",{"className":132,"code":134,"language":29},[133],"language-text","用户浏览器\n    ↓\nCloudflare CDN\n    ↓\nNginx 反向代理（服务器）\n    ├── /api/**  →  .NET 后端 (:5000)\n    └── /**      →  Nuxt SSR 服务器 (:3000)\n                        ↓ (SSR 期间需要获取文章数据)\n                        → 向 .NET 后端发起 HTTP 请求\n",[136],{"type":23,"tag":44,"props":137,"children":138},{"__ignoreMap":18},[139],{"type":29,"value":134},{"type":23,"tag":32,"props":141,"children":142},{},[143,145,151,153,159,161,167,169,174],{"type":29,"value":144},"当用户直接访问 ",{"type":23,"tag":44,"props":146,"children":148},{"className":147},[],[149],{"type":29,"value":150},"/article/77-xxx",{"type":29,"value":152}," 时，Nginx 将请求转发到 Nuxt SSR 服务器（",{"type":23,"tag":44,"props":154,"children":156},{"className":155},[],[157],{"type":29,"value":158},":3000",{"type":29,"value":160},"）。Nuxt 在服务端执行 Vue 组件的 ",{"type":23,"tag":44,"props":162,"children":164},{"className":163},[],[165],{"type":29,"value":166},"useAsyncData",{"type":29,"value":168}," 钩子，",{"type":23,"tag":36,"props":170,"children":171},{},[172],{"type":29,"value":173},"在服务端发起 API 请求",{"type":29,"value":175},"获取文章数据，渲染完成后返回完整 HTML。",{"type":23,"tag":32,"props":177,"children":178},{},[179,184],{"type":23,"tag":36,"props":180,"children":181},{},[182],{"type":29,"value":183},"关键区分",{"type":29,"value":94},{"type":23,"tag":96,"props":186,"children":187},{},[188,198],{"type":23,"tag":100,"props":189,"children":190},{},[191,196],{"type":23,"tag":36,"props":192,"children":193},{},[194],{"type":29,"value":195},"直接访问（SSR）",{"type":29,"value":197},"：Nuxt 服务器在 Node.js 进程中执行数据加载 → 需要从服务端向后端 API 发送 HTTP 请求",{"type":23,"tag":100,"props":199,"children":200},{},[201,206,208,214],{"type":23,"tag":36,"props":202,"children":203},{},[204],{"type":29,"value":205},"客户端导航（CSR）",{"type":29,"value":207},"：已有的 Vue 应用在浏览器端执行数据加载 → 浏览器直接向 ",{"type":23,"tag":44,"props":209,"children":211},{"className":210},[],[212],{"type":29,"value":213},"https://yourdomain.com/api/...",{"type":29,"value":215}," 发请求，Nginx 正确代理",{"type":23,"tag":24,"props":217,"children":219},{"id":218},"_3-根因分析",[220],{"type":29,"value":221},"3. 根因分析",{"type":23,"tag":223,"props":224,"children":226},"h3",{"id":225},"_31-直接原因fetch-使用了相对路径",[227,229,235],{"type":29,"value":228},"3.1 直接原因：",{"type":23,"tag":44,"props":230,"children":232},{"className":231},[],[233],{"type":29,"value":234},"$fetch",{"type":29,"value":236}," 使用了相对路径",{"type":23,"tag":32,"props":238,"children":239},{},[240,246,248,253],{"type":23,"tag":44,"props":241,"children":243},{"className":242},[],[244],{"type":29,"value":245},"articleDetail.repository.ts",{"type":29,"value":247},"（文章详情数据仓库）中使用了裸 ",{"type":23,"tag":44,"props":249,"children":251},{"className":250},[],[252],{"type":29,"value":234},{"type":29,"value":254}," 调用：",{"type":23,"tag":130,"props":256,"children":260},{"className":257,"code":258,"language":259,"meta":18,"style":18},"language-typescript shiki shiki-themes material-theme-darker one-dark-pro","// ❌ 修改前 — 使用相对路径的裸 $fetch\nexport const createArticleDetailRepository = () => {\n  const getArticleById = async (id: string | number) => {\n    return await $fetch\u003CRecord\u003Cstring, unknown>>(`/api/articles/${id}`)\n  }\n  return { getArticleById }\n}\n","typescript",[261],{"type":23,"tag":44,"props":262,"children":263},{"__ignoreMap":18},[264,276,320,388,485,494,517],{"type":23,"tag":265,"props":266,"children":269},"span",{"class":267,"line":268},"line",1,[270],{"type":23,"tag":265,"props":271,"children":273},{"style":272},"--shiki-default:#545454;--shiki-default-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic",[274],{"type":29,"value":275},"// ❌ 修改前 — 使用相对路径的裸 $fetch\n",{"type":23,"tag":265,"props":277,"children":279},{"class":267,"line":278},2,[280,286,292,298,304,310,315],{"type":23,"tag":265,"props":281,"children":283},{"style":282},"--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:inherit",[284],{"type":29,"value":285},"export",{"type":23,"tag":265,"props":287,"children":289},{"style":288},"--shiki-default:#C792EA;--shiki-dark:#C678DD",[290],{"type":29,"value":291}," const",{"type":23,"tag":265,"props":293,"children":295},{"style":294},"--shiki-default:#EEFFFF;--shiki-dark:#61AFEF",[296],{"type":29,"value":297}," createArticleDetailRepository",{"type":23,"tag":265,"props":299,"children":301},{"style":300},"--shiki-default:#89DDFF;--shiki-dark:#56B6C2",[302],{"type":29,"value":303}," =",{"type":23,"tag":265,"props":305,"children":307},{"style":306},"--shiki-default:#89DDFF;--shiki-dark:#ABB2BF",[308],{"type":29,"value":309}," ()",{"type":23,"tag":265,"props":311,"children":312},{"style":288},[313],{"type":29,"value":314}," =>",{"type":23,"tag":265,"props":316,"children":317},{"style":306},[318],{"type":29,"value":319}," {\n",{"type":23,"tag":265,"props":321,"children":323},{"class":267,"line":322},3,[324,329,334,338,343,348,354,359,365,370,375,380,384],{"type":23,"tag":265,"props":325,"children":326},{"style":288},[327],{"type":29,"value":328},"  const",{"type":23,"tag":265,"props":330,"children":331},{"style":294},[332],{"type":29,"value":333}," getArticleById",{"type":23,"tag":265,"props":335,"children":336},{"style":300},[337],{"type":29,"value":303},{"type":23,"tag":265,"props":339,"children":340},{"style":288},[341],{"type":29,"value":342}," async",{"type":23,"tag":265,"props":344,"children":345},{"style":306},[346],{"type":29,"value":347}," (",{"type":23,"tag":265,"props":349,"children":351},{"style":350},"--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic",[352],{"type":29,"value":353},"id",{"type":23,"tag":265,"props":355,"children":356},{"style":306},[357],{"type":29,"value":358},":",{"type":23,"tag":265,"props":360,"children":362},{"style":361},"--shiki-default:#FFCB6B;--shiki-dark:#E5C07B",[363],{"type":29,"value":364}," string",{"type":23,"tag":265,"props":366,"children":367},{"style":306},[368],{"type":29,"value":369}," |",{"type":23,"tag":265,"props":371,"children":372},{"style":361},[373],{"type":29,"value":374}," number",{"type":23,"tag":265,"props":376,"children":377},{"style":306},[378],{"type":29,"value":379},")",{"type":23,"tag":265,"props":381,"children":382},{"style":288},[383],{"type":29,"value":314},{"type":23,"tag":265,"props":385,"children":386},{"style":306},[387],{"type":29,"value":319},{"type":23,"tag":265,"props":389,"children":391},{"class":267,"line":390},4,[392,397,402,408,413,418,422,427,432,437,442,448,454,460,466,471,476,480],{"type":23,"tag":265,"props":393,"children":394},{"style":282},[395],{"type":29,"value":396},"    return",{"type":23,"tag":265,"props":398,"children":399},{"style":282},[400],{"type":29,"value":401}," await",{"type":23,"tag":265,"props":403,"children":405},{"style":404},"--shiki-default:#82AAFF;--shiki-dark:#61AFEF",[406],{"type":29,"value":407}," $fetch",{"type":23,"tag":265,"props":409,"children":410},{"style":306},[411],{"type":29,"value":412},"\u003C",{"type":23,"tag":265,"props":414,"children":415},{"style":361},[416],{"type":29,"value":417},"Record",{"type":23,"tag":265,"props":419,"children":420},{"style":306},[421],{"type":29,"value":412},{"type":23,"tag":265,"props":423,"children":424},{"style":361},[425],{"type":29,"value":426},"string",{"type":23,"tag":265,"props":428,"children":429},{"style":306},[430],{"type":29,"value":431},",",{"type":23,"tag":265,"props":433,"children":434},{"style":361},[435],{"type":29,"value":436}," unknown",{"type":23,"tag":265,"props":438,"children":439},{"style":306},[440],{"type":29,"value":441},">>",{"type":23,"tag":265,"props":443,"children":445},{"style":444},"--shiki-default:#F07178;--shiki-dark:#ABB2BF",[446],{"type":29,"value":447},"(",{"type":23,"tag":265,"props":449,"children":451},{"style":450},"--shiki-default:#89DDFF;--shiki-dark:#98C379",[452],{"type":29,"value":453},"`",{"type":23,"tag":265,"props":455,"children":457},{"style":456},"--shiki-default:#C3E88D;--shiki-dark:#98C379",[458],{"type":29,"value":459},"/api/articles/",{"type":23,"tag":265,"props":461,"children":463},{"style":462},"--shiki-default:#89DDFF;--shiki-dark:#C678DD",[464],{"type":29,"value":465},"${",{"type":23,"tag":265,"props":467,"children":469},{"style":468},"--shiki-default:#EEFFFF;--shiki-dark:#E06C75",[470],{"type":29,"value":353},{"type":23,"tag":265,"props":472,"children":473},{"style":462},[474],{"type":29,"value":475},"}",{"type":23,"tag":265,"props":477,"children":478},{"style":450},[479],{"type":29,"value":453},{"type":23,"tag":265,"props":481,"children":482},{"style":444},[483],{"type":29,"value":484},")\n",{"type":23,"tag":265,"props":486,"children":488},{"class":267,"line":487},5,[489],{"type":23,"tag":265,"props":490,"children":491},{"style":306},[492],{"type":29,"value":493},"  }\n",{"type":23,"tag":265,"props":495,"children":497},{"class":267,"line":496},6,[498,503,508,512],{"type":23,"tag":265,"props":499,"children":500},{"style":282},[501],{"type":29,"value":502},"  return",{"type":23,"tag":265,"props":504,"children":505},{"style":306},[506],{"type":29,"value":507}," {",{"type":23,"tag":265,"props":509,"children":510},{"style":468},[511],{"type":29,"value":333},{"type":23,"tag":265,"props":513,"children":514},{"style":306},[515],{"type":29,"value":516}," }\n",{"type":23,"tag":265,"props":518,"children":520},{"class":267,"line":519},7,[521],{"type":23,"tag":265,"props":522,"children":523},{"style":306},[524],{"type":29,"value":525},"}\n",{"type":23,"tag":223,"props":527,"children":529},{"id":528},"_32-为什么相对路径在-ssr-中失败",[530],{"type":29,"value":531},"3.2 为什么相对路径在 SSR 中失败",{"type":23,"tag":32,"props":533,"children":534},{},[535,537,542,544,549],{"type":29,"value":536},"Nuxt 3 基于 Nitro 服务引擎。当 ",{"type":23,"tag":44,"props":538,"children":540},{"className":539},[],[541],{"type":29,"value":234},{"type":29,"value":543}," 在",{"type":23,"tag":36,"props":545,"children":546},{},[547],{"type":29,"value":548},"服务端",{"type":29,"value":550},"使用相对路径时，Nitro 的行为与浏览器完全不同：",{"type":23,"tag":552,"props":553,"children":554},"table",{},[555,580],{"type":23,"tag":556,"props":557,"children":558},"thead",{},[559],{"type":23,"tag":560,"props":561,"children":562},"tr",{},[563,569],{"type":23,"tag":564,"props":565,"children":566},"th",{},[567],{"type":29,"value":568},"环境",{"type":23,"tag":564,"props":570,"children":571},{},[572,578],{"type":23,"tag":44,"props":573,"children":575},{"className":574},[],[576],{"type":29,"value":577},"$fetch('/api/articles/77')",{"type":29,"value":579}," 的行为",{"type":23,"tag":581,"props":582,"children":583},"tbody",{},[584,617],{"type":23,"tag":560,"props":585,"children":586},{},[587,596],{"type":23,"tag":588,"props":589,"children":590},"td",{},[591],{"type":23,"tag":36,"props":592,"children":593},{},[594],{"type":29,"value":595},"浏览器（CSR）",{"type":23,"tag":588,"props":597,"children":598},{},[599,601,607,609,615],{"type":29,"value":600},"浏览器发送 ",{"type":23,"tag":44,"props":602,"children":604},{"className":603},[],[605],{"type":29,"value":606},"GET https://yourdomain.com/api/articles/77",{"type":29,"value":608}," → Nginx 代理到 ",{"type":23,"tag":44,"props":610,"children":612},{"className":611},[],[613],{"type":29,"value":614},":5000",{"type":29,"value":616}," → ✅ 成功",{"type":23,"tag":560,"props":618,"children":619},{},[620,628],{"type":23,"tag":588,"props":621,"children":622},{},[623],{"type":23,"tag":36,"props":624,"children":625},{},[626],{"type":29,"value":627},"服务端（SSR）",{"type":23,"tag":588,"props":629,"children":630},{},[631,633,638,640,646],{"type":29,"value":632},"Nitro ",{"type":23,"tag":36,"props":634,"children":635},{},[636],{"type":29,"value":637},"直接在内部查找",{"type":29,"value":639}," ",{"type":23,"tag":44,"props":641,"children":643},{"className":642},[],[644],{"type":29,"value":645},"/api/articles/77",{"type":29,"value":647}," 对应的 server route → 找不到（Nuxt 中没有定义此路由） → ❌ 失败",{"type":23,"tag":32,"props":649,"children":650},{},[651,656,658,664,666,672,674,679,681,686,688,694,696,702],{"type":23,"tag":36,"props":652,"children":653},{},[654],{"type":29,"value":655},"Nitro 内部路由机制",{"type":29,"value":657},"：在服务端执行 ",{"type":23,"tag":44,"props":659,"children":661},{"className":660},[],[662],{"type":29,"value":663},"$fetch('/api/...')",{"type":29,"value":665}," 时，Nitro 会先检查 ",{"type":23,"tag":44,"props":667,"children":669},{"className":668},[],[670],{"type":29,"value":671},"server/api/",{"type":29,"value":673}," 目录下是否有匹配的处理器。这是一个",{"type":23,"tag":36,"props":675,"children":676},{},[677],{"type":29,"value":678},"进程内直调",{"type":29,"value":680},"（direct call），不经过网络。项目中 ",{"type":23,"tag":44,"props":682,"children":684},{"className":683},[],[685],{"type":29,"value":671},{"type":29,"value":687}," 只有 ",{"type":23,"tag":44,"props":689,"children":691},{"className":690},[],[692],{"type":29,"value":693},"__sitemap__/urls.get.ts",{"type":29,"value":695},"，完全没有 ",{"type":23,"tag":44,"props":697,"children":699},{"className":698},[],[700],{"type":29,"value":701},"/api/articles/[id]",{"type":29,"value":703}," 对应的处理器。",{"type":23,"tag":32,"props":705,"children":706},{},[707,712,714,719],{"type":23,"tag":36,"props":708,"children":709},{},[710],{"type":29,"value":711},"为什么不会回退到网络请求",{"type":29,"value":713},"：Nitro 的相对路径 ",{"type":23,"tag":44,"props":715,"children":717},{"className":716},[],[718],{"type":29,"value":234},{"type":29,"value":720}," 设计上就是用来调用同进程的 server routes 的。当路径不匹配任何 server route 时，返回错误（通常是 404 或内部异常），而不是尝试通过网络请求。",{"type":23,"tag":32,"props":722,"children":723},{},[724,737,738,744,746,752,754,765,767,772],{"type":23,"tag":36,"props":725,"children":726},{},[727,729,735],{"type":29,"value":728},"为什么 ",{"type":23,"tag":44,"props":730,"children":732},{"className":731},[],[733],{"type":29,"value":734},"routeRules",{"type":29,"value":736}," 没起到代理作用",{"type":29,"value":94},{"type":23,"tag":44,"props":739,"children":741},{"className":740},[],[742],{"type":29,"value":743},"nuxt.config.ts",{"type":29,"value":745}," 中的 ",{"type":23,"tag":44,"props":747,"children":749},{"className":748},[],[750],{"type":29,"value":751},"/api/**",{"type":29,"value":753}," routeRules 只设置了 CORS 响应头，",{"type":23,"tag":36,"props":755,"children":756},{},[757,759],{"type":29,"value":758},"没有配置 ",{"type":23,"tag":44,"props":760,"children":762},{"className":761},[],[763],{"type":29,"value":764},"proxy",{"type":29,"value":766},"。所以即使请求路径匹配 ",{"type":23,"tag":44,"props":768,"children":770},{"className":769},[],[771],{"type":29,"value":751},{"type":29,"value":773},"，也不会被代理到后端。",{"type":23,"tag":223,"props":775,"children":777},{"id":776},"_33-完整的错误链路",[778],{"type":29,"value":779},"3.3 完整的错误链路",{"type":23,"tag":130,"props":781,"children":784},{"className":782,"code":783,"language":29},[133],"用户直接访问 /article/77-xxx\n    ↓\nNginx → Nuxt SSR (:3000)\n    ↓\nVue 组件 SSR 执行 useAsyncData\n    ↓\n调用 repository.getArticleById(77)\n    ↓\n执行 $fetch('/api/articles/77')        ← 相对路径\n    ↓\nNitro 内部查找 server route '/api/articles/77'\n    ↓\n未找到匹配的 server handler           ← 失败点\n    ↓\n$fetch 抛出异常\n    ↓\nuseAsyncData 错误 → SSR 渲染失败\n    ↓\nNuxt 无法返回有效响应给 Nginx\n    ↓\nNginx upstream 超时/错误 → 返回 502\n    ↓\nCloudflare 显示 \"502 Host Error\"\n",[785],{"type":23,"tag":44,"props":786,"children":787},{"__ignoreMap":18},[788],{"type":29,"value":783},{"type":23,"tag":223,"props":790,"children":792},{"id":791},"_34-为什么从首页导航正常",[793],{"type":29,"value":794},"3.4 为什么从首页导航正常",{"type":23,"tag":32,"props":796,"children":797},{},[798,800,806,808,813,815,820],{"type":29,"value":799},"首页本身的 SSR 不依赖 ",{"type":23,"tag":44,"props":801,"children":803},{"className":802},[],[804],{"type":29,"value":805},"/api/articles/{id}",{"type":29,"value":807},"（首页使用文章列表 API，其 repository ",{"type":23,"tag":36,"props":809,"children":810},{},[811],{"type":29,"value":812},"已经",{"type":29,"value":814},"使用了共享 API 客户端）。首页 SSR 成功后，后续的文章导航走的是 ",{"type":23,"tag":36,"props":816,"children":817},{},[818],{"type":29,"value":819},"客户端路由（Vue Router CSR transition）",{"type":29,"value":94},{"type":23,"tag":822,"props":823,"children":824},"ol",{},[825,830,835,850,861],{"type":23,"tag":100,"props":826,"children":827},{},[828],{"type":29,"value":829},"首页 SSR → 浏览器收到完整 HTML + Vue 应用 hydrate",{"type":23,"tag":100,"props":831,"children":832},{},[833],{"type":29,"value":834},"用户点击文章链接 → Vue Router 客户端导航（不刷新页面）",{"type":23,"tag":100,"props":836,"children":837},{},[838,843,845],{"type":23,"tag":44,"props":839,"children":841},{"className":840},[],[842],{"type":29,"value":166},{"type":29,"value":844}," 在浏览器端执行 ",{"type":23,"tag":44,"props":846,"children":848},{"className":847},[],[849],{"type":29,"value":577},{"type":23,"tag":100,"props":851,"children":852},{},[853,855],{"type":29,"value":854},"浏览器将请求发送到 ",{"type":23,"tag":44,"props":856,"children":858},{"className":857},[],[859],{"type":29,"value":860},"https://yourdomain.com/api/articles/77",{"type":23,"tag":100,"props":862,"children":863},{},[864,866,871],{"type":29,"value":865},"Nginx 代理到 ",{"type":23,"tag":44,"props":867,"children":869},{"className":868},[],[870],{"type":29,"value":614},{"type":29,"value":872}," → 成功获取数据 → 页面正常渲染",{"type":23,"tag":24,"props":874,"children":876},{"id":875},"_4-修复方案",[877],{"type":29,"value":878},"4. 修复方案",{"type":23,"tag":223,"props":880,"children":882},{"id":881},"_41-核心修改使用共享-api-客户端",[883],{"type":29,"value":884},"4.1 核心修改：使用共享 API 客户端",{"type":23,"tag":32,"props":886,"children":887},{},[888,893,894],{"type":23,"tag":36,"props":889,"children":890},{},[891],{"type":29,"value":892},"文件",{"type":29,"value":94},{"type":23,"tag":44,"props":895,"children":897},{"className":896},[],[898],{"type":29,"value":899},"nuxt/app/features/article-detail/services/articleDetail.repository.ts",{"type":23,"tag":130,"props":901,"children":903},{"className":257,"code":902,"language":259,"meta":18,"style":18},"// ✅ 修改后 — 使用共享 API 客户端\nimport { createApiClient } from '~/shared/api/client'\n\nexport const createArticleDetailRepository = () => {\n  const getArticleById = async (id: string | number) => {\n    const client = createApiClient()\n    return await client.get\u003CRecord\u003Cstring, unknown>>(`/articles/${id}`)\n  }\n  return { getArticleById }\n}\n",[904],{"type":23,"tag":44,"props":905,"children":906},{"__ignoreMap":18},[907,915,957,966,997,1052,1079,1165,1173,1193],{"type":23,"tag":265,"props":908,"children":909},{"class":267,"line":268},[910],{"type":23,"tag":265,"props":911,"children":912},{"style":272},[913],{"type":29,"value":914},"// ✅ 修改后 — 使用共享 API 客户端\n",{"type":23,"tag":265,"props":916,"children":917},{"class":267,"line":278},[918,923,927,932,937,942,947,952],{"type":23,"tag":265,"props":919,"children":920},{"style":282},[921],{"type":29,"value":922},"import",{"type":23,"tag":265,"props":924,"children":925},{"style":306},[926],{"type":29,"value":507},{"type":23,"tag":265,"props":928,"children":929},{"style":468},[930],{"type":29,"value":931}," createApiClient",{"type":23,"tag":265,"props":933,"children":934},{"style":306},[935],{"type":29,"value":936}," }",{"type":23,"tag":265,"props":938,"children":939},{"style":282},[940],{"type":29,"value":941}," from",{"type":23,"tag":265,"props":943,"children":944},{"style":450},[945],{"type":29,"value":946}," '",{"type":23,"tag":265,"props":948,"children":949},{"style":456},[950],{"type":29,"value":951},"~/shared/api/client",{"type":23,"tag":265,"props":953,"children":954},{"style":450},[955],{"type":29,"value":956},"'\n",{"type":23,"tag":265,"props":958,"children":959},{"class":267,"line":322},[960],{"type":23,"tag":265,"props":961,"children":963},{"emptyLinePlaceholder":962},true,[964],{"type":29,"value":965},"\n",{"type":23,"tag":265,"props":967,"children":968},{"class":267,"line":390},[969,973,977,981,985,989,993],{"type":23,"tag":265,"props":970,"children":971},{"style":282},[972],{"type":29,"value":285},{"type":23,"tag":265,"props":974,"children":975},{"style":288},[976],{"type":29,"value":291},{"type":23,"tag":265,"props":978,"children":979},{"style":294},[980],{"type":29,"value":297},{"type":23,"tag":265,"props":982,"children":983},{"style":300},[984],{"type":29,"value":303},{"type":23,"tag":265,"props":986,"children":987},{"style":306},[988],{"type":29,"value":309},{"type":23,"tag":265,"props":990,"children":991},{"style":288},[992],{"type":29,"value":314},{"type":23,"tag":265,"props":994,"children":995},{"style":306},[996],{"type":29,"value":319},{"type":23,"tag":265,"props":998,"children":999},{"class":267,"line":487},[1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048],{"type":23,"tag":265,"props":1001,"children":1002},{"style":288},[1003],{"type":29,"value":328},{"type":23,"tag":265,"props":1005,"children":1006},{"style":294},[1007],{"type":29,"value":333},{"type":23,"tag":265,"props":1009,"children":1010},{"style":300},[1011],{"type":29,"value":303},{"type":23,"tag":265,"props":1013,"children":1014},{"style":288},[1015],{"type":29,"value":342},{"type":23,"tag":265,"props":1017,"children":1018},{"style":306},[1019],{"type":29,"value":347},{"type":23,"tag":265,"props":1021,"children":1022},{"style":350},[1023],{"type":29,"value":353},{"type":23,"tag":265,"props":1025,"children":1026},{"style":306},[1027],{"type":29,"value":358},{"type":23,"tag":265,"props":1029,"children":1030},{"style":361},[1031],{"type":29,"value":364},{"type":23,"tag":265,"props":1033,"children":1034},{"style":306},[1035],{"type":29,"value":369},{"type":23,"tag":265,"props":1037,"children":1038},{"style":361},[1039],{"type":29,"value":374},{"type":23,"tag":265,"props":1041,"children":1042},{"style":306},[1043],{"type":29,"value":379},{"type":23,"tag":265,"props":1045,"children":1046},{"style":288},[1047],{"type":29,"value":314},{"type":23,"tag":265,"props":1049,"children":1050},{"style":306},[1051],{"type":29,"value":319},{"type":23,"tag":265,"props":1053,"children":1054},{"class":267,"line":496},[1055,1060,1066,1070,1074],{"type":23,"tag":265,"props":1056,"children":1057},{"style":288},[1058],{"type":29,"value":1059},"    const",{"type":23,"tag":265,"props":1061,"children":1063},{"style":1062},"--shiki-default:#EEFFFF;--shiki-dark:#E5C07B",[1064],{"type":29,"value":1065}," client",{"type":23,"tag":265,"props":1067,"children":1068},{"style":300},[1069],{"type":29,"value":303},{"type":23,"tag":265,"props":1071,"children":1072},{"style":404},[1073],{"type":29,"value":931},{"type":23,"tag":265,"props":1075,"children":1076},{"style":444},[1077],{"type":29,"value":1078},"()\n",{"type":23,"tag":265,"props":1080,"children":1081},{"class":267,"line":519},[1082,1086,1090,1094,1099,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1145,1149,1153,1157,1161],{"type":23,"tag":265,"props":1083,"children":1084},{"style":282},[1085],{"type":29,"value":396},{"type":23,"tag":265,"props":1087,"children":1088},{"style":282},[1089],{"type":29,"value":401},{"type":23,"tag":265,"props":1091,"children":1092},{"style":1062},[1093],{"type":29,"value":1065},{"type":23,"tag":265,"props":1095,"children":1096},{"style":306},[1097],{"type":29,"value":1098},".",{"type":23,"tag":265,"props":1100,"children":1101},{"style":404},[1102],{"type":29,"value":1103},"get",{"type":23,"tag":265,"props":1105,"children":1106},{"style":306},[1107],{"type":29,"value":412},{"type":23,"tag":265,"props":1109,"children":1110},{"style":361},[1111],{"type":29,"value":417},{"type":23,"tag":265,"props":1113,"children":1114},{"style":306},[1115],{"type":29,"value":412},{"type":23,"tag":265,"props":1117,"children":1118},{"style":361},[1119],{"type":29,"value":426},{"type":23,"tag":265,"props":1121,"children":1122},{"style":306},[1123],{"type":29,"value":431},{"type":23,"tag":265,"props":1125,"children":1126},{"style":361},[1127],{"type":29,"value":436},{"type":23,"tag":265,"props":1129,"children":1130},{"style":306},[1131],{"type":29,"value":441},{"type":23,"tag":265,"props":1133,"children":1134},{"style":444},[1135],{"type":29,"value":447},{"type":23,"tag":265,"props":1137,"children":1138},{"style":450},[1139],{"type":29,"value":453},{"type":23,"tag":265,"props":1141,"children":1142},{"style":456},[1143],{"type":29,"value":1144},"/articles/",{"type":23,"tag":265,"props":1146,"children":1147},{"style":462},[1148],{"type":29,"value":465},{"type":23,"tag":265,"props":1150,"children":1151},{"style":468},[1152],{"type":29,"value":353},{"type":23,"tag":265,"props":1154,"children":1155},{"style":462},[1156],{"type":29,"value":475},{"type":23,"tag":265,"props":1158,"children":1159},{"style":450},[1160],{"type":29,"value":453},{"type":23,"tag":265,"props":1162,"children":1163},{"style":444},[1164],{"type":29,"value":484},{"type":23,"tag":265,"props":1166,"children":1168},{"class":267,"line":1167},8,[1169],{"type":23,"tag":265,"props":1170,"children":1171},{"style":306},[1172],{"type":29,"value":493},{"type":23,"tag":265,"props":1174,"children":1176},{"class":267,"line":1175},9,[1177,1181,1185,1189],{"type":23,"tag":265,"props":1178,"children":1179},{"style":282},[1180],{"type":29,"value":502},{"type":23,"tag":265,"props":1182,"children":1183},{"style":306},[1184],{"type":29,"value":507},{"type":23,"tag":265,"props":1186,"children":1187},{"style":468},[1188],{"type":29,"value":333},{"type":23,"tag":265,"props":1190,"children":1191},{"style":306},[1192],{"type":29,"value":516},{"type":23,"tag":265,"props":1194,"children":1196},{"class":267,"line":1195},10,[1197],{"type":23,"tag":265,"props":1198,"children":1199},{"style":306},[1200],{"type":29,"value":525},{"type":23,"tag":32,"props":1202,"children":1203},{},[1204,1208,1209,1215],{"type":23,"tag":36,"props":1205,"children":1206},{},[1207],{"type":29,"value":892},{"type":29,"value":94},{"type":23,"tag":44,"props":1210,"children":1212},{"className":1211},[],[1213],{"type":29,"value":1214},"nuxt/app/composables/useArticleNavigation.ts",{"type":29,"value":1216},"（预加载逻辑）",{"type":23,"tag":130,"props":1218,"children":1220},{"className":257,"code":1219,"language":259,"meta":18,"style":18},"// ✅ 修改后 — 同样使用共享 API 客户端\nimport { createApiClient } from '~/shared/api/client'\n\n// 在预加载代码中:\nconst client = createApiClient()\nvoid client.get\u003CArticleApiResponse>(`/articles/${articleId}`)\n  .then(...)\n",[1221],{"type":23,"tag":44,"props":1222,"children":1223},{"__ignoreMap":18},[1224,1232,1267,1274,1282,1307,1374],{"type":23,"tag":265,"props":1225,"children":1226},{"class":267,"line":268},[1227],{"type":23,"tag":265,"props":1228,"children":1229},{"style":272},[1230],{"type":29,"value":1231},"// ✅ 修改后 — 同样使用共享 API 客户端\n",{"type":23,"tag":265,"props":1233,"children":1234},{"class":267,"line":278},[1235,1239,1243,1247,1251,1255,1259,1263],{"type":23,"tag":265,"props":1236,"children":1237},{"style":282},[1238],{"type":29,"value":922},{"type":23,"tag":265,"props":1240,"children":1241},{"style":306},[1242],{"type":29,"value":507},{"type":23,"tag":265,"props":1244,"children":1245},{"style":468},[1246],{"type":29,"value":931},{"type":23,"tag":265,"props":1248,"children":1249},{"style":306},[1250],{"type":29,"value":936},{"type":23,"tag":265,"props":1252,"children":1253},{"style":282},[1254],{"type":29,"value":941},{"type":23,"tag":265,"props":1256,"children":1257},{"style":450},[1258],{"type":29,"value":946},{"type":23,"tag":265,"props":1260,"children":1261},{"style":456},[1262],{"type":29,"value":951},{"type":23,"tag":265,"props":1264,"children":1265},{"style":450},[1266],{"type":29,"value":956},{"type":23,"tag":265,"props":1268,"children":1269},{"class":267,"line":322},[1270],{"type":23,"tag":265,"props":1271,"children":1272},{"emptyLinePlaceholder":962},[1273],{"type":29,"value":965},{"type":23,"tag":265,"props":1275,"children":1276},{"class":267,"line":390},[1277],{"type":23,"tag":265,"props":1278,"children":1279},{"style":272},[1280],{"type":29,"value":1281},"// 在预加载代码中:\n",{"type":23,"tag":265,"props":1283,"children":1284},{"class":267,"line":487},[1285,1290,1294,1298,1302],{"type":23,"tag":265,"props":1286,"children":1287},{"style":288},[1288],{"type":29,"value":1289},"const",{"type":23,"tag":265,"props":1291,"children":1292},{"style":1062},[1293],{"type":29,"value":1065},{"type":23,"tag":265,"props":1295,"children":1296},{"style":300},[1297],{"type":29,"value":303},{"type":23,"tag":265,"props":1299,"children":1300},{"style":404},[1301],{"type":29,"value":931},{"type":23,"tag":265,"props":1303,"children":1305},{"style":1304},"--shiki-default:#EEFFFF;--shiki-dark:#ABB2BF",[1306],{"type":29,"value":1078},{"type":23,"tag":265,"props":1308,"children":1309},{"class":267,"line":496},[1310,1315,1319,1323,1327,1331,1336,1341,1345,1349,1353,1357,1362,1366,1370],{"type":23,"tag":265,"props":1311,"children":1312},{"style":462},[1313],{"type":29,"value":1314},"void",{"type":23,"tag":265,"props":1316,"children":1317},{"style":1062},[1318],{"type":29,"value":1065},{"type":23,"tag":265,"props":1320,"children":1321},{"style":306},[1322],{"type":29,"value":1098},{"type":23,"tag":265,"props":1324,"children":1325},{"style":404},[1326],{"type":29,"value":1103},{"type":23,"tag":265,"props":1328,"children":1329},{"style":306},[1330],{"type":29,"value":412},{"type":23,"tag":265,"props":1332,"children":1333},{"style":361},[1334],{"type":29,"value":1335},"ArticleApiResponse",{"type":23,"tag":265,"props":1337,"children":1338},{"style":306},[1339],{"type":29,"value":1340},">",{"type":23,"tag":265,"props":1342,"children":1343},{"style":1304},[1344],{"type":29,"value":447},{"type":23,"tag":265,"props":1346,"children":1347},{"style":450},[1348],{"type":29,"value":453},{"type":23,"tag":265,"props":1350,"children":1351},{"style":456},[1352],{"type":29,"value":1144},{"type":23,"tag":265,"props":1354,"children":1355},{"style":462},[1356],{"type":29,"value":465},{"type":23,"tag":265,"props":1358,"children":1359},{"style":468},[1360],{"type":29,"value":1361},"articleId",{"type":23,"tag":265,"props":1363,"children":1364},{"style":462},[1365],{"type":29,"value":475},{"type":23,"tag":265,"props":1367,"children":1368},{"style":450},[1369],{"type":29,"value":453},{"type":23,"tag":265,"props":1371,"children":1372},{"style":1304},[1373],{"type":29,"value":484},{"type":23,"tag":265,"props":1375,"children":1376},{"class":267,"line":519},[1377,1382,1387,1391,1396],{"type":23,"tag":265,"props":1378,"children":1379},{"style":306},[1380],{"type":29,"value":1381},"  .",{"type":23,"tag":265,"props":1383,"children":1384},{"style":404},[1385],{"type":29,"value":1386},"then",{"type":23,"tag":265,"props":1388,"children":1389},{"style":1304},[1390],{"type":29,"value":447},{"type":23,"tag":265,"props":1392,"children":1393},{"style":306},[1394],{"type":29,"value":1395},"...",{"type":23,"tag":265,"props":1397,"children":1398},{"style":1304},[1399],{"type":29,"value":484},{"type":23,"tag":223,"props":1401,"children":1403},{"id":1402},"_42-修复生效原理",[1404],{"type":29,"value":1405},"4.2 修复生效原理",{"type":23,"tag":32,"props":1407,"children":1408},{},[1409,1411,1417,1419,1425,1427,1432],{"type":29,"value":1410},"共享 API 客户端 (",{"type":23,"tag":44,"props":1412,"children":1414},{"className":1413},[],[1415],{"type":29,"value":1416},"createApiClient",{"type":29,"value":1418},") 通过 ",{"type":23,"tag":44,"props":1420,"children":1422},{"className":1421},[],[1423],{"type":29,"value":1424},"resolveApiBaseURL()",{"type":29,"value":1426}," 在不同环境解析到正确的",{"type":23,"tag":36,"props":1428,"children":1429},{},[1430],{"type":29,"value":1431},"绝对地址",{"type":29,"value":94},{"type":23,"tag":130,"props":1434,"children":1436},{"className":257,"code":1435,"language":259,"meta":18,"style":18},"// shared/api/base-url.ts — URL 解析逻辑\nexport function resolveApiBaseURL(): string {\n  const config = useRuntimeConfig()\n  const apiBaseServer = config.apiBaseServer  // 服务端私有配置\n\n  // SSR 时：使用服务端专用地址（内网直连后端）\n  if (process.server && apiBaseServer) {\n    return apiBaseServer  // → 'http://127.0.0.1:5000/api'\n  }\n\n  // CSR 时：使用公共地址（经过 Nginx 代理）\n  return config.public.apiBase  // → 'https://yourdomain.com/api'\n}\n",[1437],{"type":23,"tag":44,"props":1438,"children":1439},{"__ignoreMap":18},[1440,1448,1478,1503,1537,1544,1552,1597,1613,1620,1627,1636,1671],{"type":23,"tag":265,"props":1441,"children":1442},{"class":267,"line":268},[1443],{"type":23,"tag":265,"props":1444,"children":1445},{"style":272},[1446],{"type":29,"value":1447},"// shared/api/base-url.ts — URL 解析逻辑\n",{"type":23,"tag":265,"props":1449,"children":1450},{"class":267,"line":278},[1451,1455,1460,1465,1470,1474],{"type":23,"tag":265,"props":1452,"children":1453},{"style":282},[1454],{"type":29,"value":285},{"type":23,"tag":265,"props":1456,"children":1457},{"style":288},[1458],{"type":29,"value":1459}," function",{"type":23,"tag":265,"props":1461,"children":1462},{"style":404},[1463],{"type":29,"value":1464}," resolveApiBaseURL",{"type":23,"tag":265,"props":1466,"children":1467},{"style":306},[1468],{"type":29,"value":1469},"():",{"type":23,"tag":265,"props":1471,"children":1472},{"style":361},[1473],{"type":29,"value":364},{"type":23,"tag":265,"props":1475,"children":1476},{"style":306},[1477],{"type":29,"value":319},{"type":23,"tag":265,"props":1479,"children":1480},{"class":267,"line":322},[1481,1485,1490,1494,1499],{"type":23,"tag":265,"props":1482,"children":1483},{"style":288},[1484],{"type":29,"value":328},{"type":23,"tag":265,"props":1486,"children":1487},{"style":1062},[1488],{"type":29,"value":1489}," config",{"type":23,"tag":265,"props":1491,"children":1492},{"style":300},[1493],{"type":29,"value":303},{"type":23,"tag":265,"props":1495,"children":1496},{"style":404},[1497],{"type":29,"value":1498}," useRuntimeConfig",{"type":23,"tag":265,"props":1500,"children":1501},{"style":444},[1502],{"type":29,"value":1078},{"type":23,"tag":265,"props":1504,"children":1505},{"class":267,"line":390},[1506,1510,1515,1519,1523,1527,1532],{"type":23,"tag":265,"props":1507,"children":1508},{"style":288},[1509],{"type":29,"value":328},{"type":23,"tag":265,"props":1511,"children":1512},{"style":1062},[1513],{"type":29,"value":1514}," apiBaseServer",{"type":23,"tag":265,"props":1516,"children":1517},{"style":300},[1518],{"type":29,"value":303},{"type":23,"tag":265,"props":1520,"children":1521},{"style":1062},[1522],{"type":29,"value":1489},{"type":23,"tag":265,"props":1524,"children":1525},{"style":306},[1526],{"type":29,"value":1098},{"type":23,"tag":265,"props":1528,"children":1529},{"style":468},[1530],{"type":29,"value":1531},"apiBaseServer",{"type":23,"tag":265,"props":1533,"children":1534},{"style":272},[1535],{"type":29,"value":1536},"  // 服务端私有配置\n",{"type":23,"tag":265,"props":1538,"children":1539},{"class":267,"line":487},[1540],{"type":23,"tag":265,"props":1541,"children":1542},{"emptyLinePlaceholder":962},[1543],{"type":29,"value":965},{"type":23,"tag":265,"props":1545,"children":1546},{"class":267,"line":496},[1547],{"type":23,"tag":265,"props":1548,"children":1549},{"style":272},[1550],{"type":29,"value":1551},"  // SSR 时：使用服务端专用地址（内网直连后端）\n",{"type":23,"tag":265,"props":1553,"children":1554},{"class":267,"line":519},[1555,1560,1564,1569,1573,1578,1583,1587,1592],{"type":23,"tag":265,"props":1556,"children":1557},{"style":282},[1558],{"type":29,"value":1559},"  if",{"type":23,"tag":265,"props":1561,"children":1562},{"style":444},[1563],{"type":29,"value":347},{"type":23,"tag":265,"props":1565,"children":1566},{"style":1062},[1567],{"type":29,"value":1568},"process",{"type":23,"tag":265,"props":1570,"children":1571},{"style":306},[1572],{"type":29,"value":1098},{"type":23,"tag":265,"props":1574,"children":1575},{"style":468},[1576],{"type":29,"value":1577},"server",{"type":23,"tag":265,"props":1579,"children":1580},{"style":300},[1581],{"type":29,"value":1582}," &&",{"type":23,"tag":265,"props":1584,"children":1585},{"style":468},[1586],{"type":29,"value":1514},{"type":23,"tag":265,"props":1588,"children":1589},{"style":444},[1590],{"type":29,"value":1591},") ",{"type":23,"tag":265,"props":1593,"children":1594},{"style":306},[1595],{"type":29,"value":1596},"{\n",{"type":23,"tag":265,"props":1598,"children":1599},{"class":267,"line":1167},[1600,1604,1608],{"type":23,"tag":265,"props":1601,"children":1602},{"style":282},[1603],{"type":29,"value":396},{"type":23,"tag":265,"props":1605,"children":1606},{"style":468},[1607],{"type":29,"value":1514},{"type":23,"tag":265,"props":1609,"children":1610},{"style":272},[1611],{"type":29,"value":1612},"  // → 'http://127.0.0.1:5000/api'\n",{"type":23,"tag":265,"props":1614,"children":1615},{"class":267,"line":1175},[1616],{"type":23,"tag":265,"props":1617,"children":1618},{"style":306},[1619],{"type":29,"value":493},{"type":23,"tag":265,"props":1621,"children":1622},{"class":267,"line":1195},[1623],{"type":23,"tag":265,"props":1624,"children":1625},{"emptyLinePlaceholder":962},[1626],{"type":29,"value":965},{"type":23,"tag":265,"props":1628,"children":1630},{"class":267,"line":1629},11,[1631],{"type":23,"tag":265,"props":1632,"children":1633},{"style":272},[1634],{"type":29,"value":1635},"  // CSR 时：使用公共地址（经过 Nginx 代理）\n",{"type":23,"tag":265,"props":1637,"children":1639},{"class":267,"line":1638},12,[1640,1644,1648,1652,1657,1661,1666],{"type":23,"tag":265,"props":1641,"children":1642},{"style":282},[1643],{"type":29,"value":502},{"type":23,"tag":265,"props":1645,"children":1646},{"style":1062},[1647],{"type":29,"value":1489},{"type":23,"tag":265,"props":1649,"children":1650},{"style":306},[1651],{"type":29,"value":1098},{"type":23,"tag":265,"props":1653,"children":1654},{"style":1062},[1655],{"type":29,"value":1656},"public",{"type":23,"tag":265,"props":1658,"children":1659},{"style":306},[1660],{"type":29,"value":1098},{"type":23,"tag":265,"props":1662,"children":1663},{"style":468},[1664],{"type":29,"value":1665},"apiBase",{"type":23,"tag":265,"props":1667,"children":1668},{"style":272},[1669],{"type":29,"value":1670},"  // → 'https://yourdomain.com/api'\n",{"type":23,"tag":265,"props":1672,"children":1674},{"class":267,"line":1673},13,[1675],{"type":23,"tag":265,"props":1676,"children":1677},{"style":306},[1678],{"type":29,"value":525},{"type":23,"tag":32,"props":1680,"children":1681},{},[1682,1684,1690],{"type":29,"value":1683},"这样 ",{"type":23,"tag":44,"props":1685,"children":1687},{"className":1686},[],[1688],{"type":29,"value":1689},"createApiClient().get('/articles/77')",{"type":29,"value":1691}," 拼接出的完整 URL 为：",{"type":23,"tag":552,"props":1693,"children":1694},{},[1695,1715],{"type":23,"tag":556,"props":1696,"children":1697},{},[1698],{"type":23,"tag":560,"props":1699,"children":1700},{},[1701,1705,1710],{"type":23,"tag":564,"props":1702,"children":1703},{},[1704],{"type":29,"value":568},{"type":23,"tag":564,"props":1706,"children":1707},{},[1708],{"type":29,"value":1709},"解析结果",{"type":23,"tag":564,"props":1711,"children":1712},{},[1713],{"type":29,"value":1714},"请求方式",{"type":23,"tag":581,"props":1716,"children":1717},{},[1718,1740],{"type":23,"tag":560,"props":1719,"children":1720},{},[1721,1726,1735],{"type":23,"tag":588,"props":1722,"children":1723},{},[1724],{"type":29,"value":1725},"SSR（服务端）",{"type":23,"tag":588,"props":1727,"children":1728},{},[1729],{"type":23,"tag":44,"props":1730,"children":1732},{"className":1731},[],[1733],{"type":29,"value":1734},"http://127.0.0.1:5000/api/articles/77",{"type":23,"tag":588,"props":1736,"children":1737},{},[1738],{"type":29,"value":1739},"Node.js 通过 HTTP 直接请求本机后端",{"type":23,"tag":560,"props":1741,"children":1742},{},[1743,1748,1756],{"type":23,"tag":588,"props":1744,"children":1745},{},[1746],{"type":29,"value":1747},"CSR（浏览器）",{"type":23,"tag":588,"props":1749,"children":1750},{},[1751],{"type":23,"tag":44,"props":1752,"children":1754},{"className":1753},[],[1755],{"type":29,"value":860},{"type":23,"tag":588,"props":1757,"children":1758},{},[1759],{"type":29,"value":1760},"浏览器通过 Nginx 代理到后端",{"type":23,"tag":32,"props":1762,"children":1763},{},[1764,1769,1771,1776,1778,1783],{"type":23,"tag":36,"props":1765,"children":1766},{},[1767],{"type":29,"value":1768},"关键差异",{"type":29,"value":1770},"：使用绝对 URL 的 ",{"type":23,"tag":44,"props":1772,"children":1774},{"className":1773},[],[1775],{"type":29,"value":234},{"type":29,"value":1777}," 会走",{"type":23,"tag":36,"props":1779,"children":1780},{},[1781],{"type":29,"value":1782},"真实 HTTP 网络请求",{"type":29,"value":1784},"，绕过 Nitro 的内部路由机制，直接连接到 .NET 后端。",{"type":23,"tag":223,"props":1786,"children":1788},{"id":1787},"_43-配套基础设施已在此前配置完成",[1789],{"type":29,"value":1790},"4.3 配套基础设施（已在此前配置完成）",{"type":23,"tag":32,"props":1792,"children":1793},{},[1794],{"type":29,"value":1795},"修复能生效还依赖以下前置配置：",{"type":23,"tag":552,"props":1797,"children":1798},{},[1799,1825],{"type":23,"tag":556,"props":1800,"children":1801},{},[1802],{"type":23,"tag":560,"props":1803,"children":1804},{},[1805,1810,1815,1820],{"type":23,"tag":564,"props":1806,"children":1807},{},[1808],{"type":29,"value":1809},"配置项",{"type":23,"tag":564,"props":1811,"children":1812},{},[1813],{"type":29,"value":1814},"位置",{"type":23,"tag":564,"props":1816,"children":1817},{},[1818],{"type":29,"value":1819},"值",{"type":23,"tag":564,"props":1821,"children":1822},{},[1823],{"type":29,"value":1824},"作用",{"type":23,"tag":581,"props":1826,"children":1827},{},[1828,1865,1905],{"type":23,"tag":560,"props":1829,"children":1830},{},[1831,1840,1851,1860],{"type":23,"tag":588,"props":1832,"children":1833},{},[1834],{"type":23,"tag":44,"props":1835,"children":1837},{"className":1836},[],[1838],{"type":29,"value":1839},"NUXT_API_BASE_URL",{"type":23,"tag":588,"props":1841,"children":1842},{},[1843,1849],{"type":23,"tag":44,"props":1844,"children":1846},{"className":1845},[],[1847],{"type":29,"value":1848},"ecosystem.config.js",{"type":29,"value":1850}," (PM2 环境变量)",{"type":23,"tag":588,"props":1852,"children":1853},{},[1854],{"type":23,"tag":44,"props":1855,"children":1857},{"className":1856},[],[1858],{"type":29,"value":1859},"http://127.0.0.1:5000/api",{"type":23,"tag":588,"props":1861,"children":1862},{},[1863],{"type":29,"value":1864},"SSR 时的后端内网地址",{"type":23,"tag":560,"props":1866,"children":1867},{},[1868,1877,1885,1895],{"type":23,"tag":588,"props":1869,"children":1870},{},[1871],{"type":23,"tag":44,"props":1872,"children":1874},{"className":1873},[],[1875],{"type":29,"value":1876},"runtimeConfig.apiBaseServer",{"type":23,"tag":588,"props":1878,"children":1879},{},[1880],{"type":23,"tag":44,"props":1881,"children":1883},{"className":1882},[],[1884],{"type":29,"value":743},{"type":23,"tag":588,"props":1886,"children":1887},{},[1888,1890],{"type":29,"value":1889},"读取 ",{"type":23,"tag":44,"props":1891,"children":1893},{"className":1892},[],[1894],{"type":29,"value":1839},{"type":23,"tag":588,"props":1896,"children":1897},{},[1898,1900],{"type":29,"value":1899},"将环境变量暴露给 ",{"type":23,"tag":44,"props":1901,"children":1903},{"className":1902},[],[1904],{"type":29,"value":1424},{"type":23,"tag":560,"props":1906,"children":1907},{},[1908,1919,1928,1933],{"type":23,"tag":588,"props":1909,"children":1910},{},[1911,1917],{"type":23,"tag":44,"props":1912,"children":1914},{"className":1913},[],[1915],{"type":29,"value":1916},"UseHttpsRedirection",{"type":29,"value":1918}," 改为 dev-only",{"type":23,"tag":588,"props":1920,"children":1921},{},[1922],{"type":23,"tag":44,"props":1923,"children":1925},{"className":1924},[],[1926],{"type":29,"value":1927},"Program.cs",{"type":23,"tag":588,"props":1929,"children":1930},{},[1931],{"type":29,"value":1932},"仅在 Development 环境启用",{"type":23,"tag":588,"props":1934,"children":1935},{},[1936],{"type":29,"value":1937},"避免 SSR 的 HTTP 请求被后端 301 重定向到 HTTPS",{"type":23,"tag":223,"props":1939,"children":1941},{"id":1940},"_44-数据流对比",[1942],{"type":29,"value":1943},"4.4 数据流对比",{"type":23,"tag":32,"props":1945,"children":1946},{},[1947,1952],{"type":23,"tag":36,"props":1948,"children":1949},{},[1950],{"type":29,"value":1951},"修复前",{"type":29,"value":1953},"（SSR 失败路径）：",{"type":23,"tag":130,"props":1955,"children":1958},{"className":1956,"code":1957,"language":29},[133],"Nuxt SSR → $fetch('/api/articles/77')\n         → Nitro 内部查找 server route\n         → 404/异常 → SSR 崩溃 → 502\n",[1959],{"type":23,"tag":44,"props":1960,"children":1961},{"__ignoreMap":18},[1962],{"type":29,"value":1957},{"type":23,"tag":32,"props":1964,"children":1965},{},[1966,1971],{"type":23,"tag":36,"props":1967,"children":1968},{},[1969],{"type":29,"value":1970},"修复后",{"type":29,"value":1972},"（SSR 正常路径）：",{"type":23,"tag":130,"props":1974,"children":1977},{"className":1975,"code":1976,"language":29},[133],"Nuxt SSR → createApiClient().get('/articles/77')\n         → resolveApiBaseURL() → 'http://127.0.0.1:5000/api'\n         → $fetch('http://127.0.0.1:5000/api/articles/77')\n         → Node.js HTTP 请求 → .NET 后端\n         → 返回文章数据 → SSR 正常渲染 → 200\n",[1978],{"type":23,"tag":44,"props":1979,"children":1980},{"__ignoreMap":18},[1981],{"type":29,"value":1976},{"type":23,"tag":24,"props":1983,"children":1985},{"id":1984},"_5-其他同类文件现状",[1986],{"type":29,"value":1987},"5. 其他同类文件现状",{"type":23,"tag":32,"props":1989,"children":1990},{},[1991,1993,1998],{"type":29,"value":1992},"排查结果：项目中其他使用 API 的 repository ",{"type":23,"tag":36,"props":1994,"children":1995},{},[1996],{"type":29,"value":1997},"已经正确使用",{"type":29,"value":1999},"了共享客户端：",{"type":23,"tag":552,"props":2001,"children":2002},{},[2003,2023],{"type":23,"tag":556,"props":2004,"children":2005},{},[2006],{"type":23,"tag":560,"props":2007,"children":2008},{},[2009,2013,2018],{"type":23,"tag":564,"props":2010,"children":2011},{},[2012],{"type":29,"value":892},{"type":23,"tag":564,"props":2014,"children":2015},{},[2016],{"type":29,"value":2017},"使用方式",{"type":23,"tag":564,"props":2019,"children":2020},{},[2021],{"type":29,"value":2022},"状态",{"type":23,"tag":581,"props":2024,"children":2025},{},[2026,2052,2076,2100,2124,2161],{"type":23,"tag":560,"props":2027,"children":2028},{},[2029,2038,2047],{"type":23,"tag":588,"props":2030,"children":2031},{},[2032],{"type":23,"tag":44,"props":2033,"children":2035},{"className":2034},[],[2036],{"type":29,"value":2037},"features/article-list/services/articles.repository.ts",{"type":23,"tag":588,"props":2039,"children":2040},{},[2041],{"type":23,"tag":44,"props":2042,"children":2044},{"className":2043},[],[2045],{"type":29,"value":2046},"createApiClient()",{"type":23,"tag":588,"props":2048,"children":2049},{},[2050],{"type":29,"value":2051},"✅ 正确",{"type":23,"tag":560,"props":2053,"children":2054},{},[2055,2064,2072],{"type":23,"tag":588,"props":2056,"children":2057},{},[2058],{"type":23,"tag":44,"props":2059,"children":2061},{"className":2060},[],[2062],{"type":29,"value":2063},"features/article-list/composables/useArticleCacheFeature.ts",{"type":23,"tag":588,"props":2065,"children":2066},{},[2067],{"type":23,"tag":44,"props":2068,"children":2070},{"className":2069},[],[2071],{"type":29,"value":2046},{"type":23,"tag":588,"props":2073,"children":2074},{},[2075],{"type":29,"value":2051},{"type":23,"tag":560,"props":2077,"children":2078},{},[2079,2088,2096],{"type":23,"tag":588,"props":2080,"children":2081},{},[2082],{"type":23,"tag":44,"props":2083,"children":2085},{"className":2084},[],[2086],{"type":29,"value":2087},"features/gallery-public/",{"type":23,"tag":588,"props":2089,"children":2090},{},[2091],{"type":23,"tag":44,"props":2092,"children":2094},{"className":2093},[],[2095],{"type":29,"value":2046},{"type":23,"tag":588,"props":2097,"children":2098},{},[2099],{"type":29,"value":2051},{"type":23,"tag":560,"props":2101,"children":2102},{},[2103,2112,2120],{"type":23,"tag":588,"props":2104,"children":2105},{},[2106],{"type":23,"tag":44,"props":2107,"children":2109},{"className":2108},[],[2110],{"type":29,"value":2111},"features/gallery-admin/",{"type":23,"tag":588,"props":2113,"children":2114},{},[2115],{"type":23,"tag":44,"props":2116,"children":2118},{"className":2117},[],[2119],{"type":29,"value":2046},{"type":23,"tag":588,"props":2121,"children":2122},{},[2123],{"type":29,"value":2051},{"type":23,"tag":560,"props":2125,"children":2126},{},[2127,2139,2156],{"type":23,"tag":588,"props":2128,"children":2129},{},[2130],{"type":23,"tag":36,"props":2131,"children":2132},{},[2133],{"type":23,"tag":44,"props":2134,"children":2136},{"className":2135},[],[2137],{"type":29,"value":2138},"features/article-detail/services/articleDetail.repository.ts",{"type":23,"tag":588,"props":2140,"children":2141},{},[2142,2144,2149,2151],{"type":29,"value":2143},"裸 ",{"type":23,"tag":44,"props":2145,"children":2147},{"className":2146},[],[2148],{"type":29,"value":234},{"type":29,"value":2150}," → ",{"type":23,"tag":36,"props":2152,"children":2153},{},[2154],{"type":29,"value":2155},"已修复",{"type":23,"tag":588,"props":2157,"children":2158},{},[2159],{"type":29,"value":2160},"✅ 已修复",{"type":23,"tag":560,"props":2162,"children":2163},{},[2164,2176,2190],{"type":23,"tag":588,"props":2165,"children":2166},{},[2167],{"type":23,"tag":36,"props":2168,"children":2169},{},[2170],{"type":23,"tag":44,"props":2171,"children":2173},{"className":2172},[],[2174],{"type":29,"value":2175},"composables/useArticleNavigation.ts",{"type":23,"tag":588,"props":2177,"children":2178},{},[2179,2180,2185,2186],{"type":29,"value":2143},{"type":23,"tag":44,"props":2181,"children":2183},{"className":2182},[],[2184],{"type":29,"value":234},{"type":29,"value":2150},{"type":23,"tag":36,"props":2187,"children":2188},{},[2189],{"type":29,"value":2155},{"type":23,"tag":588,"props":2191,"children":2192},{},[2193],{"type":29,"value":2160},{"type":23,"tag":24,"props":2195,"children":2197},{"id":2196},"_6-验证结果",[2198],{"type":29,"value":2199},"6. 验证结果",{"type":23,"tag":32,"props":2201,"children":2202},{},[2203,2205,2210,2212,2217],{"type":29,"value":2204},"本地同时启动后端（",{"type":23,"tag":44,"props":2206,"children":2208},{"className":2207},[],[2209],{"type":29,"value":614},{"type":29,"value":2211},"）和 Nuxt dev（",{"type":23,"tag":44,"props":2213,"children":2215},{"className":2214},[],[2216],{"type":29,"value":158},{"type":29,"value":2218},"），模拟 SSR 直接访问：",{"type":23,"tag":552,"props":2220,"children":2221},{},[2222,2248],{"type":23,"tag":556,"props":2223,"children":2224},{},[2225],{"type":23,"tag":560,"props":2226,"children":2227},{},[2228,2233,2238,2243],{"type":23,"tag":564,"props":2229,"children":2230},{},[2231],{"type":29,"value":2232},"测试 URL",{"type":23,"tag":564,"props":2234,"children":2235},{},[2236],{"type":29,"value":2237},"状态码",{"type":23,"tag":564,"props":2239,"children":2240},{},[2241],{"type":29,"value":2242},"响应大小",{"type":23,"tag":564,"props":2244,"children":2245},{},[2246],{"type":29,"value":2247},"结果",{"type":23,"tag":581,"props":2249,"children":2250},{},[2251,2278,2302,2327],{"type":23,"tag":560,"props":2252,"children":2253},{},[2254,2263,2268,2273],{"type":23,"tag":588,"props":2255,"children":2256},{},[2257],{"type":23,"tag":44,"props":2258,"children":2260},{"className":2259},[],[2261],{"type":29,"value":2262},"http://127.0.0.1:3000/article/77",{"type":23,"tag":588,"props":2264,"children":2265},{},[2266],{"type":29,"value":2267},"200",{"type":23,"tag":588,"props":2269,"children":2270},{},[2271],{"type":29,"value":2272},"166,670 chars",{"type":23,"tag":588,"props":2274,"children":2275},{},[2276],{"type":29,"value":2277},"✅ SSR 正常",{"type":23,"tag":560,"props":2279,"children":2280},{},[2281,2289,2293,2298],{"type":23,"tag":588,"props":2282,"children":2283},{},[2284],{"type":23,"tag":44,"props":2285,"children":2287},{"className":2286},[],[2288],{"type":29,"value":2262},{"type":23,"tag":588,"props":2290,"children":2291},{},[2292],{"type":29,"value":2267},{"type":23,"tag":588,"props":2294,"children":2295},{},[2296],{"type":29,"value":2297},"207,752 chars",{"type":23,"tag":588,"props":2299,"children":2300},{},[2301],{"type":29,"value":2277},{"type":23,"tag":560,"props":2303,"children":2304},{},[2305,2314,2318,2323],{"type":23,"tag":588,"props":2306,"children":2307},{},[2308],{"type":23,"tag":44,"props":2309,"children":2311},{"className":2310},[],[2312],{"type":29,"value":2313},"http://127.0.0.1:3000/tutorials",{"type":23,"tag":588,"props":2315,"children":2316},{},[2317],{"type":29,"value":2267},{"type":23,"tag":588,"props":2319,"children":2320},{},[2321],{"type":29,"value":2322},"34,393 chars",{"type":23,"tag":588,"props":2324,"children":2325},{},[2326],{"type":29,"value":2277},{"type":23,"tag":560,"props":2328,"children":2329},{},[2330,2339,2343,2348],{"type":23,"tag":588,"props":2331,"children":2332},{},[2333],{"type":23,"tag":44,"props":2334,"children":2336},{"className":2335},[],[2337],{"type":29,"value":2338},"http://127.0.0.1:3000/",{"type":23,"tag":588,"props":2340,"children":2341},{},[2342],{"type":29,"value":2267},{"type":23,"tag":588,"props":2344,"children":2345},{},[2346],{"type":29,"value":2347},"59,534 chars",{"type":23,"tag":588,"props":2349,"children":2350},{},[2351],{"type":29,"value":2277},{"type":23,"tag":24,"props":2353,"children":2355},{"id":2354},"_7-经验总结",[2356],{"type":29,"value":2357},"7. 经验总结",{"type":23,"tag":223,"props":2359,"children":2361},{"id":2360},"核心教训",[2362],{"type":29,"value":2360},{"type":23,"tag":2364,"props":2365,"children":2366},"blockquote",{},[2367],{"type":23,"tag":32,"props":2368,"children":2369},{},[2370],{"type":23,"tag":36,"props":2371,"children":2372},{},[2373,2375,2380],{"type":29,"value":2374},"在 Nuxt 3 SSR 中，",{"type":23,"tag":44,"props":2376,"children":2378},{"className":2377},[],[2379],{"type":29,"value":234},{"type":29,"value":2381}," 的相对路径和绝对路径行为截然不同。",{"type":23,"tag":96,"props":2383,"children":2384},{},[2385,2409],{"type":23,"tag":100,"props":2386,"children":2387},{},[2388,2393,2394,2400,2402,2407],{"type":23,"tag":36,"props":2389,"children":2390},{},[2391],{"type":29,"value":2392},"相对路径",{"type":29,"value":639},{"type":23,"tag":44,"props":2395,"children":2397},{"className":2396},[],[2398],{"type":29,"value":2399},"$fetch('/api/xxx')",{"type":29,"value":2401},"：SSR 时走 Nitro 内部路由（进程内直调），",{"type":23,"tag":36,"props":2403,"children":2404},{},[2405],{"type":29,"value":2406},"不",{"type":29,"value":2408}," 产生网络请求",{"type":23,"tag":100,"props":2410,"children":2411},{},[2412,2417,2418,2424],{"type":23,"tag":36,"props":2413,"children":2414},{},[2415],{"type":29,"value":2416},"绝对路径",{"type":29,"value":639},{"type":23,"tag":44,"props":2419,"children":2421},{"className":2420},[],[2422],{"type":29,"value":2423},"$fetch('http://xxx/api/xxx')",{"type":29,"value":2425},"：SSR 时走真实 HTTP 网络请求，连接到目标服务",{"type":23,"tag":223,"props":2427,"children":2429},{"id":2428},"防范措施",[2430],{"type":29,"value":2428},{"type":23,"tag":822,"props":2432,"children":2433},{},[2434,2458,2471],{"type":23,"tag":100,"props":2435,"children":2436},{},[2437,2442,2444,2449,2451,2456],{"type":23,"tag":36,"props":2438,"children":2439},{},[2440],{"type":29,"value":2441},"所有 API 调用统一通过共享客户端",{"type":29,"value":2443},"（",{"type":23,"tag":44,"props":2445,"children":2447},{"className":2446},[],[2448],{"type":29,"value":2046},{"type":29,"value":2450},"），禁止在业务代码中直接使用裸 ",{"type":23,"tag":44,"props":2452,"children":2454},{"className":2453},[],[2455],{"type":29,"value":663},{"type":29,"value":2457}," + 相对路径",{"type":23,"tag":100,"props":2459,"children":2460},{},[2461,2463,2469],{"type":29,"value":2462},"新增 repository 时参照 ",{"type":23,"tag":44,"props":2464,"children":2466},{"className":2465},[],[2467],{"type":29,"value":2468},"articles.repository.ts",{"type":29,"value":2470}," 等已有文件的模式",{"type":23,"tag":100,"props":2472,"children":2473},{},[2474,2476,2482],{"type":29,"value":2475},"可考虑通过 ESLint 自定义规则检测裸 ",{"type":23,"tag":44,"props":2477,"children":2479},{"className":2478},[],[2480],{"type":29,"value":2481},"$fetch('/api/...)",{"type":29,"value":2483}," 模式，在 CI 中拦截",{"type":23,"tag":223,"props":2485,"children":2487},{"id":2486},"站长总结",[2488],{"type":29,"value":2486},{"type":23,"tag":32,"props":2490,"children":2491},{},[2492,2494,2499,2501,2507],{"type":29,"value":2493},"说白了就是Nitro服务器的路由处理逻辑的问题，直接访问文章链接",{"type":23,"tag":44,"props":2495,"children":2497},{"className":2496},[],[2498],{"type":29,"value":49},{"type":29,"value":2500},"，触发了Nitro 内部的fetch处理，其将",{"type":23,"tag":44,"props":2502,"children":2504},{"className":2503},[],[2505],{"type":29,"value":2506},"$fetch('/api/articles/${id}')",{"type":29,"value":2508},"这个相对路径当作内部的路由处理处理",{"type":23,"tag":32,"props":2510,"children":2511},{},[2512],{"type":23,"tag":36,"props":2513,"children":2514},{},[2515],{"type":29,"value":2516},"为什么会这样呢？",{"type":23,"tag":32,"props":2518,"children":2519},{},[2520],{"type":29,"value":2521},"首先是我的Nginx的代理处有以下逻辑：",{"type":23,"tag":130,"props":2523,"children":2526},{"className":2524,"code":2525,"language":29},[133],"    location /api/ {\n        proxy_pass http://127.0.0.1:5000;\n        }\n",[2527],{"type":23,"tag":44,"props":2528,"children":2529},{"__ignoreMap":18},[2530],{"type":29,"value":2525},{"type":23,"tag":32,"props":2532,"children":2533},{},[2534],{"type":29,"value":2535},"正常能拿到数据的请求应该是",{"type":23,"tag":130,"props":2537,"children":2540},{"className":2538,"code":2539,"language":29},[133],"https://yourdomain.com/api/article/77\n",[2541],{"type":23,"tag":44,"props":2542,"children":2543},{"__ignoreMap":18},[2544],{"type":29,"value":2539},{"type":23,"tag":32,"props":2546,"children":2547},{},[2548],{"type":29,"value":2549},"这样才能触发Nginx将请求代理到后端",{"type":23,"tag":32,"props":2551,"children":2552},{},[2553],{"type":29,"value":2554},"而直接请求则是",{"type":23,"tag":130,"props":2556,"children":2559},{"className":2557,"code":2558,"language":29},[133],"https://yourdomain.com/article/77\n",[2560],{"type":23,"tag":44,"props":2561,"children":2562},{"__ignoreMap":18},[2563],{"type":29,"value":2558},{"type":23,"tag":32,"props":2565,"children":2566},{},[2567,2569,2575],{"type":29,"value":2568},"由于缺失了",{"type":23,"tag":44,"props":2570,"children":2572},{"className":2571},[],[2573],{"type":29,"value":2574},"/api/ ",{"type":29,"value":2576},"导致了数据请求没有被Nginx转发至后端，而是直接发送到了Nuxt的Nitro服务器。",{"type":23,"tag":32,"props":2578,"children":2579},{},[2580,2582,2587],{"type":29,"value":2581},"但 Nuxt 没有对应的 ",{"type":23,"tag":44,"props":2583,"children":2585},{"className":2584},[],[2586],{"type":29,"value":701},{"type":29,"value":2588}," server route，也没有代理规则，导致请求失败 → SSR 渲染崩溃 → Nginx 拿不到响应 → Cloudflare 返回 502。",{"type":23,"tag":32,"props":2590,"children":2591},{},[2592],{"type":29,"value":2593},"解决方式就是很简单的解析API从相对直接改为绝对路径。",{"type":23,"tag":32,"props":2595,"children":2596},{},[2597],{"type":29,"value":2598},"这个问题很可笑吧，我是靠AI才发现了这个严重的问题，当时一度以为是后端的问题，结果是请求根本没有发到后端。",{"type":23,"tag":32,"props":2600,"children":2601},{},[2602],{"type":29,"value":2603},"说实话，当时检查一下后端日志应该就能发现问题了。",{"type":23,"tag":32,"props":2605,"children":2606},{},[2607],{"type":29,"value":2608},"我是第一次使用SSR框架，完全不知道 SSR 直接访问文章内容页 502 错误的原因，只觉得有点懵，直到我尝试把问题描述发给AI agent（CloudCode）让其尝试解决。",{"type":23,"tag":32,"props":2610,"children":2611},{},[2612],{"type":29,"value":2613},"然后Agent直接发现关键问题，连解决带测试只花了短短5分钟，还生成了上述详细的报告（原因+解决方式+防范措施），不得不感叹AI的进步速度。",{"type":23,"tag":2615,"props":2616,"children":2617},"style",{},[2618],{"type":29,"value":2619},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":18,"searchDepth":390,"depth":390,"links":2621},[2622,2623,2624,2631,2637,2638,2639],{"id":26,"depth":278,"text":30},{"id":120,"depth":278,"text":123},{"id":218,"depth":278,"text":221,"children":2625},[2626,2628,2629,2630],{"id":225,"depth":322,"text":2627},"3.1 直接原因：$fetch 使用了相对路径",{"id":528,"depth":322,"text":531},{"id":776,"depth":322,"text":779},{"id":791,"depth":322,"text":794},{"id":875,"depth":278,"text":878,"children":2632},[2633,2634,2635,2636],{"id":881,"depth":322,"text":884},{"id":1402,"depth":322,"text":1405},{"id":1787,"depth":322,"text":1790},{"id":1940,"depth":322,"text":1943},{"id":1984,"depth":278,"text":1987},{"id":2196,"depth":278,"text":2199},{"id":2354,"depth":278,"text":2357,"children":2640},[2641,2642,2643],{"id":2360,"depth":322,"text":2360},{"id":2428,"depth":322,"text":2428},{"id":2486,"depth":322,"text":2486},1776415020895]