嵌套路由问题排查
问题描述
访问 /study 路由时报错 unexpected token,子页面 /study/index 无法正常加载。
排查过程
- 检查
study.vue文件本身无报错 - 检查菜单配置无异常
- 查看嵌套路由结构是否正确
Nuxt 的嵌套路由结构与 Vue Router 一致:
pages/
├── study/
│ ├── index.vue → /study(子路由默认页)
│ └── [id].vue → /study/:id(子路由详情页)
└── study.vue → /study(父路由,包含 <NuxtPage />)
text
错误原因
study/index.vue 中引用了一个不存在的 .md 文件(如 test.md),导致编译时找不到该文件。将相关引用注释或删除后,路由恢复正常。
经验总结:逐文件索引排查,定位引用缺失的文件,是解决编译错误的有效方法。
@nuxt/content 在详情页中的使用
Markdown 内容映射
要实现 /study/:id 加载对应的 Markdown 内容,需要建立 pages/ 与 content/ 之间的路径映射关系:
pages/study/[id].vue → content/study/{id}.md
text
例如访问 /study/test,需要存在 content/study/test.md 文件。
页面实现
<!-- pages/study/[id].vue -->
<template>
<div>
<ContentDoc />
</div>
</template>
vue
<ContentDoc> 会根据当前路由路径自动查找对应的 Markdown 文件。
MDC 组件语法迁移
将 Vite 项目中的 Vue 组件迁移到 @nuxt/content 的 Markdown 文件中时,组件语法需要调整。
行内 Props 格式
<!-- 错误:多行格式 -->
::ImageSwiper
title="高阶跃迁"
items="[...]"
::
<!-- 正确:行内格式,属性必须在同一行 -->
::ImageSwiper{title="高阶跃迁" items='[...]'}
::
markdown
关键规则:
- Props 必须使用花括号
{}包裹 - 所有属性必须在同一行,不能有换行
- 属性值用单引号包裹,内部用双引号
YAML 格式替代方案
对于复杂属性,可以使用 YAML 格式:
---
title: 高阶跃迁
items:
- name: "课程1"
- name: "课程2"
---
::ImageSwiper
::
markdown
注意:YAML 格式对缩进和空格要求严格,属性冒号后必须有一个空格,否则解析失败。
组件注册
组件必须在 components/content/ 目录中创建,才能在 Markdown 文件中全局使用:
components/
└── content/
└── ImageSwiper.vue → Markdown 中使用 ::ImageSwiper
text
Vue 3.3 实验特性配置
如果项目使用了 Vue 3.3 的新特性(如 defineModel),需要在 nuxt.config.ts 中开启:
// nuxt.config.ts
export default defineNuxtConfig({
vite: {
vue: {
script: {
defineModel: true,
propsDestructure: true,
}
}
}
})
typescript
Mock 数据与 API 代理
开发环境 API 代理
在开发环境中,需要将 /api/* 的请求转发到 /mock/*,生产环境则正常请求。
中间件实现
// server/middleware/mock.ts
export default defineEventHandler((event) => {
// 1. 判断是否为开发环境
if (process.env.NODE_ENV !== 'development') return
// 2. 获取请求路径
const url = getRequestURL(event)
// 3. 判断是否以 /api 开头
if (url.pathname.startsWith('/api')) {
// 4. 替换路径并代理转发
const target = url.pathname.replace('/api', '/mock')
return proxyRequest(event, `http://localhost:3000${target}`)
}
})
typescript
注意:不能使用
sendRedirect(它只会返回 302 重定向,数据不会传递),应该使用proxyRequest进行服务端代理转发。
proxyRequest 详解
proxyRequest 来自 Nuxt 底层的 H3 库:
import { proxyRequest } from 'h3'
// 基本用法
proxyRequest(event, target, proxyOptions?)
typescript
| 参数 | 说明 |
|---|---|
event | H3 事件对象 |
target | 目标 URL(必须是完整的 URL) |
proxyOptions | 代理选项(headers、cookie 等) |
环境变量判断
Nuxt 使用 Vite 作为打包工具,执行 pnpm dev 时会自动设置 NODE_ENV=development:
if (process.env.NODE_ENV === 'development') {
// 开发环境逻辑
}
typescript
可以通过打印验证:
console.log(process.env.NODE_ENV) // 输出 "development"
typescript
静态资源路径处理
Mock 数据中的图片路径
迁移到 Nuxt 后,Mock 数据中的图片路径需要调整:
原先:/api/assets/images/bg.png
现在:/assets/images/bg.png(public 目录下的绝对路径)
text
文件忽略配置
Mock 数据文件(JSON、下载资源等)不需要提交到 Git:
# .gitignore
public/downloads/
public/json/
gitignore
页面布局问题排查
首页图片不显示
如果首页 Swiper 轮播图片不显示,可能的原因和解决方案:
问题 1:容器高度为 0
Swiper 组件的高度依赖外部容器,如果没有设置高度则默认为 0:
<div style="height: 40rem">
<ImageSwiper :items="swipers" />
</div>
vue
问题 2:resize 监听未初始化
原项目在 app.vue 中监听了窗口尺寸变化,Nuxt 中需要在 onBeforeMount 中重新设置:
// composables/useWindowSize.ts
export function useWindowSize() {
const width = ref(0)
const height = ref(0)
onBeforeMount(() => {
width.value = window.innerWidth
height.value = window.innerHeight
})
return { width, height }
}
typescript
使用 onBeforeMount 确保 document 和 window 对象存在(仅在客户端执行)。
Nuxt Server API 文件路由规则
路由生成规则
server/api/hello.ts → GET /api/hello
server/api/hello.get.ts → GET /api/hello
server/api/hello.post.ts → POST /api/hello
server/routes/mock/test.ts → GET /mock/test
server/routes/mock/test.post.ts → POST /mock/test
text
文件名与路径的关系
文件名中的参数会直接映射为 URL 路径:
server/routes/mock/hello111.ts → /mock/hello111
text
改造总结
通过两节课的 Nuxt 改造,完成了以下工作:
| 改造项 | 状态 |
|---|---|
| 组件自动导入 | 已完成 |
| Layout 布局系统 | 已完成 |
| 文件路由 | 已完成 |
| 静态资源迁移 | 已完成 |
| Store 状态管理 | 已完成 |
| PWA 注册 | 已完成 |
| SSR 数据获取(useFetch) | 已完成 |
| API 代理与 Mock 数据 | 已完成 |
| @nuxt/content 详情页 | 已完成 |
| 404 页面 | 已完成 |
Nuxt 的 "约定大于配置" 理念在这次改造中得到了充分体现:大部分功能只需将文件放到正确的目录,Nuxt 就会自动处理导入、路由和注册。
↑