Nuxt阶段二:核心概念 —— 路由、布局、组件与 Composables

Nuxt阶段二:核心概念 ------ 路由、布局、组件与 Composables

学习目标 :掌握文件路由(动态/嵌套)、布局系统、组件自动导入、composables 与 useState

预计时间 :1--2 周

前置 :完成阶段一,项目能 pnpm dev 跑起来。

本阶段产出:一个三页「个人博客骨架」(首页 / 文章列表 / 文章详情)。


1. 文件路由深入

Nuxt 根据 app/pages/ 下的文件自动生成路由。

1.1 命名与 URL 对照

文件路径 URL
pages/index.vue /
pages/about.vue /about
pages/blog/index.vue /blog
pages/blog/[id].vue /blog/123(动态段)
pages/blog/[...slug].vue /blog/a/b/c(捕获剩余路径)
pages/user-[group]/id.vue /user-admin/1(前缀 + 动态)

1.2 动态路由:文章详情

创建 app/pages/blog/[id].vue

html 复制代码
<script setup lang="ts">
const route = useRoute()
const id = computed(() => route.params.id as string)

// 先用假数据,阶段三再接真实 API
const posts: Record<string, { title: string; body: string }> = {
  '1': { title: '什么是 Nuxt 4', body: 'Nuxt 是 Vue 全栈框架......' },
  '2': { title: '文件路由入门', body: 'pages 目录决定 URL......' },
}

const post = computed(() => posts[id.value] ?? null)

if (!post.value) {
  throw createError({ statusCode: 404, statusMessage: '文章不存在' })
}

useSeoMeta({
  title: () => post.value?.title ?? '文章',
})
</script>

<template>
  <article v-if="post" class="post">
    <NuxtLink to="/blog">← 返回列表</NuxtLink>
    <h1>{{ post.title }}</h1>
    <p class="meta">文章 ID:{{ id }}</p>
    <div>{{ post.body }}</div>
  </article>
</template>

要点:

  • useRoute() 读取当前路由
  • route.params.id 对应文件名 [id]
  • createError({ statusCode: 404 }) 触发错误页

1.3 列表页 app/pages/blog/index.vue

html 复制代码
<script setup lang="ts">
const posts = [
  { id: '1', title: '什么是 Nuxt 4', excerpt: '从零认识 Nuxt 4' },
  { id: '2', title: '文件路由入门', excerpt: 'pages 如何变成 URL' },
]

useSeoMeta({ title: '博客列表' })
</script>

<template>
  <section class="blog-list">
    <h1>博客</h1>
    <ul>
      <li v-for="p in posts" :key="p.id">
        <NuxtLink :to="`/blog/${p.id}`">
          <strong>{{ p.title }}</strong>
          <span>{{ p.excerpt }}</span>
        </NuxtLink>
      </li>
    </ul>
  </section>
</template>

1.4 编程式导航

模板里优先用 <NuxtLink>(预取、无刷新)。脚本里用:

ts 复制代码
const router = useRouter()

// 跳转
await navigateTo('/blog')
await navigateTo(`/blog/${id}`)

// 或
router.push('/about')

navigateTo 在中间件、服务端也能用,更推荐。

1.5 嵌套路由(可选了解)

若需要「父级布局 + 子页面」同屏显示:

text 复制代码
pages/
  user.vue          ← 父页面,内含 <NuxtPage />
  user/
    index.vue       → /user
    profile.vue     → /user/profile

user.vue

html 复制代码
<template>
  <div>
    <nav>
      <NuxtLink to="/user">概览</NuxtLink>
      <NuxtLink to="/user/profile">资料</NuxtLink>
    </nav>
    <NuxtPage />
  </div>
</template>

博客骨架可以暂不嵌套,先掌握动态路由即可。


2. 布局系统 Layouts

布局是页面外层的「壳」:导航栏、页脚、侧边栏。

2.1 默认布局

创建 app/layouts/default.vue

html 复制代码
<template>
  <div class="layout">
    <header class="header">
      <NuxtLink to="/" class="logo">MyBlog</NuxtLink>
      <nav>
        <NuxtLink to="/">首页</NuxtLink>
        <NuxtLink to="/blog">博客</NuxtLink>
        <NuxtLink to="/about">关于</NuxtLink>
      </nav>
    </header>

    <main class="main">
      <slot />
    </main>

    <footer class="footer">
      <p>© {{ new Date().getFullYear() }} Nuxt 4 学习博客</p>
    </footer>
  </div>
