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 状态管理,把假数据换成真实请求。


参考链接

相关推荐
用户938515635078 小时前
深入理解 Next.js:从 SPA 的 SEO 问题到约定式路由与 SSR 原理
前端·后端·全栈
程序员黑豆1 天前
Java 内存存储机制:基本数据类型 vs 引用数据类型
前端·ai编程·全栈
用户938515635071 天前
从前后端分离到前端接口工程:React + MockJS + Vite 解析
前端·后端·全栈
满栀5851 天前
vue动态路由效果
前端·javascript·vue.js·前端框架·vue
程序员黑豆2 天前
深入解析Java数据类型:基本类型与引用类型的本质区别与实战选择
前端·ai编程·全栈
e6zzseo2 天前
wordpress独立站seo怎么做才有效
seo·wordpress·独立站·内容优化·外链建设
前端双越老师2 天前
前端学习 Java 其实很容易:TS 和 Java 语法的 N 个相同点
java·全栈
碧水澜庭2 天前
头条【vue+fastapi 】全栈教学项目系列之《02_双系统零基础环境搭建手册》
vue·fastapi
船长@2 天前
WordPress Astra 主题从零上手教程:安装、启用、模板导入全流程
seo·外贸独立站·google seo
烬羽3 天前
navigator.gpu 存在就够了?WebGPU 检测的两层陷阱
typescript·浏览器·全栈