视图结构

Nuxt 给你 4 层来搭 UI:app.vue组件页面布局。每一层分工明确,Nuxt 会自动连线,大多数时候你只要加文件就行。

app.vue

app.vue 是应用入口。如果完全不需要路由:

<template>
  <div>
    <h1>欢迎</h1>
  </div>
</template>

想要多页面时挂上 <NuxtPage />

<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

如果删掉 app.vue,Nuxt 会生成一个默认入口,相当于 <NuxtLayout><NuxtPage /></NuxtLayout>

组件

app/components/ 下的所有内容会自动导入,不用手写 import

app/components/
├── AppHeader.vue
├── AppFooter.vue
└── AppAlert.vue
<template>
  <div>
    <AppHeader />
    <AppAlert>Hello,来自自动导入的组件。</AppAlert>
    <AppFooter />
  </div>
</template>

嵌套目录会变成名称前缀:app/components/base/Button.vue<BaseButton />

小建议:

  • <template> 里统一用 PascalCase。
  • 在组件名前加 Lazy 可以延迟加载:<LazyAppHeavy />

页面

页面放在 app/pages/,每个 .vue 文件就是一条路由:

app/pages/
├── index.vue          →  /
├── about.vue          →  /about
└── blog/
    ├── index.vue      →  /blog
    └── [id].vue       →  /blog/:id
<!-- app/pages/about.vue -->
<template>
  <section>
    <h1>关于我们</h1>
  </section>
</template>

页面会自动分包,<NuxtLink> 会帮你预取,definePageMeta({ … }) 用来声明页面级元信息。

布局

布局用来包裹一组页面,负责共用的外壳(头部、底部、侧边栏)。文件放在 app/layouts/

<!-- app/layouts/default.vue -->
<template>
  <div>
    <AppHeader />
    <slot />
    <AppFooter />
  </div>
</template>

default.vue 会自动应用。要自定义布局,页面级声明:

<script setup lang="ts">
definePageMeta({ layout: 'admin' })
</script>

<template>
  <div>Dashboard</div>
</template>

……对应的文件放到 app/layouts/admin.vue

<NuxtLayout><NuxtPage>

  • <NuxtPage /> 是文件式路由的“出口”,负责流式渲染、预取、过渡。
  • <NuxtLayout> 决定当前页面用哪个布局,也可以手动指定 name
<NuxtLayout name="admin">
  <NuxtPage />
</NuxtLayout>

如果只有一个布局,完全可以跳过 layouts/,把外壳直接写在 app.vue 里。

要点回顾

需求 去哪儿
根入口/全站外壳 app.vue(或单布局)
可复用 UI 块 app/components/
一条路由 + 视图 app/pages/
多页面共享的外壳 app/layouts/

进阶:定制 HTML 模板

如果 useHead 还不够(需要改 Nitro 输出的原始 HTML),用 render:html 钩子写一个 Nitro 插件:

// server/plugins/meta.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('render:html', (html, { event }) => {
    html.head.push('<meta name="description" content="自定义描述" />')
  })
})

这种写法谨慎使用 —— useHead 能覆盖 99% 的情况,静态资源和 SEO 章节会介绍更顺手的做法。

視圖結構

Nuxt 提供 4 層介面建構:app.vue元件頁面佈局。各司其職,Nuxt 會自動串接,多數時候只需新增檔案即可。

app.vue

app.vue 是應用入口。若完全不需路由:

<template>
  <div>
    <h1>歡迎</h1>
  </div>
</template>

需要多頁面時掛上 <NuxtPage />

<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

刪除 app.vue,Nuxt 會產生預設入口,等同於 <NuxtLayout><NuxtPage /></NuxtLayout>

元件

app/components/ 下的所有內容會自動匯入,毋須手寫 import

app/components/
├── AppHeader.vue
├── AppFooter.vue
└── AppAlert.vue
<template>
  <div>
    <AppHeader />
    <AppAlert>Hello,來自自動匯入的元件。</AppAlert>
    <AppFooter />
  </div>
</template>

巢狀目錄會成為名稱前綴:app/components/base/Button.vue<BaseButton />

小建議:

  • <template> 一律採用 PascalCase。
  • 前置 Lazy 可延遲載入:<LazyAppHeavy />

頁面

頁面存放於 app/pages/,每個 .vue 檔即一條路由:

app/pages/
├── index.vue          →  /
├── about.vue          →  /about
└── blog/
    ├── index.vue      →  /blog
    └── [id].vue       →  /blog/:id
<!-- app/pages/about.vue -->
<template>
  <section>
    <h1>關於我們</h1>
  </section>
</template>

頁面會自動分包,<NuxtLink> 會協助預取,definePageMeta({ … }) 用於宣告頁面層級中繼資料。

佈局

