---
title: "pages"
description: "Nuxt 提供基于文件的路由来在您的 Web 应用中创建路由。"
canonical_url: "https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/pages"
---
# pages

> Nuxt 提供基于文件的路由来在您的 Web 应用中创建路由。

::note
为了减小应用的打包体积，该目录是**可选的**，这意味着如果您只使用 [`app.vue`](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/app)，则不会包含 [`vue-router`](https://router.vuejs.org)。若要强制启用 pages 系统，请在 `nuxt.config` 中设置 `pages: true`，或使用 [`router.options.ts`](https://nuxt.zhcndoc.com/docs/4.x/guide/recipes/custom-routing#using-routeroptions)。
::

## 用法

页面是 Vue 组件，并且可以使用 Nuxt 支持的任何[有效扩展名](https://nuxt.zhcndoc.com/docs/4.x/api/nuxt-config#extensions)（默认包括 `.vue`、`.js`、`.jsx`、`.mjs`、`.ts` 或 `.tsx`）。

Nuxt 会为 `~/pages/` 目录中的每个页面自动创建路由。

::tip
只有在未禁用 `prerender.crawlLinks` 且 `prerender.routes` 列表中至少有一个页面时，页面才会自动注册以进行预渲染。请参阅[预渲染指南](https://nuxt.zhcndoc.com/docs/4.x/getting-started/prerendering)。
::

::code-group
```vue [app/pages/index.vue]
<template>
  <h1>索引页</h1>
</template>
```

```ts twoslash [pages/index.ts]
// https://vue.zhcndoc.com/guide/extras/render-function.html
export default defineComponent({
  render () {
    return h('h1', '索引页')
  },
})
```

```tsx twoslash [pages/index.tsx]
// /docs/examples/advanced/jsx
// https://vuejs.org/guide/extras/render-function.html#jsx-tsx
export default defineComponent({
  render () {
    return <h1>索引页</h1>
  },
})
```
::

`app/pages/index.vue` 文件将映射到应用的 `/` 路由。

如果您使用 [`app.vue`](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/app)，请务必使用 [`<NuxtPage/>`](https://nuxt.zhcndoc.com/docs/4.x/api/components/nuxt-page) 组件来显示当前页面：

```vue [app/app.vue]
<template>
  <div>
    <!-- 在所有页面间共享的标记，例如：导航栏 -->
    <NuxtPage />
  </div>
</template>
```

页面**必须只有一个根元素**，才能在页面之间实现[路由过渡](https://nuxt.zhcndoc.com/docs/4.x/getting-started/transitions)。HTML 注释也会被视为元素。

这意味着当路由在服务器端渲染或静态生成时，您将能正确看到其内容；但当您在客户端导航到该路由时，如果页面没有单一根元素，路由间的过渡将失败，且页面不会被渲染。

下面是一些示例来说明具有单一根元素的页面长什么样：

::code-group
```vue [app/pages/working.vue]
<template>
  <div>
    <!-- 此页面正确地只有一个根元素 -->
    页面内容
  </div>
</template>
```

```vue [app/pages/bad-1.vue]
<template>
  <!-- 此页面在客户端导航时不会渲染，因为这个注释会被视为元素 -->
  <div>页面内容</div>
</template>
```

```vue [app/pages/bad-2.vue]
<template>
  <div>这个页面</div>
  <div>有多个根元素</div>
  <div>并且在客户端侧导航时路由变化时不会渲染</div>
</template>
```
::

## 动态路由

如果您在方括号内放置任何内容，它将被转换为[动态路由](https://router.vuejs.org/guide/essentials/dynamic-matching)参数。您可以在文件名或目录中混合多个参数，甚至混合非动态文本。

如果您希望参数为可选，则必须使用双重方括号包裹，例如 `~/pages/[[slug]]/index.vue` 或 `~/pages/[[slug]].vue` 将匹配 `/` 和 `/test`。

```bash [Directory Structure]
-| pages/
---| index.vue
---| users-[group]/
-----| [id].vue
```

基于上述示例，您可以在组件中通过 `$route` 对象访问 group/id：

```vue [app/pages/users-[group\\]/[id\\].vue]
<template>
  <p>{{ $route.params.group }} - {{ $route.params.id }}</p>
</template>
```

导航到 `/users-admins/123` 将渲染：

```html
<p>admins - 123</p>
```

如果您希望使用组合式 API 访问路由，可以使用全局 [`useRoute`](https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-route) 函数，它允许您像在选项式 API 中使用 `this.$route` 一样访问路由。

```vue twoslash
<script setup lang="ts">
const route = useRoute()

if (route.params.group === 'admins' && !route.params.id) {
  console.log('警告！请确保用户已通过身份验证！')
}
</script>
```

::note
命名的父路由会优先于嵌套的动态路由。对于 `/foo/hello` 路由，`~/pages/foo.vue` 会优先于 `~/pages/foo/[slug].vue`。   
 要使用不同页面分别匹配 `/foo` 和 `/foo/hello`，请使用 `~/pages/foo/index.vue` 和 `~/pages/foo/[slug].vue`。
::

::video-accordion{title="观看 Vue School 关于动态路由的视频" videoId="754465699" platform="vimeo"}
::

## Catch-all 路由

如果您需要一个 catch-all 路由，可以使用名为 `[...slug].vue` 的文件创建。它将匹配该路径下的所有路由。

```vue [app/pages/[...slug\\].vue]
<template>
  <p>{{ $route.params.slug }}</p>
</template>
```

导航到 `/hello/world` 将渲染：

```html
<p>["hello", "world"]</p>
```

## 嵌套路由

可以使用 `<NuxtPage>` 显示[嵌套路由](https://router.vuejs.org/guide/essentials/nested-routes)。

示例：

```bash [Directory Structure]
-| pages/
---| parent/
-----| child.vue
---| parent.vue
```

此文件结构将生成如下路由：

```js
[
  {
    path: '/parent',
    component: '~/pages/parent.vue',
    name: 'parent',
    children: [
      {
        path: 'child',
        component: '~/pages/parent/child.vue',
        name: 'parent-child',
      },
    ],
  },
]
```

要显示 `child.vue` 组件，您需要在 `app/pages/parent.vue` 中插入 `<NuxtPage>` 组件：

```vue {}[pages/parent.vue]
<template>
  <div>
    <h1>我是父视图</h1>
    <NuxtPage :foobar="123" />
  </div>
</template>
```

```vue {}[pages/parent/child.vue]
<script setup lang="ts">
const props = defineProps({
  foobar: String,
})

console.log(props.foobar)
</script>
```

### 子路由键

如果您想更精细地控制何时重新渲染 `<NuxtPage>` 组件（例如，用于过渡），可以通过 `pageKey` 属性传入字符串或函数，或者通过 `definePageMeta` 定义 `key` 值：

```vue {}[pages/parent.vue]
<template>
  <div>
    <h1>我是父视图</h1>
    <NuxtPage :page-key="route => route.fullPath" />
  </div>
</template>
```

或者：

```vue twoslash {}[pages/parent/child.vue]
<script setup lang="ts">
definePageMeta({
  key: route => route.fullPath,
})
</script>
```

::link-example{to="https://nuxt.zhcndoc.com/docs/examples/routing/pages"}
::

## 命名视图 :badge[v4.5]{color="info" size="xs" .align-middle}

单个路由可以通过为每个出口指定一个 `name` 并为每个名称提供一个同级页面文件，在父组件中的多个 `<NuxtPage>` 出口中渲染。

使用 `name@view.vue` 文件名约定，在默认路由文件旁边声明一个命名视图：

```bash [Directory Structure]
-| pages/
---| parent/
-----| child.vue
-----| child@sidebar.vue
---| parent.vue
```

然后从父组件按名称渲染每个出口：

```vue [pages/parent.vue]
<template>
  <div>
    <NuxtPage />
    <aside>
      <NuxtPage name="sidebar" />
    </aside>
  </div>
</template>
```

当用户导航到 `/parent/child` 时，`child.vue` 会渲染到默认的 `<NuxtPage />` 中，而 `child@sidebar.vue` 会渲染到 `<NuxtPage name="sidebar" />` 中。没有匹配命名视图的出口将保持为空。

::note
`definePageMeta` 仅从默认路由文件中读取。`name@view.vue` 同级文件中声明的元数据不会对该路由生效。
::

::read-more{title="命名视图" to="https://router.vuejs.org/guide/essentials/named-views.html" target="_blank"}
::

## 路由分组

在某些情况下，您可能希望将一组路由分组，但不影响基于文件的路由结构。为此，您可以将文件放在用圆括号包裹的文件夹中 - `(` 和 `)`。

例如：

```bash [Directory structure]
-| pages/
---| index.vue
---| (marketing)/
-----| about.vue
-----| contact.vue
```

这将在您的应用中生成 `/`、`/about` 和 `/contact` 页面。对于 URL 结构而言，`marketing` 分组会被忽略。

### 访问路由分组 :badge[v4.3]{color="info" size="xs" .align-middle}

路由分组会自动包含在路由元数据中，作为 `route.meta.groups`。这允许您在组件中访问分组信息，以实现条件逻辑、样式或其他用途。

```vue {}[pages/(marketing)/about.vue]
<script setup lang="ts">
const route = useRoute()

console.log(route.meta.groups) // 输出: ['marketing']
</script>

<template>
  <div>
    <p v-if="route.meta.groups?.includes('marketing')">
      这是一个营销页面
    </p>
  </div>
</template>
```

## 页面元数据

您可能希望为应用中的每个路由定义元数据。您可以使用 `definePageMeta` 宏来实现，它在 `<script>` 和 `<script setup>` 中都可用：

```vue twoslash
<script setup lang="ts">
definePageMeta({
  title: '我的首页',
})
</script>
```

然后可以在应用的其他地方通过 `route.meta` 对象访问这些数据。

```vue twoslash
<script setup lang="ts">
const route = useRoute()

console.log(route.meta.title) // 我的首页
</script>
```

如果您使用嵌套路由，所有这些路由的页面元数据会合并为一个对象。有关路由元数据的更多信息，请参阅 [vue-router 文档](https://router.vuejs.org/guide/advanced/meta)。

与 `defineEmits` 或 `defineProps` 类似（参见 [Vue 文档](https://vue.zhcndoc.com/api/sfc-script-setup#defineprops-defineemits)），`definePageMeta` 是一个**编译器宏**。它在编译时会被提取，因此您不能在组件内部引用它。传递给它的元数据将被提升出组件。
因此，页面元对象不能引用该组件本身。但它可以引用已导入的绑定以及本地定义的**纯函数**。

::warning
确保不要引用任何响应式数据或会产生副作用的函数。这可能导致不可预期的行为。
::

```vue
<script setup lang="ts">
import { someData } from '~/utils/example'

function validateIdParam (route) {
  return route.params.id && !Number.isNaN(Number(route.params.id))
}

const title = ref('')

definePageMeta({
  validate: validateIdParam,
  someData,
  title, // 不要这样做，ref 将会在组件外被提升
})
</script>
```

### 特殊元数据

当然，您可以为应用中自定义使用定义元数据。但通过 `definePageMeta` 定义的一些元数据具有特定用途：

#### `alias`

您可以定义页面别名。它允许您通过不同路径访问同一页面。它可以是字符串或字符串数组，如 [vue-router 文档](https://router.vuejs.org/guide/essentials/redirect-and-alias#Alias) 中所述。

#### `keepalive`

如果你在 `definePageMeta` 中设置了 `keepalive: true`，Nuxt 会自动用 [Vue 的 `<KeepAlive>` 组件](https://vuejs.org/guide/built-ins/keep-alive#keepalive) 包裹你的页面。例如，在具有动态子路由的父路由中，如果你希望在路由切换时保留页面状态，这可能会很有用。

如果目标是为父路由保留状态，请使用此语法：`<NuxtPage keepalive />`。您也可以为 `<KeepAlive>` 设置要传入的 props（参见[完整列表](https://vue.zhcndoc.com/api/built-in-components#keepalive)）。

您可以[在 `nuxt.config` 中](https://nuxt.zhcndoc.com/docs/4.x/api/nuxt-config#keepalive)为此属性设置默认值。

#### `key`

[参见上文](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/pages#child-route-keys)。

#### `layout`

您可以定义用于渲染路由的布局。它可以是 `false`（用于禁用所有布局）、字符串，或者 ref/computed（如果您希望以某种方式使其具有响应性）。[了解有关布局的更多信息](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/layouts)。

#### `layoutTransition` 和 `pageTransition`

您可以为包裹页面和布局的 `<transition>` 组件定义过渡属性，或者传入 `false` 来为该路由禁用 `<transition>` 包裹。您可以查看[可传入的选项列表](https://vue.zhcndoc.com/api/built-in-components#transition)或阅读[有关过渡工作原理的更多内容](https://vue.zhcndoc.com/guide/built-ins/transition#transition)。

您可以[在 `nuxt.config` 中](https://nuxt.zhcndoc.com/docs/4.x/api/nuxt-config#layouttransition)为这些属性设置默认值。

#### `middleware`

您可以定义在加载此页面之前应用的中间件。它会与所有匹配的父/子路由所用的其他中间件合并。它可以是字符串、函数（遵循[全局前置守卫模式](https://router.vuejs.org/guide/advanced/navigation-guards#Global-Before-Guards)的匿名/内联中间件函数），或字符串/函数数组。[了解有关命名中间件的更多信息](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/middleware)。

#### `name`

您可以为此页面的路由定义一个名称。

#### `path`

如果您的匹配模式比文件名能表达的更复杂，可以定义路径匹配器。有关更多信息，请参阅 [vue-router 文档](https://router.vuejs.org/guide/essentials/route-matching-syntax#Custom-regex-in-params)。

#### `props`

允许将路由的 `params` 作为 props 传递给页面组件。有关更多信息，请参阅 [vue-router 文档](https://router.vuejs.org/guide/essentials/passing-props)。

### 为自定义元数据添加类型

如果您为页面添加自定义元数据，您可能希望以类型安全的方式进行。可以扩展 `definePageMeta` 接受对象的类型：

```ts [index.d.ts]
declare module '#app' {
  interface PageMeta {
    pageType?: string
  }
}

// 扩展类型时，确保导入/导出一些内容是很重要的
export {}
```

## 导航

要在应用的页面之间导航，您应该使用 [`<NuxtLink>`](https://nuxt.zhcndoc.com/docs/4.x/api/components/nuxt-link) 组件。

该组件随 Nuxt 一起包含，因此您无需像其他组件那样导入它。

一个指向 `app/pages` 文件夹中 `index.vue` 页面的简单链接：

```vue
<template>
  <NuxtLink to="/">首页</NuxtLink>
</template>
```

::read-more{to="https://nuxt.zhcndoc.com/docs/4.x/api/components/nuxt-link"}
了解有关 `<NuxtLink>` 用法的更多信息。
::

## 编程式导航

Nuxt 通过 `navigateTo()` 工具方法支持编程式导航。使用此工具方法，您可以在应用中以编程方式导航用户。这对于根据用户输入动态导航非常有用。在此示例中，我们有一个简单的方法 `navigate()`，当用户提交搜索表单时会被调用。

::note
请确保始终对 `navigateTo` 使用 `await`，或通过从函数中返回其结果来链式处理。
::

```vue twoslash
<script setup lang="ts">
const name = ref('')
const type = ref(1)

function navigate () {
  return navigateTo({
    path: '/search',
    query: {
      name: name.value,
      type: type.value,
    },
  })
}
</script>
```

## 仅客户端页面

您可以为页面添加 `.client.vue` 后缀，将其定义为[仅客户端](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/components#client-components)。此页面的任何内容都不会在服务器上渲染。

## 仅服务器页面

您可以为页面添加 `.server.vue` 后缀，将其定义为[仅服务器](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/components#server-components)。虽然您可以通过由 `vue-router` 控制的客户端导航进入该页面，但它会自动使用服务器组件进行渲染，这意味着渲染页面所需的代码不会包含在客户端打包文件中。

::warning
仅服务器页面必须只有一个根元素。（HTML 注释也被视为元素。）
::

导航到仅服务器页面需要请求来渲染该页面，因此当 `<NuxtLink>` 预取指向此类页面的内容时，Nuxt 也会预取该响应。

::read-more{to="https://nuxt.zhcndoc.com/docs/4.x/guide/concepts/server-components#the-client-navigation-round-trip"}
::

## 自定义路由

随着应用变得更大更复杂，您的路由可能需要更多灵活性。因此，Nuxt 直接暴露了路由器、路由和路由器选项，以便以不同方式进行自定义。

::read-more{to="https://nuxt.zhcndoc.com/docs/4.x/guide/recipes/custom-routing"}
::

## 多个 pages 目录

默认情况下，所有页面都应位于项目根目录的 `app/pages` 目录中。

不过，您可以使用 [Nuxt Layers](https://nuxt.zhcndoc.com/docs/4.x/getting-started/layers) 为应用的页面创建分组：

```bash [directory structure]
-| some-app/
---| nuxt.config.ts
---| pages/
-----| app-page.vue
-| nuxt.config.ts
```

```ts twoslash [some-app/nuxt.config.ts]
// some-app/nuxt.config.ts
export default defineNuxtConfig({
})
```

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  extends: ['./some-app'],
})
```

::read-more{to="https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/layers"}
::


## Sitemap

See the full [sitemap](https://nuxt.zhcndoc.com/sitemap.md) for all pages.