</template>

<style scoped>
.layout { min-height: 100vh; display: flex; flex-direction: column; }
.header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 1rem 1.5rem; border-bottom: 1px solid #e5e5e5;
}
.header nav { display: flex; gap: 1rem; }
.main { flex: 1; max-width: 720px; width: 100%; margin: 0 auto; padding: 2rem 1rem; }
.footer { padding: 1.5rem; text-align: center; color: #666; border-top: 1px solid #e5e5e5; }
.logo { font-weight: 700; text-decoration: none; color: inherit; }
a.router-link-active { color: #00dc82; }
</style>

<slot /> 的位置就是各页面内容插入处。

确保 app/app.vue 为:

html 复制代码
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

不写 definePageMeta 时,页面默认用 default 布局。

2.2 多布局:管理后台壳

创建 app/layouts/admin.vue

html 复制代码
<template>
  <div class="admin">
    <aside>
      <p>后台</p>
      <NuxtLink to="/admin">仪表盘</NuxtLink>
    </aside>
    <section>
      <slot />
    </section>
  </div>
</template>

在页面里指定:

html 复制代码
<script setup lang="ts">
definePageMeta({
  layout: 'admin',
})
</script>

<template>
  <h1>仪表盘</h1>
</template>

创建 app/pages/admin/index.vue 即可访问 /admin

2.3 页面元信息 definePageMeta

ts 复制代码
definePageMeta({
  layout: 'admin',
  title: '仪表盘',        // 自定义字段,可在中间件里读
  middleware: ['auth'],   // 阶段六再细讲
})

注意:definePageMeta 是编译期宏,不要放进函数内部,也不要依赖运行时变量。

2.4 Layout Props(Nuxt 4.4+ 了解)

新版本支持向 layout 传 props,便于标题条等。若你的版本支持,可查阅官方 Layout Props 文档;入门阶段用 useState 传标题也完全够用。


3. 组件自动导入

把组件放进 app/components/无需 import,模板里直接用。

3.1 基础示例

app/components/AppHeader.vue

html 复制代码
<script setup lang="ts">
defineProps<{ subtitle?: string }>()
</script>

<template>
  <header>
    <h1>MyBlog</h1>
    <p v-if="subtitle">{{ subtitle }}</p>
  </header>
</template>

任意页面:

html 复制代码
<template>
  <AppHeader subtitle="阶段二练习" />
</template>

3.2 目录与组件名

文件 组件名
components/AppHeader.vue <AppHeader>
components/blog/PostCard.vue <BlogPostCard>
components/blog/PostCard.vue(配 pathPrefix) 也可配置为 <PostCard>

创建 app/components/blog/PostCard.vue

html 复制代码
<script setup lang="ts">
defineProps<{
  id: string
  title: string
  excerpt: string
}>()
</script>

<template>
  <article class="card">
    <NuxtLink :to="`/blog/${id}`">
      <h2>{{ title }}</h2>
      <p>{{ excerpt }}</p>
    </NuxtLink>
  </article>
</template>

<style scoped>
.card {
  padding: 1rem 0;
  border-bottom: 1px solid #eee;
}
.card a { text-decoration: none; color: inherit; }
.card h2 { margin: 0 0 0.5rem; font-size: 1.25rem; }
.card p { margin: 0; color: #666; }
</style>

列表页改为:

html 复制代码
<template>
  <section>
    <h1>博客</h1>
    <BlogPostCard
      v-for="p in posts"
      :key="p.id"
      :id="p.id"
      :title="p.title"
      :excerpt="p.excerpt"
    />
  </section>
</template>

3.3 懒加载组件

组件名前加 Lazy 前缀,会按需加载:

html 复制代码
<LazyBlogHeavyChart v-if="showChart" />

适合大型、非首屏组件。


4. Composables(组合式函数)

业务逻辑抽到 app/composables/,同样自动导入

4.1 命名约定

文件 useXxx.ts,导出函数 useXxx

ts 复制代码
// app/composables/useSite.ts
export function useSite() {
  const config = useRuntimeConfig()
  const name = computed(() => config.public.appName || 'MyBlog')
  return { name }
}

页面中:

html 复制代码
<script setup lang="ts">
const { name } = useSite()
</script>

<template>
  <p>站点:{{ name }}</p>
</template>

4.2 SSR 友好的全局状态:useState

不要用普通模块级 ref 做跨请求共享(会在服务端串数据)。用:

ts 复制代码
// app/composables/useCounter.ts
export function useCounter() {
  const count = useState('counter', () => 0)
  const increment = () => { count.value++ }
  const reset = () => { count.value = 0 }
  return { count, increment, reset }
}

useState 的 key('counter')保证同一请求内共享,且会正确传递到客户端 hydration。

4.3 读取路由参数的小封装

ts 复制代码
// app/composables/useBlogId.ts
export function useBlogId() {
  const route = useRoute()
  return computed(() => String(route.params.id || ''))
}

5. 本阶段完整练习:博客骨架

按下面清单搭好结构:

text 复制代码
app/
├── app.vue
├── layouts/
│   └── default.vue
├── components/
│   └── blog/
│       └── PostCard.vue
├── composables/
│   └── useSite.ts
└── pages/
    ├── index.vue
    ├── about.vue
    └── blog/
        ├── index.vue
        └── [id].vue

首页建议内容

html 复制代码
<script setup lang="ts">
const { name } = useSite()
useSeoMeta({
  title: '首页',
  description: 'Nuxt 4 学习博客',
})
</script>

<template>
  <div>
    <h1>欢迎来到 {{ name }}</h1>
    <p>这是阶段二的博客骨架。去看看 <NuxtLink to="/blog">文章列表</NuxtLink>。</p>
  </div>
</template>

验收清单

  1. 顶部导航可在 首页 / 博客 / 关于 间跳转
  2. /blogBlogPostCard 列出至少 2 篇文章
  3. /blog/1/blog/2 显示详情;访问 /blog/999 出现 404
  4. 使用了 useSite composable
  5. 所有页面套在 default 布局里

加分题:

  • 增加 layouts/admin.vue + /admin
  • 给当前导航项加高亮(可用 useRoute() 或 NuxtLink 的 active class)

6. 常见坑

动态参数是 string | string\[\]

多段路由时可能是数组,统一 String(route.params.id) 或做判断。

布局不生效

检查 app.vue 是否包了 <NuxtLayout>layout: false 会关闭布局。

组件名对不上

看 Nuxt DevTools → Components,确认自动生成的名字。

definePageMeta 报错

只能写在 <script setup> 顶层;不能在 pages 以外的组件里用(有例外高级用法,入门先遵守这点)。


7. 阶段总结

你已经掌握:

  • 动态路由、useRoute / navigateTo、404
  • default / 多布局与 definePageMeta
  • 组件与 composables 自动导入
  • useState 的正确用法

下一篇 :阶段三 ------ useFetch / useAsyncData、Nuxt 4 数据层、Pinia 状态管理,把假数据换成真实请求。


参考链接

相关推荐
swipe2 小时前
03|Axios 请求进了后端之后:Controller、Request、Response 是怎么接住它的?
前端·后端·全栈
swipe2 小时前
02|从 `pnpm dev` 到 Spring Boot 启动:后端服务到底怎么跑起来?
前端·后端·全栈
swipe3 小时前
01|前端人第一次打开 Spring Boot 项目,应该先看哪里?
前端·后端·全栈
-Camellia007-5 小时前
用 Vue3 + Vite + Pinia 写一个极简的“个人任务看板”(数据存本地 LocalStorage)
vue
shawxlee16 小时前
vue3在public下封装config.js自定义配置动态数据,可在打包后直接修改,方便后端部署及后续维护
前端·javascript·经验分享·vue·团队开发·js·项目优化
达达尼昂17 小时前
AI 编程的工程化实践:Flutter AI Harness 的设计与落地
人工智能·后端·全栈
jieyucx18 小时前
Nuxt4阶段六:工程化与进阶 —— 模块、中间件、插件、TS 与测试
中间件·vue·web·nuxt·全栈·ssr
jieyucx18 小时前
Nuxt4阶段五:渲染模式与性能优化
性能优化·vue·nuxt
csdn2015_1 天前
nodejs安装
node.js·vue
Web极客码2 天前
WordPress SEO优化:提升网站排名的13个关键步骤
服务器·seo·wordpress