用 .config.yml 配置目录级导航,以及设置页面/目录重定向。

目录配置与重定向

除了单篇 Markdown 的 frontmatter,你还可以在每个内容目录下放一个 .config.yml 文件,对整个目录做配置。

目录级导航

# content/docs/guide/.config.yml
navigation:
  label: 指南
  icon: book
  description: 上手指南合集
字段类型说明
navigation.titlestring该目录在侧边栏的分组标题。
navigation.labelstring备用标签。
navigation.descriptionstring分组描述。
navigation.iconstring图标标识(由你的导航组件解释)。
navigation.hiddenboolean隐藏整个目录分组,子项也不显示。
navigation.flattenboolean把子项提升到父级,不再生成这一层分组。

Header 导航

如果你的站点布局需要把内容目录映射到顶栏,可以在 .config.yml 里显式开启:

# content/docs/guide/.config.yml
navigation:
  label: 指南
header:
  enable: true

常见用法是只给一级目录开启 header.enable,这样顶栏就显示一级栏目;如果你还给子目录也开启,它们会继续作为下一级 tree 节点参与渲染。

目前内置 schema 支持:

字段类型说明
header.enableboolean设为 true 时,把当前目录加入 header 导航树。
header.labelstring覆盖 header 中显示的标题,不影响侧边栏 navigation.label

用内置 LayoutNav 渲染 header

vike-vue-content 的 docs 数据加载器会自动把这棵 header 树放到 pageContext.data.headerNav 里。

如果你想直接使用内置组件,可以在布局里这样接:

<script setup lang="ts">
import { computed } from 'vue'
import { usePageContext } from 'vike-vue/usePageContext'
import { LayoutNav } from 'vike-vue-content/components/layout-nav'
import type { DocsHeaderNavItem } from 'vike-vue-content/shared/types'

const pageContext = usePageContext()

const headerNav = computed<DocsHeaderNavItem[]>(() => {
  const data = pageContext.data as { headerNav?: DocsHeaderNavItem[] } | undefined
  return data?.headerNav ?? []
})

const currentPath = computed(() => {
  const pathname = pageContext.urlPathname
  const base = (pageContext as { _baseServer?: string })._baseServer ?? '/'

  if (base === '/' || !pathname.startsWith(base)) {
    return pathname
  }

  const trimmed = pathname.slice(base.length)
  return trimmed.startsWith('/') ? trimmed : `/${trimmed}`
})
</script>

<template>
  <header>
    <LayoutNav :items="headerNav" :current-path="currentPath" />
  </header>
</template>

这里的 currentPath 最好传去掉 base 前缀后的路径,这样激活态才能和 headerNav.path 对齐。

自定义 header 时怎么取数据

如果你不想直接用 LayoutNav,也可以自己消费同一份数据。headerNav 的类型是:

type DocsHeaderNavItem = {
  title: string
  path: string
  matchPath: string
  children?: DocsHeaderNavItem[]
}

下面是一个最小可运行的自定义 header 示例,它直接从 pageContext.data 读取,再渲染成你自己的顶栏:

Docs

这个示例直接消费 pageContext.data.headerNav,不依赖内置 LayoutNav

index.vue
<template>
  <div class="demo-header-shell">
    <header class="demo-header">
      <div class="demo-header-brand">
        Docs
      </div>

      <nav v-if="headerNav.length" class="demo-header-nav" aria-label="Custom header demo">
        <Link
          v-for="item in headerNav"
          :key="item.path"
          :href="item.path"
          class="demo-header-link"
          :class="{ 'is-active': isActive(item) }"
          data-vike="false"
        >
          {{ item.title }}
        </Link>
      </nav>
    </header>

    <p class="demo-header-note">
      这个示例直接消费 <code>pageContext.data.headerNav</code>,不依赖内置 <code>LayoutNav</code>。
    </p>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { usePageContext } from 'vike-vue/usePageContext'