佈局用於包裹多個頁面,承載共用外殼(頁首、頁尾、側欄)。檔案置於 app/layouts/

<!-- app/layouts/default.vue -->
<template>
  <div>
    <AppHeader />
    <slot />
    <AppFooter />
  </div>
</template>

default.vue 會自動套用。如需自訂佈局,於頁面宣告:

<script setup lang="ts">
definePageMeta({ layout: 'admin' })
</script>

<template>
  <div>Dashboard</div>
</template>

……對應檔案放於 app/layouts/admin.vue

<NuxtLayout><NuxtPage>

  • <NuxtPage /> 為檔案式路由的「出口」,負責串流渲染、預取與過渡動畫。
  • <NuxtLayout> 決定當前頁面採用哪個佈局,亦可透過 name 手動指定:
<NuxtLayout name="admin">
  <NuxtPage />
</NuxtLayout>

若僅有一個佈局,可跳過 layouts/,將外殼直接寫於 app.vue

要點回顧

需求 該往哪放
根入口/全站外殼 app.vue(或單一佈局)
可重用的 UI 區塊 app/components/
一條路由 + 視圖 app/pages/
多頁共享的外殼 app/layouts/

進階:自訂 HTML 模板

useHead 無法滿足(需改動 Nitro 輸出的原始 HTML),可透過 render:html 鉤子撰寫 Nitro 外掛:

// server/plugins/meta.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('render:html', (html, { event }) => {
    html.head.push('<meta name="description" content="自訂描述" />')
  })
})

此方式宜節制使用 —— useHead 已涵蓋 99% 情境,靜態資源與 SEO 章節將介紹更順手的做法。

Views

Nuxt gives you four layers to build the UI: app.vue, components, pages, and layouts. Each has a clear job, and Nuxt auto-wires them so you mostly add files and the rest just works.

app.vue

Nuxt treats app.vue as the entrypoint. If you don't need routing at all, you can stop here:

<template>
  <div>
    <h1>Welcome</h1>
  </div>
</template>

When you want per-route pages, include <NuxtPage />:

<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

If you delete app.vue, Nuxt generates a default entry that mounts <NuxtLayout><NuxtPage /></NuxtLayout>.

Components

Anything under app/components/ is auto-imported — no more import Button from './Button.vue'.

app/components/
├── AppHeader.vue
├── AppFooter.vue
└── AppAlert.vue
<template>
  <div>
    <AppHeader />
    <AppAlert>Hello from an auto-imported component.</AppAlert>
    <AppFooter />
  </div>
</template>

Nested folders become name prefixes: app/components/base/Button.vue<BaseButton />.

Tips:

  • Use PascalCase in <template> for clarity.
  • Add Lazy to defer loading: <LazyAppHeavy />.

Pages

Pages live in app/pages/. Every .vue file is a route:

app/pages/
├── index.vue          →  /
├── about.vue          →  /about
└── blog/
    ├── index.vue      →  /blog
    └── [id].vue       →  /blog/:id
<!-- app/pages/about.vue -->
<template>
  <section>
    <h1>About us</h1>
  </section>
</template>

Pages are code-split, prefetched by <NuxtLink>, and can declare per-page metadata with definePageMeta({ … }).

Layouts

A layout wraps one or more pages so you can share chrome (header, footer, sidebar). Files live in app/layouts/:

<!-- app/layouts/default.vue -->
<template>
  <div>
    <AppHeader />
    <slot />
    <AppFooter />
  </div>
</template>

default.vue applies automatically. For a custom layout, opt in per-page:

<script setup lang="ts">
definePageMeta({ layout: 'admin' })
</script>

<template>
  <div>Dashboard</div>
</template>

…and put the wrapper at app/layouts/admin.vue.

<NuxtLayout> and <NuxtPage>

  • <NuxtPage /> is the "outlet" for file-based routes. It knows how to stream, prefetch, and animate.
  • <NuxtLayout> decides which layout wraps the current page. It accepts name for manual control:
<NuxtLayout name="admin">
  <NuxtPage />
</NuxtLayout>

If you have only one layout, skip layouts entirely and put the shell directly in app.vue.

Building blocks recap

Need Use this
Root entry / app-wide chrome app.vue (or single layout)
Reusable UI piece Component in app/components/
Route + view Page in app/pages/
Shared shell around several pages Layout in app/layouts/

Advanced: customizing the HTML template

When you need to tweak the raw HTML that Nitro emits (beyond useHead), register a Nitro plugin with the render:html hook:

// server/plugins/meta.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('render:html', (html, { event }) => {
    html.head.push('<meta name="description" content="My custom description" />')
  })
})

Use this sparingly — useHead covers 99% of cases, and the next chapter ("Assets") plus the SEO chapter show the ergonomic paths.