import type { DocsHeaderNavItem } from 'vike-vue-content/shared/types'
import { Link } from 'vike-vue-content/components/link'

const pageContext = usePageContext()

const headerNav = computed<DocsHeaderNavItem[]>(() => {
  const data = pageContext.data as { headerNav?: DocsHeaderNavItem[] } | undefined
  return data?.headerNav ?? []
})

const currentPath = computed(() => {
  const pathname = pageContext.urlPathname
  const base = (pageContext as { _baseServer?: string })._baseServer ?? '/'

  if (base === '/' || !pathname.startsWith(base)) {
    return pathname
  }

  const trimmed = pathname.slice(base.length)
  return trimmed.startsWith('/') ? trimmed : `/${trimmed}`
})

function isActive(item: DocsHeaderNavItem) {
  return (
    currentPath.value === item.matchPath
    || currentPath.value.startsWith(`${item.matchPath}/`)
  )
}
</script>

<style scoped>
.demo-header-shell {
  background:
    radial-gradient(circle at top left, color-mix(in srgb, var(--color-primary) 12%, transparent), transparent 42%),
    linear-gradient(180deg, color-mix(in srgb, var(--color-surface) 88%, white), var(--color-bg));
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) + 0.5rem);
  padding: 1rem;
}

.demo-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.demo-header-brand {
  color: var(--color-text);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.demo-header-nav {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.demo-header-link {
  color: var(--color-text-muted);
  text-decoration: none;
  padding: 0.55rem 0.85rem;
  border-radius: 999px;
  transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

.demo-header-link:hover {
  color: var(--color-text);
  background-color: var(--color-surface-elevated);
}

.demo-header-link.is-active {
  color: var(--color-primary);
  background-color: color-mix(in srgb, var(--color-primary) 10%, var(--color-bg));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 22%, transparent);
  font-weight: 600;
}

.demo-header-note {
  margin: 0.875rem 0 0;
  color: var(--color-text-muted);
  font-size: 0.925rem;
  line-height: 1.6;
}

.demo-header-note code {
  background-color: var(--color-surface-elevated);
  border-radius: 0.35rem;
  padding: 0.1rem 0.35rem;
}
</style>

说明:

  • headerNav 只包含显式开启了 header.enable 的目录节点
  • 如果子目录也开启了 header.enable,它们会出现在 children 里,适合做二级菜单
  • 侧边栏仍然使用 navigation 树;header 只是额外派生出一棵顶栏导航树
  • 你可以把这个 demo 的实现搬到自己的 +Layout.vue,然后改造成 dropdown、tabs 或 drawer

hidden:隐藏分组

navigation:
  hidden: true

目录从侧边栏消失,但目录内的页面仍可通过 URL 直接访问。

flatten:展开层级

navigation:
  flatten: true

子页面会被提升到上一级,不再单独出现「目录分组 → 子项」的两层结构,适合不想多一层嵌套的场景。

重定向

redirect 可以写在 .config.yml(目录级)或 Markdown frontmatter(页面级)中。

目录重定向

常用于把一个 collection 的根路径重定向到某篇具体文档:

# content/zh-CN/.config.yml
navigation:
  hidden: true
redirect: /introduction

访问 /zh-CN 会自动跳到 /zh-CN/introduction

页面重定向

---
redirect: /zh-CN/guide/routing
---

重定向目标的解析规则

  • 绝对协议链接(如 https://...mailto:...)原样保留。
  • / 开头的路径会按当前 collection base 解析。例如在 base 为 /zh-CN 的 collection 里写 redirect: /introduction,最终目标是 /zh-CN/introduction
  • 不以 / 开头的路径会按当前目录解析。例如在 content/zh-CN/guide/.config.yml 里写 redirect: test,最终目标是 /zh-CN/guide/test
  • 如果目标已经带了 base 前缀,则不会重复添加。
框架会在构建期收集整个 workspace 的重定向规则;同一来源路径出现冲突的目标时会直接报错,帮助你尽早发现配置问题。