• 大约 60 分钟

文档

介绍

Nuxt是一个免费且开源的创建,它提供了一种直观且可扩展的方式来创建类型安全、高性能和生产级别的全栈Web应用和网站,使用的是Vue.js。

我们做了一切,让你从一开始就可以编写.vue文件,同时在开发中享受到热模块替换的便利,并在生产中获得高性能的应用,其中默认启用了服务器端渲染。

Nuxt没有供应商锁定,允许你将应用部署到任何地方、甚至是边缘。

自动化和约定

Nuxt使用约定和一套规范的目录结构来自动化重复的任务,让开发者可以专注于推动功能的开发。配置文件仍然可以自定义和覆盖其默认行为。

  • 基于文件的路由:根据pages/目录的结构定义结构。这样可以更容易地组织应用程序,避免手动配置路由的需要。
  • 代码分割:Nuxt自动将代码拆分成较小的块,这有助于减少应用程序的初始加载时间。
  • 内置服务器端渲染:Nuxt具备内置的服务器端渲染能力,因此你不需要自己设置单独的服务器。
  • 自动导入:在各自的目录中编写Vue组件和可组合函数,并在使用时无需手动导入,享受树摇和优化JS捆绑包的好处。
  • 数据获取工具:Nuxt提供了可用于处理于服务器端渲染兼容的数据获取的可组合函数,以及不同的策略。
  • 零配置的TypeScript支持:可以编写类型安全的代码,无需学习TypeScript,因为我们提供了自动生成的类型和tsconfig.json配置文件。
  • 配置好的构建工具:我们默认使用Vite来支持开发中的热模块替换(HMR),以及在生产中将代码打包成符合最佳实践的形式。

Nuxt负责处理这些事情,并提供前端和后端的能力,让你可以专注于创建你的Web应用。

服务器端渲染

Nuxt默认具备内置的服务器端渲染(SSR)能力,无需自己配置服务器,这对于Web应用有许多好处:

  • 更快的初始页面加载时间:Nuxt向浏览器发送完全渲染的HTML页面,可以立即展示。这可以提供更快的页面加载时间和更好的用户体验(UX),特别是在网络或设备较慢的情况下。
  • 改善SEO:搜索引擎可以更好地索引SSR页面,因为HTML内容立即可用,而不需要依赖JavaScript在客户端渲染内容。
  • 在低功率设备上更好的性能:减少了需要再客户端下载和执行的JavaScript量,这对于处理重型JavaScript应用程序可能有困难得低功率设备非常有益。
  • 更好的可访问性:内容在初始页面加载时立即可用,改善了依赖屏幕阅读器或其他辅助技术的用户的可访问性。
  • 更容易得缓存:页面可以在服务器端缓存,这可以通过减少生成和发送内容所需的时间而进一步提高性能。

总体而言,服务器端渲染可以提供更快更高效的用户体验,同时改善搜索引擎优化和可访问性。

由于Nuxt是一个多功能的框架,它允许你将整个应用程序静态渲染为静态托管,使用nuxt generate进行部署,通过ssr: false选项在全局禁用SSR,或通过设置routeRules选项来实现混合渲染。

服务器引擎

Nuxt的服务器引擎Nitro开启了全新的全栈能力。

在开发中,它使用RollupNode.js工作线程来处理你的服务器代码和上下文隔离。它还通过读取server/api/中的文件生成你的服务器API,以及读取server/middleware/中的文件生成服务器中间件。

在生产中,Nitro将你的应用程序和服务器构建为一个通用的.output目录。这个输出所轻量级的;经过压缩,并且不包含任何Node.js模块(处理polyfills)。你可以将此输出部署到支持JavaScript的任何系统上,包括Node.js,无服务器(Serverless),Worders,边缘渲染或纯静态环境。

生产就绪

Nuxt应用程序可以部署到NodeDeno服务器上,预渲染以在静态环境中托管,或部署到无服务器和边缘提供商。

模块化

Nuxt的模块系统允许你扩展Nuxt以添加自定义功能和与第三方服务的集成。

架构

Nuxt由不同的核心包组成:

  • 核心引擎:nuxt
  • 打包工具:@nuxt/vite-builder@nuxt/webpack-builder
  • 命令行界面:nuxi
  • 服务器引擎:nitro
  • 开发工具包:@nuxt/kit
  • Nuxt2桥接:@nuxt/bridge

安装

新项目

先决条件

  • Node.js - v16.10.0或更高版本
  • 文本编辑器
  • 终端 - 用于运行Nuxt命令

打开一个终端,并使用以下命令创建一个新的起步项目:

pnpm dlx nuxi@latest init <project-name>

打开项目文件夹:

code <project-name>

安装依赖:

# 在运行 pnpm install 之前,请确保在 `.npmrc` 中设置了 `shamefully-hoist=true`
pnpm install

开发服务器

现在你可以以开发模式启动你的Nuxt应用程序了。

pnpm dev -o

下一步

现在你可以创建了你的Nuxt3项目,你可以开始构建你的应用程序了。

配置

默认情况下,Nuxt已经配置了大多数使用情况。nuxt.config.ts文件可以覆盖和扩展此默认配置。

Nuxt配置

nuxt.config.ts文件位于Nuxt项目的根目录,可以覆盖或扩展应用程序的行为。

一个最简配置文件导出了defineNuxtConfig函数,函数中包含了一个配置对象。defuneNuxtConfig助手函数在全局范围内无需导入即可使用。

export default defineNuxtConfig({
  // 我的nuxt配置
})

通常在文件中会提到此文件,例如添加自定义脚本、注册模块或更改渲染模式。

环境覆盖

你可以在nuxt.config中配置完全类型化的环境覆盖。

export default defineNuxtConfig({
  $production: {
    routeRules: {
      '/**': {isr: true}
    },
    $development: {
      //
    }
  }
})

环境变量和私有令牌

runtimeConfigAPI将像环节变量这样的值暴露给应用程序的其余部分。默认情况下,这些键只在服务器端可用。runtimeConfig.public中的键也可以在客户端使用。

这些值应该在nuxt.config中定义,并可以使用环节变量进行覆盖。

nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // 只在服务器端可用的私有键
    apiSecret: '123',
    // public中的键也可以在客户端使用
    public: {
      apiBase: '/api'
    }
  }
})

这些变量通过useRuntimeConfig()组合函数暴露给应用程序的其余部分。

<script setup lang="ts">
  const runtimeConfig = useRuntimeConfig();
</script>

应用程序配置

app.config.ts文件位于原目录中(默认为项目的根目录),用于公开在构建时确定的公共变量。与runtimeConfig选项不同,这些变量不能使用环节变量进行覆盖。

一个最简配置文件导出了defineAppConfig函数,函数中包含了一个配置对象。defineAppConfig助手函数在全局范围内无需导入即可使用。

export default defineAppConfig({
  title: 'Hello Nuxt',
  theme: {
    dark: true,
    colors: {
      primary: '#ff0000'
    }
  }
})

这些变量通过useAppConfig组合函数暴露给应用程序的其余部分。

<script setup lang="ts">
  const appConfig = useAppConfig();
</script>

runtimeConfig与appConfig

如上所述,runtimeConfigapp.config都用于向应用程序的其余部分公开变量。为了确定应该使用其中之一,以下是一些指导原则:

  • runtimeConfig:需要在构建后使用环节变量指定的私有或公共令牌。
  • app.config:在构建时确定的公共令牌,网站配置(如主题变体、标题)以及不敏感的项目配置等。
功能runtimeConfigapp.config
客户端已注入已打包
环境变量✅ 是❌ 否
响应式✅ 是✅ 是
类型支持✅ 部分✅ 是
每个请求的配置❌ 否✅ 是
热模块替换❌ 否✅ 是
非原始JS类型❌ 否✅ 是

外部配置文件

Nuxt使用nuxt.config.ts文件作为配置的唯一来源,并跳过读取外部配置文件。在构建项目的过程中,你可能需要配置这些文件。下表列出了常见的配置以及如何在Nuxt中配置它们。

名称配置文件如何配置
Nitronitro.config.tsnuxt.config中使用nitro
postCsspostcss.config.tsnuxt.config中使用postcss
Vitevite.config.tsnuxt.config中使用vite
webpackwebpack.config.tsnuxt.config中使用webpack

以下是其他常见配置文件的列表:

名称配置文件
TypeScripttsconfig.json
ESLint.eslintrc.js
Prettierprettierrc.json
Stylelint.stylelintrc.json
TailwindCsstailwind.config.js
Vitestvitest.config.ts

Vue配置

使用Vite

如果你需要传递选项给@vitejs/plugin-vue@vitejs/plugin-vue-jsx,你可以在你的nuxt.config文件中进行配置。

  • vite.vue用于@vitejs/plugin-vue
  • vite.vueJSx用于@vitejs/plugin-vue-jsx
export default deinfeNuxtConfig({
  vite: {
    vue: {
      customElement: true,
    },
    vueJsx: {
      mergeProps: true,
    }
  }
})

使用webpack

如果你使用webpack并且需要配置vue-loader,你可以在nuxt.config文件中使用webpack.loaders.vue键进行配置。

export default defineNuxtConfig({
  webpack: {
    loaders: {
      vue: {
        hotReload: true,
      }
    }
  }
})

启用实验性Vue功能

你可能需要在Vue中启用实验性功能,例如defineModelpropsDestructure。无论你使用哪个构建工具,Nuxt都提供了一种简单的方法在nuxt.config.ts中进行配置:

export default defineNuxtConfig({
  vue: {
    defineModel: true,
    propsDestructure: true,
  }
})

视图

app.vue

默认情况下,Nuxt将把这个文件视为入口点,并为应用程序的每个路由渲染其内容。

<template>
  <div>
    <h1>欢迎来到首页</h1>
  </div>
</template>

组件

大多数组件是可重用的用户界面组件,如按钮和菜单。在Nuxt中,你可以在components/目录中创建这些组件,它们将整个应用程序中可用无需显式地导入。

app.vue
<template>
  <div>
    <h1>欢迎来到首页</h1>
    <AppAlert>
      这是一个自动导入的组件。
    </AppAlert>
  </div>
</template>

页面

页面代表了每个特定路由模式的视图。pages/目录中的每个文件都表示一个不同的路由,显示其内容。

要使用页面,创建pages/index.vue文件并将<NuxtPage />组件添加到app.vue(或者删除app.vue以使用默认入口)。现在,你可以通过在pages/目录中添加新文件来创建更多页面及其对应的路由。

pages/index.vue
<template>
  <div>
    <h1>欢迎来到首页</h1>
    <AppAlert>
      这是一个自动导入的组件。
    </AppAlert>
  </div>
</template>

布局

页面是布局的包装器,包含了多个页面的共同用户界面,如页眉和页脚。布局是使用<slot />组件来显示页面内容的Vue文件。layouts/default.vue文件将被默认使用。自定义布局可以作为页面元数据的一部分进行设置。

相关信息

如果你的应用程序只有一个布局,我们建议使用app.vue<NuxtPage/>

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

高级:扩展HTML模板

你可以通过添加一个Nitro插件来完全控制HTML模板,该插件注册一个钩子函数。render:html钩子函数的回调函数允许你在将HTML发送到客户端之前对其进行修改。

import {response} from "express";

export default defineNitroPlugin(nitroApp => {
  nitroApp.hooks.hook('render:html', (html, {event}) => {
    // 这将是HTML模版的对象表示形式。
    console.log(html);
    html.head.push(`<meta name="description" content="My custom description" />`);
  })
  // 你也可以在这里拦截响应
  nitroApp.hooks.hook('render:response', (response, {event}) => {
    console.log(response);
  })
})

资源

Nuxt使用两个目录来处理样式表、字体或图片资源。

  • public/目录中的内容会按原样作为服务器根目录下的公共资源提供。
  • assets/目录按约定包含了你希望构建工具(Vite或webpack)处理的所有资源。

公共目录

public/目录作应用程序的公共服务器,用于存放在应用程序的指定URL下公开访问的静态资源。

你可以通过应用程序的代码或浏览器的根URL/获取public/目录中的文件。

示例

例如,在public/img/目录中引用一个图像文件,该文件可通过静态URL/img/nuxt.png访问:

<template>
  <img src="/img/nuxt.png" alt="">
</template>

资源目录

Nuxt使用Vite(默认)或webpack来构建和打包你的应用程序。这些构建工具的主要功能是处理JavaScript文件,但它们可以通过插件(对于Vite)或加载器(对于webpack)来处理其他类型的资源,如样式表、字体或SVG。此步骤主要是为了提高性能或缓存目的而对原始文件进行转换(例如样式表的缩小或浏览器缓存失效)。

按照约定,Nuxt使用assets/目录来存储这些文件,但该目录没有自动扫描功能,你可以使用任何其他名称。

在应用程序的代码中,你可以通过使用~/assets/路径来引用位于assets/目录中的文件。

示例

例如,引用一个图像文件,如果构建工具配置为处理该文件扩展名:

<template>
  <img src="~/assets/img/nuxt.png" alt="">
</template>

相关信息

Nuxt不会将assets/目录中的文件作为静态URL(如/assets/my-file.png)提供。如果你需要一个静态URL,请使用public/目录。

全局样式导入

要在你的Nuxt组件样式中全局插入语句,你可以在nuxt.config文件中使用Vite选项。

示例

在这个示例中,有一个sass部分文件,其中包含颜色变量,供你的Nuxt页面和组件使用。

assets/_colors.scss
$primary: #49240F;
$secondary: #E4A79D;

在你的nuxt.config

SCSS
export default defineNuxtConfig({
  vite: {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: '@use "@/assets/_colors.scss" as *;'
        }
      }
    }
  }
})

样式化

在样式化方面,Nuxt非常灵活。你可以编写自己的样式,或者引用本地和外部样式表。你可以使用CSS预处理器、CSS框架、UI库和Nuxt模块来为你的应用程序添加样式。

本地样式表

如果你正在编写本地样式表,讲它们放在assets/目录是最自然的位置。

在组件中引入

你可以在页面、布局和组件中直接引入样式表。你可以使用JavaScript的import,或者使用css的@import语句。

<script>
// 使用静态导入以实现服务器端兼容性
import '~/assets/css/first.css'

// 注意:动态导入不兼容服务器端
import('~/assets/css/first.css')
</script>

<style>
@import url("~/assets/css/second.css");
</style>

样式表将被内联到Nuxt渲染的HTML中。

CSS属性

你还可以使用Nuxt配置中的CSS属性。将你的样式表放在assets/目录是最自然的位置。然后你可以引用它的路径,Nuxt将会将它包含在应用程序的所有页面中。

export default defineNuxtConfig({
  css: ['~/assets/css/main.css']
})

样式表将被内嵌到Nuxt渲染的HTML中,全局注入并存在于所有页面中。

使用字体

将本地字体文件放在~/public/目录中,例如~/public/fonts。然后可以在样式表中使用url()引用它们。

@font-face {
  font-family: 'FarAwayGalaxy';
  src: url('/fonts/FarAwayGalaxy.woff') format('woff');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

然后再样式表、页面或组件中按名称引用你的字体:

<style>
h1 {
  font-family: 'FarAwayGalaxy', sans-serif;
}
</style>

通过NPM分发样式表

你还可以引用通过npm分发的样式表。让我们以流行的animate.css库为例。

npm install animate.css

然后你可以直接在页面、布局和组件中引用它:

<script>
import 'animate.css'
</script>

<style>
@import url("animate.css");
</style>

你还可以将该包作为字符串引用到Nuxt配置的css属性中。

export default defineNuxtConfig({
  css: ['animate.css']
})

外部样式表

你可以通过在nuxt.config文件的head部分添加一个link元素来在应用程序中应用外部样式表。你可以使用不同的方法来实现这个目标。注意,本地样式表也可以以这种方式包含。

你可以通过Nuxt配置的app.head属性来修改head:

export default defineNuxtConfig({
  app: {
    head: {
      link: [
        {
          rel: 'stylesheet',
          href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css'
        }
      ]
    }
  }
})

动态添加样式表

你可以使用usehead组合式函数在代码中动态设置head中的值。

useHead({
  link: [{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' }]
})

Nuxt在内部使用unhead

使用Nitro插件修改渲染的head

如果你需要更高级的控制,你可以使用hook拦截渲染的html,并以编程方式修改head/

创建一个插件,放在~/server/plugins/my-plugin.ts中,像这样:

export default defineNitroPlugin((nitro) => {
  nitro.hooks.hook('render:html', (html) => {
    html.head.push('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">')
  })
})

外部样式表是渲染阻塞资源:它们必须在浏览器渲染页面之前加载和处理。包含过大样式的网页渲染时间更长。

使用预处理器

要使用Scss/Sass/less或stylus等预处理器,首先要安装它。

编写样式表的自然位置是assets目录。然后,你可以使用你的预处理器的语法在你的app.vue(或布局文件)中导入你的源文件。

<style lang="scss">
  @use "~/assets/scss/main.scss";
</style>

或者,你可以使用Nuxt配置的css属性。

export default defineNuxtConfig({
  css: ['~/assets/scss/main.scss']
})

如果你需要再预处理文件中注入代码,比如包含颜色变量的sass部分,你可以使用Vite的preprocessors选项来实现。上文中有示例。

单文件组件(SFC)样式化

Vue和SFC的最大优点之一就是在处理样式方面非常好用。你可以直接在组件文件的央视快中编写CSS或预处理器代码,因此你可以拥有出色的开发体验,而无需使用CSS-in-JS等工具。不过,如果你想使用CSS-in-JS,你可以找到支持它的第三方库和模块,比如pinceau。

类和样式绑定

你可以利用Vue SFC的特性,使用classstyle属性为组件添加样式。

Ref和Reactive
<script setup lang="ts">
const isActive = ref(true);
const hasError = ref(false);
const classObject = reactive({
  active: true,
  'text-danger': false
})
</script>

<template>
  <div class="static" :class="{active: isActive, 'text-danger': hasError}"></div>
  <dov :class="classObject"></dov>
</template>

使用v-bind动态样式

你可以在样式块中使用v-bind函数引用JavaScript变量和表达式。绑定将是动态的,这意味着如果变量值发生变化,样式将会更新。

<script setup lang="ts">
const color = ref("red")
</script>

<template>
  <div class="text">hello</div>
</template>

<style>
.text {
  color: v-bind(color);
}
</style>

作用域样式

scoped属性允许你将样式应用于组件内部。使用此属性声明的样式将只适用于该组件。

<template>
  <div class="example">hi</div>
</template>

<style scoped>
.example {
  color: red;
}
</style>

CSS模块

你可以使用module属性和CSS Modules。可以使用注入的$style变量访问它。

<template>
  <p :class="$style.red">This should be red</p>
</template>

<style module>
.red {
  color: red;
}
</style>

预处理器支持

SFC样式块支持预处理器语法。Vite内置支持.scss.sass.less.styl.stylus文件,无需配置,你只需要安装它们,然后在SFC中使用lang属性直接引用它们。

使用PostCSS

Nuxt内置了Postcss。你可以在nuxt.config文件中配置它。

export default defineNuxtConfig({
  postcss: {
    plugins: {
      'postcss-nested': {},
      'postcss-custom-media': {}
    }
  }
})

为了在SFC中实现正确的语法高亮,你可以使用postcss lang属性。

<style lang="postcss">
  /* 在这里编写 stylus */
</style>

默认情况下,Nuxt已经预配置了以下插件:

  • postcss-import: 改进了@import规则
  • postcss-url: 转换url()语句
  • autoprefixer: 自动添加产商前缀
  • cssnano: 压缩和清除无用的css

利用布局实现多样式

如果你需要完全不同的样式来样式化应用程序的不同部分,你可以使用布局。为不同的布局使用不同的样式。

<template>
  <div class="default-layout">
    <h1>默认布局</h1>
    <slot />
  </div>
</template>

<style>
.default-layout {
  color: red;
}
</style>

第三方库和模块

Nuxt在样式化方面没有固定的意见,提供了各种各样的选择。你可以使用任何你想要的样式工具,比如流行的库UnoCSS或Tailwind CSS。

社区和Nuxt团队开发了许多Nuxt模块,以便更轻松的集成,你可以在网站的modules部分发现它们。以下是一些帮助你入门的模块:

  • UnoCSS: 即时按需的原子CSS引擎
  • Tailwind CSS: 实用优先的CSS框架
  • Fontaine: 字体度量回退
  • Pinceau: 使用性样式框架
  • Nuxt UI: 现代Web应用程序的UI库

Nuxt模块为你提供了良好的开发体验,但请记住,如果你喜欢的工具没有一个模块,这并不意味着你不能再Nuxt中使用它!你可以为自己的项目自行配置它。根据工具的不同,你可能需要使用Nuxt插件和/或制作自己的模块。如果你这样做了,请与社区分享!

轻松加载Web字体

你可以使用Nuxt Google Fonts模块来加载Google字体。

如果你正在使用UnoCSS,请注意它附带了一个Web字体预设,方便地从常见提供商加载字体。

进阶

过渡效果

Nuxt拥有与Vue相同的<transition>元素,并且还支持实验性的View Transitions API。

字体高级优化

我们建议使用Fontaine来减少你的CLS。如果你需要更高级的功能,可以考虑创建一个Nuxt模块来扩展构建过程或Nuxt运行时。

始终记得利用web生态系统中可用的各种工具和技术,使你的应用程序的样式更加简单高效。无论你是使用原生CSS、预处理器、PostCSS、UI库还是模块,Nuxt都能满足你的需求。祝你样式编写愉快!

LCP高级优化

你可以采取以下措施来加快全局CSS文件的下载速度:

  • 使用CDN,让文件物理上更接近你的用户
  • 压缩你的资源,最好使用Brotli
  • 使用HTTP2/HTTP3进行传递
  • 将你的资源托管在同一个域名下(不要使用不同的子域名)

如果你正在使用Cloudflare、Netlify或Vercel等现代平台,大多数情况下这些事情应该会自动完成。 你可以在web.dev上找到一个LCP优化指南。

如果你的所有CSS都由Nuxt内联,你可以(实验性地)完全停止在渲染的HTML中引用外部CSS文件。 你可以通过一个钩子来实现,你可以将它放在一个模块中或者你的Nuxt配置文件中。

export default defineNuxtConfig({
  hooks: {
    'build:manifest': (manifest) => {
      // 找到应用程序入口的CSS列表
      const css = manifest['node_modules/nuxt/dist/app/entry.js']?.css
      if (css) {
        // 从数组的末尾开始,向前遍历
        for (let i = css.length - 1; i >= 0; i--) {
          // 如果以'entry'开头,从列表中删除它
          if (css[i].startsWith('entry')) css.splice(i, 1)
        }
      }
    }
  }
})

路由

Nuxt的核心功能之一是文件系统路由。pages/目录中的每个Vue文件都会创建一个相应的URL(或路由),用于显示文件的内容。通过为每个页面使用动态导入,Nuxt利用代码分割来仅加载所需路由的最小量JavaScript。

页面

Nuxt的路由基于vue-router,根据pages/目录中创建的每个组件的文件名生成路由。

文件系统路由使用命名约定来创建动态和嵌套路由。

目录结构
| pages/
---| about.vue
---| index.vue
---| posts/
-----| [id].vue

导航

<NuxtLink>组件用于在页面直接创建连接。它会将<a>标签渲染为具有href属性设置为页面的路由。一旦应用程序被渲染,页面的切换将在JavaScript中进行,通过更新浏览器URL来实现。这样可以避免整页刷新,同时允许实现动画过渡效果。

<NuxtLink>在客户端视口中可见时,Nuxt会自动预取链接页面的组件和负载(生成的页面),从而加快导航速度。

<template>
  <header>
    <nav>
      <ul>
        <li><NuxtLink to="/about">关于</NuxtLink></li>
        <li><NuxtLink to="/posts/1">文章1</NuxtLink></li>
        <li><NuxtLink to="/posts/2">文章2</NuxtLink></li>
      </ul>
    </nav>
  </header>
</template>

路由参数

useRoute()组合式函数可在Vue组件的<script setup>块或setup()方法中使用,以访问当前路由的详细信息。

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

// 当访问/posts/1时,route.params.id将为1
console.log(route.params.id)
</script>

路由中间件

Nuxt提供了一个可自定义的路由中间件框架,您可以在应用程序中使用,非常适合提取在导航到特定路由之前要运行的代码。

路由中间件在Nuxt应用程序的Vue部分中运行。尽管名称相似,但它们与在应用程序的Nitro服务器部分中运行的服务器中间件完全不同。

有三种类型的路由中间件:

  1. 匿名(或内联)路由中间件,直接在使用它们的页面中定义。
  2. 命名路由中间件,放置在middleware/目录中,当在页面中使用时,会通过异步导入自动加载。(注意:路由中间件名称会转换为短横线分隔命名,因此someMiddleware会变成some-middleware
  3. 全局路由中间件,放置在middleware/目录中(使用.global后缀),将在每次路由更改时自动运行。

以下是保护/dashboard页面的auth中间件的示例:

middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  // isAuthenticated()是一个验证用户是否已经认证的示例方法
  if (isAuthenticated() === false) {
    return navigateTo('/login')
  }
})

路由验证

Nuxt通过每个要验证的页面中的definePageMeta()validate属性提供路由验证。

validate属性接受route作为参数。您可以返回一个布尔值来确定是否将此路由视为有效路由以渲染此页面。如果返回false,并且找不到其他匹配项,这将导致404错误。您还可以直接返回一个带有statusCode/statusMessage的对象以立即响应错误(其他匹配项将不会被检查)。

如果你有更复杂的用例,可以使用匿名路由中间件代替。

<script setup lang="ts">
definePageMeta({
  validate: async (route) => {
    // 检查id是否由数字组成
    return /^\d+$/.test(route.params.id)
  }
})
</script>

SEO和Meta

使用强大的头部配置、组合函数和组件来提升你的Nuxt应用的SEO。

默认值

Nuxt提供了合理的默认值,如果需要的话,你可以进行覆盖。

export default defineNuxtConfig({
  app: {
    head: {
      charset: 'utf-8',
      viewport: 'width=device-width, initial-scale=1'
    }
  }
})

在你的nuxt.config.ts文件中提供app.head属性,可以自定义整个应用的头部。

该方法不允许你提供响应式数据。我们建议在`app.vue`中使用`useHead()`。

为了使配置更简单,可以使用快捷方式:charsetviewport。你还可以在类型中提供下面列出的任何键。

useHead

useHead组合函数允许你以编程和响应式的方式管理头部标签,它由Unhead提供支持。

和所有组合函数一样,它只能在组件的setup和生命周期钩子中使用。

<script setup lang="ts">
useHead({
  title: '我的应用',
  meta: [
    { name: 'description', content: '我的神奇网站。' }
  ],
  bodyAttrs: {
    class: 'test'
  },
  script: [ { innerHTML: 'console.log(\'Hello world\')' } ]
})
</script>

我们建议查看useHeaduseHeadSafe组合函数。

useSeoMeta

useSeoMeta组合函数允许你将站点的SEO元标签定义为一个扁平的对象,并提供完整的TypeScript支持。

这有助于避免拼写错误和常见错误,比如使用name而不是property

<script setup lang="ts">
useSeoMeta({
  title: '我的神奇网站',
  ogTitle: '我的神奇网站',
  description: '这是我的神奇网站,让我来告诉你关于它的一切。',
  ogDescription: '这是我的神奇网站,让我来告诉你关于它的一切。',
  ogImage: 'https://example.com/image.png',
  twitterCard: 'summary_large_image',
})
</script>

组件

Nuxt提供了<Title><Base><NoScript><Style><Meta><Link><Body><Html><Head>组件,让你可以直接在组件的模板中与元数据进行交互。

由于这些组件名称与原生HTML元素相匹配,在模板中将它们大写非常重要。

<Head><Body>可以接受嵌套的元标签(出于美观的原因),但这对最终HTML中嵌套的元标签的渲染位置没有影响。

<script setup lang="ts">
  const title = ref('你好,世界')
</script>

<template>
  <div>
    <Head>
      <Title>{{title}}</Title>
      <Meta name="description" :content="title" />
      <Style type="text/css" children="body {background: green;}" />
    </Head>
    <h1>{{title}}</h1>
  </div>
</template>

类型

下面是用于useHeadapp.head和组件的非响应式类型。

interface MetaObject {
  title?: string
  titleTemplate?: string | ((title?: string) => string)
  templateParams?: Record<string, string | Record<string, string>>
  base?: Base
  link?: Link[]
  meta?: Meta[]
  style?: Style[]
  script?: Script[]
  noscript?: Noscript[];
  htmlAttrs?: HtmlAttributes;
  bodyAttrs?: BodyAttributes;
}

更详细的类型信息请参考 @unhead/schema。

功能

响应式

所有属性都支持响应式,包括计算属性、getter和响应式。

建议使用getter(() => value)而不是计算属性computed(() => value)

useHead
<script setup lang="ts">
const description = ref('我的神奇网站。')

useHead({
  meta: [
    { name: 'description', content: description }
  ],
})
</script>

标题模板

你可以使用titleTemplate选项来提供一个动态模板,以自定义站点的标题,例如在每个页面的标题中添加站点名称。

titleTemplate可以是一个字符串,其中%s会被标题替换,也可以是一个函数。

如果你想使用一个函数(以获得更多的控制),那么它不能在nuxt.config中设置,而是建议在app.vue文件中设置,这样它将适用于你站点上的所有页面:

<script setup lang="ts">
useHead({
  titleTemplate: (titleChunk) => {
    return titleChunk ? `${titleChunk} - 网站名称` : '网站名称';
  }
})
</script>

现在,如果你在你站点的另一个页面上使用useHead将标题设置为我的页面,在浏览器标签中,标题将显示为我的页面 - 网站名称。你还可以传递null来使用默认的站点标题。

Body标签

你可以在适用的标签上使用tagPosition: 'bodyClose'选项将它们附件到<body>标签的末尾。

例如:

<script setup lang="ts">
useHead({
  script: [
    {
      src: 'https://third-party-script.com',
      // 有效选项为:'head' | 'bodyClose' | 'bodyOpen'
      tagPosition: 'bodyClose'
    }
  ]
})
</script>

示例

使用definePageMeta

在你的pages/目录下,你可以使用definePageMetauseHead来根据当前路由设置元数据。

例如,你可以先设置当前页面的标题(这是在构建时通过宏提取的,因此不能动态设置):

<script setup lang="ts">
definePageMeta({
  title: '某个页面'
})
</script>

然后在你的布局文件中,你可以使用之前设置的路由元数据:

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

useHead({
  meta: [{ property: 'og:title', content: `应用名称 - ${route.meta.title}` }]
})
</script>

动态标题

在下面的示例中,titleTemplate可以被设置为带有%s占位符的字符串,也可以被设置为一个函数,这样可以更灵活地为你的Nuxt应用的每个路由动态设置页面标题:

<script setup lang="ts">
useHead({
  // 作为字符串,
  // 其中`%s`会被标题替换
  titleTemplate: '%s - 网站标题',
  // ... 或者作为一个函数
  titleTemplate: (productCategory) => {
    return productCategory
      ? `${productCategory} - 网站标题`
      : '网站标题'
  }
})
</script>

nuxt.config也可以用作设置页面标题的替代方法。然而,nuxt.config不允许页面标题是动态的。因此,建议在app.vue文件中使用titleTemplate来添加动态标题,然后应用于你的Nuxt应用的所有路由。

外部CSS

下面的示例展示了如何使用useHead组合式函数的link属性或使用<Link>组件来启用Google Fonts:

useHead
<script setup lang="ts">
useHead({
  link: [
    {
      rel: 'preconnect',
      href: 'https://fonts.googleapis.com'
    },
    {
      rel: 'stylesheet',
      href: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',
      crossorigin: ''
    }
  ]
})
</script>

过渡效果

使用Vue或本地浏览器的视图过渡在页面和布局之间应用过渡效果。

Nuxt利用Vue的Transition组件在页面和布局之间应用过渡效果。

页面过渡

你可以启用页面过渡来为所有的页面应用自动过渡效果。

export default defineNuxtConfig({
  app: {
    pageTansition: {name: 'page', mode: 'out-in'}
  }
})

如果你同时更改布局和页面,这里设置的页面过渡效果将不会运行。相反,你应该设置布局过渡效果。

要开始在页面之间添加过渡效果,请在你的app.vue文件中添加以下CSS:

app.vue
<template>
  <NuxtPage />
</template>
<style>
  .page-enter-active,
  .page-leave-active {
    transition: all .4s;
  }
  .page-enter-from,
  .page-leave-to {
    opacity: 0;
    filter: blur(1rem);
  }
</style>

在页面之间导航时,将产生如下结果:

要为页面设置不同的过渡效果,请在页面的definePageMeta中设置pageTransition键:

pages/about.vue
<script setup lang="ts">
  definePageMeta({
    pageTransition: {
      name: 'rotate'
    }
  })
</script>

切换到关于页面时,将添加3D旋转效果:

布局过渡

你可以启用布局过渡来为所有的布局应用自动过渡效果。

export default defineNuxtConfig({
  app: {
    layoutTramsition: {name: 'layout', mode: 'out-in'}
  }
})

要开始在页面和布局之间添加过渡效果,请在你的app.vue文件中添加以下CSS:

app.vue
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

<style>
.layout-enter-active,
.layout-leave-active {
  transition: all 0.4s;
}
.layout-enter-from,
.layout-leave-to {
  filter: grayscale(1);
}
</style>

在页面之间导航时,将产生如下结果:

pageTransition类似,你可以使用definePageMeta将自定义的layoutTransition应用到页面组件:

<script setup lang="ts">
  definePageMeta({
    layout: 'orange',
    layoutTransition: {
      name: 'slide-in'
    }
  })
</script>

全局设置

你可以使用nuxt.config全局自定义这些默认的过渡效果名称。

pageTransitionlayoutTransition键都接受transitionProps作为JSON可序列化的值,你可以通过它传递自定义CSS过渡的namemode和其他有效的过渡属性。

export default defineNuxtConfig({
  app: {
    pageTransition: {
      name: 'fade',
      mode: 'out-in' // 默认值
    },
    layoutTransition: {
      name: 'slide',
      mode: 'out-in' // 默认值
    }
  }
})

如果你更改了name属性,你还必须响应的重新命名CSS类。

要覆盖全局过渡效果,使用definePageMeta为单个Nuxt页面定义页面或布局过渡,并覆盖在nuxt.config文件中全局定义的任何页面或布局过渡。

<script setup lang="ts">
  definePageMeta({
    pageTransition: {
      name: 'bounce',
      mode: 'out-in'
    }
  })
</script>

禁用过渡效果

可以为特定的路由禁用pageTransitionlayoutTransition

<script setup lang="ts">
  definePageMeta({
    pageTransition: false,
    layoutTransition: false
  })
</script>

或在nuxt.config中全局禁用:

defineNuxtConfig({
  app: {
    pageTransition: false,
    layoutTransition: false
  }
})

JavaScript钩子

对于高级用例,你可以使用JavaScript钩子为Nuxt页面创建高度动态和自定义的过渡效果。

这种方式非常适合使用FSAP等JavaScript动画库。

<script setup lang="ts">
  definePageMeta({
    pageTransition: {
      name: 'custom-flip',
      mode: 'out-in',
      onBeforeEnter: (el) => {
        console.log('进入之前...')
      },
      onEnter: (el, done) => {},
      onAfterEnter: (el) => {}
    }
  })
</script>

动画过渡效果

要使用条件逻辑应用动态过渡效果,你可以利用内联middleware将不同的过渡名称分配给to.meta.pageTransition

pages/[id].vue
<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'slide-right',
    mode: 'out-in'
  },
  middleware (to, from) {
    to.meta.pageTransition.name = +to.params.id > +from.params.id ? 'slide-left' : 'slide-right'
  }
})
</script>

<template>
  <h1>#{{ $route.params.id }}</h1>
</template>

<style>
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
  transition: all 0.2s;
}
.slide-left-enter-from {
  opacity: 0;
  transform: translate(50px, 0);
}
.slide-left-leave-to {
  opacity: 0;
  transform: translate(-50px, 0);
}
.slide-right-enter-from {
  opacity: 0;
  transform: translate(-50px, 0);
}
.slide-right-leave-to {
  opacity: 0;
  transform: translate(50px, 0);
}
</style>

页面现在前往下一个id时应用slide-left过渡效果,在前一个id时应用slide-right过渡效果:

使用NuxtPage的过渡效果

当在app.vue中使用<NuxtPage />时,可以直接将过渡属性作为组件属性传递以激活全局过渡效果。

<template>
  <div>
    <NuxtLayout>
      <NuxtPage :transition="{
        name: 'bounce',
        mode: 'out-in'
      }" />
    </NuxtLayout>
  </div>
</template>

请记住,此页面过渡效果无法通过在单个页面上使用definePageMeta来覆盖。

视图过渡API(实验性)

nuxt附带了一个实验性的视图过渡API。这是一种令人兴奋的新方法,用于实现本地浏览器过渡效果,它具有在不同页面的相关元素之间进行过渡的能力,以及其他功能。

Nuxt的集成正在积极开发中,但可以通过配置文件中的experimental.viewTransition选项启用:

export default defineNuxtConfig({
  experimental: {
    viewTransition: true
  }
})

如果你还使用了类似于pageTransitionlayoutTransition(参见上文)的Vue过渡效果来实现与新的视图过渡API相同的结果,那么你可能希望在用户的浏览器支持较新的本地Web API时_禁用_Vue过渡效果。你可以通过创建~/middleware/disable-vue-transitions.global.ts文件并包含以下内容来实现:

export default defineNuxtRouteMiddleware(to => {
  if (!document.startViewTransition) { return }

  // 禁用内置的Vue过渡效果
  to.meta.pageTransition = false
  to.meta.layoutTransition = false
})

已知问题

  • 由于上游Vue的一个bugopen in new window,视图过渡可能无法按预期工作,特别是在嵌套页面/布局/异步组件中。如果你使用这种模式,你可能需要延迟采用这个实验性功能或自己实现它。非常欢迎您的反馈。
  • 如果在页面设置函数中执行数据获取操作,您可能需要重新考虑暂时不使用此功能。(按设计,视图过渡在进行时完全冻结DOM更新。)我们正在考虑将视图过渡限制在<Suspense>解析之前的最后时刻,但在此期间,如果您符合上述情况,您可能需要仔细考虑是否采用此功能。

数据获取

Nuxt提供了组合函数来处理应用程序中的数据获取。

Nuxt提供了两个组合函数和一个内置库,用于在浏览器或服务器环境中执行数据获取:useFetchuseAsuncData$fetch

简而言之:

  • useFetch是在组件设置函数中处理数据获取的最简单方法。
  • $fetch可以根据用户交互进行网络请求。
  • useAsyncData结合$fetch,提供了更精细的控制。

useFetchuseAsyncData共享一组常见的选项和模式,在后面的章节中我们将详细介绍。

在此之前,首先要知道为什么需要这些组合函数。

为什么需要特定的组合函数?

使用像Nuxt这样的框架可以在客户端和服务器环境中执行调用调用和呈现页面时,必须解决一些问题。这就是为什么Nuxt提供了组合函数来封装查询,而不是让开发者仅依赖于$fetch调用。

网络请求重复

useFetchuseAsyncData组合函数确保一旦在服务器上进行了API调用,数据将以有效的方式在负载中传递到客户端。

负载时通过useNuxtApp().payload访问的JavaScript对象。它在客户端上用于避免在浏览器中执行代码时重新获取相同的数据。

Suspense

Nuxt在底层使用Vue的<Suspense>组件,以防止在视图中的每个异步数据可用之前导航。数据获取组合函数可以帮助您利用此功能,并根据每个调用的需求使用最适合的方法。

UseFetch

useFetch组合函数是执行数据获取的最简单的方法。

<script setup lang="ts">
  const {data: count} = await useFetch('/api/count');
</script>
<template>
  页面访问量:{{ count }}
</template>

这个组合函数是useAsyncData组合函数和$fetch工具的封装。

$fetch

Nuxt包括了ofetch库,并且作为全局别名$fetch自动导入到应用程序中。它是useFetch在幕后使用的工具。

const users = await $fetch('/api/users').catch(e => e.data)

请注意,仅使用$fetch将不会提供网络请求重复和导航阻止。建议在提交数据到事件处理程序时使用 $fetch ,在客户端逻辑中使用,或与useAsyncData结合使用。

ofetch库是基于Fetch API构建的,并为其添加了便利功能:

  • 在浏览器、Node或worker环境中的使用方式相同
  • 自动解析响应
  • 错误处理
  • 自动重试
  • 拦截器

useAsyncData

useAsyncData组合函数负责封装异步逻辑并在解析完成后返回结果。

事实上,useFetch(url)几乎相当于useAsyncData(url, () => $fetch(url)),它是为最常见的用例提供的开发者体验糖。

在某些情况下,使用useFetch组合函数是不合适的,例如当CMS或第三方提供自己的查询层时。在这种情况下,您可以使用useAsyncData来封装您的调用,并仍然保持组合函数提供的好处。

useAsyncData的第一个参数是用于缓存第二个参数(查询函数)的响应的唯一键。如果直接传递查询函数,则可以忽略该参数。在这种情况下,它将自动生成。

const { data, error } = await useAsyncData('users', () => myGetFunction('users'))

由于自动生成的键仅考虑调用useAsyncData的文件和行,因此建议始终创建自己的键以避免不需要的行为,如果您正在创建自己的自定义组合函数并封装useAsyncData

const id = ref(1)
const {data, error} = await useAsyncData(`user: ${id.value}`, () => {
  return myGetFUnction('users', {id: id.value})
}) 

useAsyncData组合函数是包装和等待多个useFetch完成,并获取每个结果的绝佳方式。

const { data: discounts, pending } = await useAsyncData('cart-discount', async () => {
  const [coupons, offers] = await Promise.all([$fetch('/cart/coupons'), $fetch('/cart/offers')])

  return {
    coupons,
    offers
  }
})

选项

useAsyncDatauseFetch返回相同的对象类型,并接受一组常见选项作为最后一个参数。它们可以帮助您控制组合函数的行为,例如导航阻止、缓存或执行。

懒加载

默认情况下,数据获取的组合函数会在异步函数解析完成之前使用Vue的Suspense进行页面导航。通过使用lazy选项,可以忽略此功能在客户端导航时的使用。在这种情况下,你需要手动处理加载状态,使用pending值。

<script setup lang="ts">
const { pending, data: posts } = useFetch('/api/posts', {
  lazy: true
})
</script>

<template>
  <!-- 你需要处理加载状态 -->
  <div v-if="pending">
    加载中...
  </div>
  <div v-else>
    <div v-for="post in posts">
      <!-- 做一些操作 -->
    </div>
  </div>
</template>

你还可以使用useLazyFetchuseLazyAsyncData作为方便的方法来执行相同的操作。

const {pending, data: posts} = useLazyFetch('/api/posts')

仅在客户端获取数据

默认情况下,数据获取的组合函数会在客户端和服务器环境中执行其异步函数。将server选项设置为false,只在客户端执行调用。在初始化加载时,在水合过程完成之前不会获取数据,因此你需要处理一个加载状态,但在随后的客户端导航中,数据将在加载页面之前等待获取。

lazy选项结合使用,这对于首次渲染不需要的数据(例如,非SEO敏感数据)非常有用。

/* 此调用在水合之前执行 */
const { article } = await useFetch('api/article')

/* 此调用仅在客户端执行 */
const { pending, data: posts } = useFetch('/api/comments', {
  lazy: true,
  server: false
})

useFetch组合函数用于在设置方法中调用,或在生命周期钩子函数的函数顶层直接调用,否则你应该使用$fetch方法。

减小有效负载大小

pick选项可帮助你通过仅选择你想要从组合函数返回的字段来减少存储在HTML文档中的有效负载大小。

<script setup lang="ts">
/* 仅选择模板中使用的字段 */
const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['title', 'description'] })
</script>

<template>
  <h1>{{ mountain.title }}</h1>
  <p>{{ mountain.description }}</p>
</template>

如果需要更多的控制或映射多个对象,可以使用transform函数来修改查询结果。

const { data: mountains } = await useFetch('/api/mountains', { 
  transform: (mountains) => {
    return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
  }
})

pick和transform都不会阻止初始时获取不需要的数据,但它们将阻止不需要的数据被添加到到从服务器传输到客户端的有效负载中。

缓存和重新获取数据

useFetchuseAsyncData使用键来防止重新获取想吐的数据。

  • useFetch使用提供的URL作为键。或者,可以在作为最后一个参数传递的options对象中提供key值。
  • useAsyncData如果第一个参数是字符串,则将其用作键。如果第一个参数是执行查询的处理函数,则会为useAsyncData的实例生成一个基于文件名和行号的唯一键。

要根据键获取缓存的数据,可以使用useNuxtData

刷新和执行

如果要手动获取或刷新数据,请使用组合函数提供的executerefresh函数。

<script setup lang="ts">
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>

<template>
  <div>
    <p>{{ data }}</p>
    <button @click="refresh">刷新数据</button>
  </div>
</template>

execute函数是refresh的别名,使用方式完全形同,但在非立即的情况下更语义化。

监听

如果希望在应用程序中的其他响应式值更改时重新运行获取函数,请使用watch选项。可以将其用于一个或多个可监听的元素。

const id = ref(1)

const { data, error, refresh } = await useFetch('/api/users', {
  /* 更改id将触发重新获取 */
  watch: [id]
})

请注意,监听响应式值不会更改获取的URL。例如,这将保持获取用户的相同初始ID,因为URL是在调用函数是构建的。

const id = ref(1)

const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
  watch: [id]
})

如果需要基于响应式值更改URL,可以使用计算URL。

计算URL

有时,您可能需要从响应式值计算URL,并在每次更改时刷新数据,不需要费力的解决此问题,您可以将每个参数作为响应式值附加。Nuxt将自动使用响应式值并在每次更改时重新获取。

const id = ref(null)

const { data, pending } = useLazyFetch('/api/user', {
  query: {
    user_id: id
  }
})

在URL构建更复杂的情况下,可以使用回调函数作为计算getter,返回URL字符串。

每当依赖关系更改时,将使用新构建的URL获取数据。将其与非立即结合使用,可以在响应元素更改之前等待获取。

<script setup lang="ts">
const id = ref(null)

const { data, pending, status } = useLazyFetch(() => `/api/users/${id.value}`, {
  immediate: false
})
</script>

<template>
  <div>
    <!-- 在获取期间禁用输入 -->
    <input v-model="id" type="number" :disabled="pending"/>

    <div v-if="status === 'idle'">
      输入用户ID
    </div>
    
    <div v-else-if="pending">
      加载中...
    </div>

    <div v-else>
      {{ data }}
    </div>
  </div>
</template>

如果需要在其他响应式值更改时强制刷新,还可以监听其他值。

不立即执行

useFetch组合函数在调用时会立即开始获取数据。你可以通过设置immediate: fakse来阻止立即执行,例如,等待用户交互。

为此,你需要使用status来处理获取生命周期,并使用execute来开始数据获取。

<script setup lang="ts">
const { data, error, execute, pending, status } = await useLazyFetch('/api/comments')
</script>

<template>
  <div v-if="status === 'idle'">
    <button @click="execute">获取数据</button>
  </div>

  <div v-else-if="pending">
    加载评论中...
  </div>

  <div v-else>
    {{ data }}
  </div>
</template>

为了更精细的控制,status变量可以有以下取值:

  • idle: 获取未开始
  • pending: 获取已开始但尚未完成
  • error: 获取失败
  • success: 获取成功完成

传递请求头和Cookie

当我们在浏览器中调用$fetch时,用户的请求头(如cookie)会直接发送到API。但在服务器端渲染期间,由于$fetch请求在服务器内部进行,它不包含用户浏览器的Cookie,也不会传递来自获取响应的Cookie。

将客户端请求头传递到API

我们可以使用useRequestHeaders来访问和代理服务器端的Cookie到API。

下面的示例将请求头添加到同构的$fetch调用中,以确保API端点能够访问用户最初发送的相同cookie请求头。

const headers = useRequestHeaders(['cookie'])
const {data} = await useFetch('/api/me', {headers})

在代理请求头到外部API之前,请谨慎选择需要包含的请求头。并非所有的请求头都可以安全地绕过,可能会引入不希望的行为。以下是一些不应该代理的常见请求头的列表:

  • host、accept
  • content-length、content-md5、content-type
  • x-forwarded-host、x-forwarded-port、x-forwarded-proto
  • cf-connecting-ip、cf-ray

从服务器端API调用中传递Cookie到SSR响应

如果你想要将Cookie传递/代理到另一个方向,从内部请求返回到客户端,你需要自行处理。

import { appendResponseHeader, H3Event } from 'h3'

export const fetchWithCookie = async (event: H3Event, url: string) => {
  /* 从服务器端点获取响应 */
  const res = await $fetch.raw(url)
  /* 从响应中获取 Cookie */
  const cookies = (res.headers.get('set-cookie') || '').split(',')
  /* 将每个 Cookie 添加到我们的传入请求中 */
  for (const cookie of cookies) {
    appendResponseHeader(event, 'set-cookie', cookie)
  }
  /* 返回响应的数据 */
  return res._data
}
// 这个组合函数将自动将 Cookie 传递给客户端
const event = useRequestEvent()

const result = await fetchWithCookie(event, '/api/with-cookie')

onMounted(() => console.log(document.cookie))

选项API支持

Nuxt3提供了一种在选项API中执行的asyncData获取数据的方式。你必须将组件定义包装在defineNuxtComponent中才能使用此功能。

<script>
export default defineNuxtComponent({
  /* 使用 fetchKey 选项提供一个唯一的键 */
  fetchKey: 'hello',
  async asyncData () {
    return {
      hello: await $fetch('/api/hello')
    }
  }
})
</script>

序列化

当从server目录获取数据时,响应会使用JSON.stringify进行序列号。然而,由于序列化仅限于JavaScript原始类型,Nuxt会尽其所能将$fetchuseFetch的返回类型转换为匹配实际值的类型。

示例

export default defineEventHandler(() => {
  return new Date()
})
<script setup lang="ts">
// 尽管我们返回了一个 Date 对象,但 `data` 的类型被推断为字符串
const { data } = await useFetch('/api/foo')
</script>

自定义序列化函数

要自定义序列化行为,你可以在返回的对象上定义一个toJSON方法。如果定义了toJSON方法,Nuxt将尊重函数的返回类型,而不会尝试转换类型。

export default defineEventHandler(() => {
  const data = {
    createdAt: new Date(),

    toJSON() {
      return {
        createdAt: {
          year: this.createdAt.getFullYear(),
          month: this.createdAt.getMonth(),
          day: this.createdAt.getDate(),
        },
      }
    },
  }
  return data
})
<script setup lang="ts">
// `data` 的类型被推断为
// {
//   createdAt: {
//     year: number
//     month: number
//     day: number
//   }
// }
const { data } = await useFetch('/api/bar')
</script>

使用替换序列化器

Nuxt当前不支持将JSON.stringify替换为其他序列化器。但是,你可以将负载返回为普通字符串,并利用toJSON方法来保持类型安全。在下面的示例中,我们使用superjson作为序列化器。

import superjson from 'superjson'

export default defineEventHandler(() => {
  const data = {
    createdAt: new Date(),

    // 绕过类型转换
    toJSON() {
      return this
    }
  }

  // 使用 superjson 将输出序列化为字符串
  return superjson.stringify(data) as unknown as typeof data
})
<script setup lang="ts">
  import superjson from 'superjson'

  // `date` 被推断为 { createdAt: Date },你可以安全地使用 Date 对象的方法
  const { data } = await useFetch('/api/superjson', {
    transform: (value) => {
      return superjson.parse(value as unknown as string)
    },
  })
</script>

状态管理

Nuxt提供了强大的状态管理库和useState组合函数,用于创建响应式且适用于SSR的共享状态。

Nuxt提供了useState组合函数,用于在组件之间创建响应式且适用于SSR的共享状态。

useState是一个适用于SSR的ref替代品。它的值将在服务器端渲染后保留(在客户端渲染期间进行hydration`),并通过唯一的键在所有组件之间共享。

由于useState内部的数据将被序列化为JSON,因此重要的是它不包含无法序列化的内容,比如类、函数或符号。

最佳实践

注意

不要在script setup>setup()函数之外定义const state = ref()。 这样的状态将在所有访问你网站的用户之间共享,并可能导致内存泄漏!

示例

基本用法

在这个示例中,我们使用一个组件本地的计数器状态。任何使用useState('counter')的其他组件都共享同一个响应式状态。

<script setup lang="ts">
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>

<template>
  <div>
    计数器:{{ counter }}
    <button @click="counter++">
      +
    </button>
    <button @click="counter--">
      -
    </button>
  </div>
</template>

要全局失效缓存的状态,请参考clearNuxtState工具。

高级用法

在这个示例中,我们使用一个组合函数从HTTP请求头中检测用户的默认语言环境,并将其保存在一个locale状态中。

composables/locale.ts
import type { Ref } from 'vue'

export const useLocale = () => {
  return useState<string>('locale', () => useDefaultLocale().value)
}

export const useDefaultLocale = (fallback = 'en-US') => {
  const locale = ref(fallback)
  if (process.server) {    
    const reqLocale = useRequestHeaders()['accept-language']?.split(',')[0]
    if (reqLocale) {
      locale.value = reqLocale
    }
  } else if (process.client) {
    const navLang = navigator.language
    if (navLang) {
      locale.value = navLang
    }
  }
  return locale
}

export const useLocales = () => {
  const locale = useLocale()
  const locales = ref([
    'en-US',
    'en-GB',
    ...
    'ja-JP-u-ca-japanese'
  ])
  if (!locales.value.includes(locale.value)) {
    locales.value.unshift(locale.value)
  }
  return locales
}

export const useLocaleDate = (date: Ref<Date> | Date, locale = useLocale()) => {
  return computed(() => new Intl.DateTimeFormat(locale.value, { dateStyle: 'full' }).format(unref(date)))
}

共享状态

通过使用自动导入的组合函数,我们可以定义全局类型安全的状态并在整个应用程序中导入它们。

export const useCounter = () => useState<number>('counter', () => 0)
export const useColor = () => useState<string>('color', () => 'pink')
<script setup lang="ts">
const color = useColor() // 与useState('color')相同
</script>

<template>
  <p>当前颜色:{{ color }}</p>
</template>

使用第三方库

Nuxt曾经依赖于Vuex库来提供全局状态管理。如果你正在从Nuxt 2迁移,请参阅迁移指南。

Nuxt对状态管理不持有特定观点,所有请根据你的需求选择合适的解决方案。Nuxt与最流行的状态管理库有多种集成方式,包括:

  • pinia - 官方推荐的Vue状态管理库
  • Harlem - 不可变的全局状态管理库
  • XState - 基于状态机的方法,具有可视化和测试状态逻辑的工具

错误处理

Nuxt3是一个全栈框架,这意味着在不同的上下文中可能会发生几种无法预防的用户运行时错误:

  • Vue渲染声明周期中的错误(SSR和CSR)
  • Nitro服务器生命周期中的错误(server/目录)
  • 服务器和客户端启动错误(SSR和CSR)
  • 下载JS chunk时错误

SSR代表 Server-Side Rendering,CSR代表 Client-Side Rendering

Vue渲染声明周期

  1. 可以使用onErrorCaptured来捕获Vue错误。
  2. Nuxt提供了Vue:error钩子,如果有任何错误传播到顶层,将会调用该钩子。
  3. 可以通过vueApp.config.errorHandler提供一个全局处理程序。它将接收所有的Vue错误,即使这些错误已经被处理。
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
    // 处理错误,例如上报到一个服务
  }

  // 也可以这样
  nuxtApp.hook('vue:error', (error, instance, info) => {
    // 处理错误,例如上报到一个服务
  })
})

Nitor服务器生命周期

目前无法为这些错误定义一个服务器端处理程序,但可以渲染一个错误页面

启动错误

如果启动Nuxt应用时出现任何错误,Nuxt将会调用app:error钩子。

  • 运行Nuxt插件
  • 处理app.createdapp:beforeMount钩子
  • 将你的Vue应用渲染为HTML(在SSR期间)
  • 挂载应用程序(在客户端),不过应该使用onErrorCapturedvue:error来处理这种情况
  • 处理app:mounted钩子

JS chunk错误

由于网络连接故障或新部署(使旧的散列JS chunk URL失效),你可能会遇到块加载错误。Nuxt提供了内置支持来处理块加载错误,当在路由导航过程中某个块加载错误,它会执行硬刷新。

你可以通过将experimental.emitRouteChunkError设置为false(完全禁用对这些错误的处理)或manual(手动处理错误)来更改此行为。如果你想手动处理块加载错误,

原生实现可通过监听router.error()实现。

错误页面

注意

当Nuxt遇到致命错误(服务器上的任何未处理的错误,或客户端上使用fatal: true创建的错误),它将要么渲染一个JSON响应(如果使用Accept: application/json头部请求),要么触发一个全屏错误页面。

在以下情况下,可能会在服务器生命周期中发生错误:

  • 处理Nuxt插件
  • 将你的Vue应用程序渲染为HTML
  • 服务器API路由抛出错误

它也可能在客户端上发生以下情况:

  • 处理Nuxt插件
  • 在挂载应用程序之前(app:beforeMount钩子)
  • 如果错误没有使用onErrorCapturedvue:error钩子进行处理,则在挂载应用程序时
  • 在浏览器中初始化和挂载Vue应用程序(app:mounted

通过在应用程序原目录中添加~/error.vue,可以自定义默认错误页面,与app.vue放在一起。

尽管它被称为“错误页面”,但它不是一个路由,不应该放在~/pages目录中。出于同样的原因,你不应该在此页面中使用definePageMeta

错误页面有一个单一的prop-error,其中包含一个待处理的错误。

error对象提供以下字段:

{
  url: string
  statusCode: number
  statusMessage: string
  message: string
  description: string
  data: any
}

如果你有一个带有自定义字段的错误,它们将会丢失;你应该将它们分配给data

throw createError({
  status: 404,
  statusMessage: 'page Not Found',
  data: {
    myCustomField: true
  }
})

对于自定义错误,我们强烈建议使用onErrorCaptured组合式,它可以在页面/组件设置函数中调用,或者使用vue:error运行时的Nuxt钩子,可以在Nuxt插件中进行设置。

export default defineNuxtPlugin(nuxtApp => {
  nuxtApp.hook('vue:error', (err) => {
    //
  })
})

当你准备移除错误页面时,你可以调用clearError辅助函数,它接受一个可选的路径进行重定向(例如,如果你想导航到一个“安全”的页面)。

错误工具

useError

function useError (): Ref<Error | (url, statusCode, statusMessage, message, description, data }>

从函数将返回正在处理的全局Nuxt错误。

createError

function createError(err: {cause, data, message, name, stack, statusCode, statusMessage, fatal}) : Error

使用附加元数据创建错误对象。它可以在你的应用程序中的vue和服务器部分中使用,并且意在被抛出。

如果你抛出使用createError创建的错误:

  • 在服务器端,它将触发一个全屏错误页面,你可以使用clearError清除该错误。
  • 在客户端,它将抛出一个非致命错误供你处理。如果你需要触发一个全屏错误页面,你可以通过设置fatal: true来实现。
<script setup lang="ts">
  const route = useRoute();
  const {data} = await useFetch(`/api/movies/${route.params.slug}`);
  if (!data.value) {
    throw createError({
      statusCode: 404,
      statusMessage: 'page Not Found'
    })
  }
</script>

showError

function showError (err: string | Error | {statusCode, statusMessage}): Error

你可以在客户端的任何地方调用此函数,或者(在服务器端)直接在中间件、插件或setup()函数中使用。它将触发一个全屏错误页面,你可以使用clearError清除该错误。

建议使用throw createError()

clearError

function clearError (options?: {redirect?: string}) : Promise<void>

此函数将清除当前正在处理的Nuxt错误。它还接受一个可选的路径进行重定向(例如,如果你想导航到一个“安全”的页面)。

在组件中渲染错误

Nuxt还提供了一个<NuxtErrorBoundary>组件,运行你在应用程序中处理客户端错误,而无需用错误页面替换整个站点。

该组件负责处理其默认插槽中发生的错误。在客户端上,它将阻止错误冒泡到顶层,并渲染#error插槽。

#error插槽将接收error作为prop。(如果你设置error=null,它将触发重新渲染默认插槽;你需要确保错误已经完全解决,否则错误插槽将被再次渲染。

<template>
  <NuxtErrorBoundary @error="someErrorLogger">
    <template #error="{error, clearError}">
      {{error}}
      <button @click="clearError">
        这将清除错误
      </button>
    </template>
  </NuxtErrorBoundary>
</template>

服务器

从单一代码库中构建全栈应用程序,从数据库获取数据,创建API,甚至生成静态的服务器端内容,如站点地图或RSS订阅源。

Nuxt的服务器框架允许你构建全栈应用程序。例如,你可以从数据库或其他服务器获取内容,创建API,甚至生成静态的服务器端内容,如站点地图或RSS订阅源 - 一切都可以从单一代码库中完成。

有Nitro驱动

Nuxt的服务器使用的是Nitro。Nitro最初所为Nuxt创建的,但现在是UnJS的一部分,也被其他创建使用,甚至可以单独使用。

使用Nitro基于Nuxt超能力:

  • 对应用程序的服务器端部分拥有完全控制权
  • 对任何提供者上进行通用部署(许多无需配置)
  • 混合渲染

对应用程序的服务器端部分拥有完全控制权

使用Nitro,你可以轻松管理Nuxt应用程序的服务器部分,从API端点到中间件,

端点和中间件可以像这样定义:

export default defineEventHandler(async event => {
  // ...在这里可以做任何你想做的事情
})

你可以直接返回textjsonhtml甚至stream

Nitro默认支持热模块替换自动导入,就像Nuxt应用程序的其他部分一样。

通用部署

Nitro提供了在任何地方部署Nuxt应用程序的能力,从裸机服务器到边缘网络,启动时间仅为几毫秒。

混合渲染

你的Nuxt应用程序是否需要静态和动态页面?Nitro为你提供支持!

Nitro有一个强大的功能叫做routeRules,它允许你定义一组规则来自定义Nuxt应用程序的每个路由的渲染方式(以及更多)。

export default defineNuxtConfig({
  routeRules: {
    // 为了SEO目的,在构建时生成
    '/': {prerender: true},
    // 缓存1小时
    '/api/*': {cache: {maxAge: 60 * 60}},
    // 重定向以避免404
    '/old-page': {
      redirect: {to: {'/new-page', statusCOde: 302}}
    }
  }
})

此外,还有一些路由规则(例如ssr和experimentalNoScripts)不是Nuxt特定的,用于改变渲染页面为HTML时的行为。

一些路由规则(redirect和prerender)也会影响客户端行为。

Nitro用于构建具有服务器端渲染和预渲染功能的应用程序。

图层

Nuxt提供了一个强大的系统,允许你扩展默认的文件、配置等。

Nuxt3的核心功能之一就是图层和扩展支持。你可以扩展默认的Nuxt应用程序,以便重组组件、工具和配置。图层结构几乎与标准的Nuxt应用程序相同,这使得它们易于编写和维护。

使用场景

  • 使用nuxt.configapp.config在项目之间共享可重用的配置预设
  • 使用components/目录创建组件库
  • 使用composables/utils/目录创建实用工具和可组合库
  • 创建Nuxt模块预设
  • 在项目之间共享标准设置
  • 创建Nuxt主题

使用方法

你可以通过在nuxt.config.ts文件中添加extends属性来扩展一个图层。

export default defineNuxtConfig({
  extends: [
    // 从本地图层扩展
    '../base',
    // 从已安装的npm包扩展
    '@my-themes/awesome',
    // 从git仓库扩展
    'github:my-themes/awwsome#v1'
  ]
})

部署

Nuxt应用可以部署在Node.js服务器上,预渲染以进行静态托管,或部署到无服务器或边缘(CDN)环境中。

Node.js服务器

使用Nitro中的Node.js服务器预设可以在任何Node托管上部署。

  • 默认输出格式,如果没有指定或自动检测
  • 仅加载渲染请求所需的块,以获得最佳的冷启动时间
  • 适用于将Nuxt应用部署到任何Node.js托管上

入口点

使用Node服务器预设运行Nuxt build后,将得到一个启动准备就绪的Node服务器的入口点。

node .output/server/index.mjs

这将启动你的生产Nuxt服务器,默认监听端口为3000。

它会遵循以下运行时环节变量:

  • NITRO_PORTPORT(默认为3000
  • NITRO_HOSTHOST(默认为0.0.0.0
  • NITRO_SSL_CERTBITRO_SSL_KEY-如果两者都存在,则会以HTTPS模式启动服务器。在绝大多数情况下,除了测试之外,不应该使用这个选项,Nitro服务器应该在像nginx或Cloudflare这样的反向代理后面运行,由它们终止SSL。

PM2

要使用pm2,请使用ecosystem.config.js文件:

module.exports = {
  apps: [
    {
      name: 'NuxtAppName',
      port: '3000',
      exec_mode: 'cluster',
      instances: 'max',
      script: './.output/server/index.mjs'
    }
  ]
}

集群模式

你可以使用NITRO_PRESET=node_cluster来利用Node.js的cluster模块来提高多进程性能。

默认情况下,工作负载会使用轮询策略分发给工作进程。

静态托管

有两种方式可以将Nuxt应用部署到任何静态托管服务上:

  • 使用ssr:true进行静态站点生成(SSG),在构建时预渲染应用程序的路由(这是运行nuxi generate时的默认行为)。它还会生成/200.html/400.html单页面应用回退页面,这些页面可以在客户端上渲染动态路由或404错误(尽管您可能需要在静态主机上进行配置)
  • 或者,你可以使用ssr:false进行预渲染(静态单页面应用)。这将产生带有空的<div id="__nuxt"></div>的HTML页面,通常用于渲染Vue应用的位置。你会失去许多预渲染站点的SEO优势,因此建议使用<ClientOnly>来包装无法在服务器端渲染的站点部分(如果有的话)。

基于爬虫的预渲染

使用nuxi generate命令使用Nitro爬虫构建和预渲染应用程序。这个命令类似于将nuxt build运行时的nitro.static选项设置为true,或者运行nuxt build --prerender

npx nuxi generate

现在可以将.output/public目录部署到任何静态托管服务上,或者使用npx serve .output/public在本地预览。

Nitro爬虫的工作原理:

  1. 加载应用程序的根路由(/)的HTML、~/pages目录中的任何非动态页面以及nitro.prerender.routes数组中的其他路由。
  2. 将HTML和payload.json保存到~/.output/public/目录,以进行静态服务。
  3. 查找HTML中的所有锚点标签(<a href="")以导航到其他路由。
  4. 对每个找到的锚点标签重复步骤1-3,直到没有更多的锚点标签可以爬取。

这一点很重要,因为没有连接到可发现页面的页面无法自动预渲染。

选择性预渲染

你可以手动指定Nitro将在构建期间获取和预渲染的路由,或者忽略不想预渲染的路由,比如在nuxt.config文件中的/dynamic路由:

defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ['/user/1', 'user/2'],
      ignore: ['/dynamic']
    }
  }
})

你可以将此与crawLinks选项结合使用,以预渲染一组爬虫无法发现的路由,比如你的/sitemap.xml/robots.txt

defineNuxtConfig({
  nitro: {
    prerender: {
      crawlLinks: true,
      routes: ['/sitemap.xml', '/robots.txt']
    }
  }
})

nitro.prerender设置为true类似于将nitro.prerender.crawlLinks设置为true。

仅客户端渲染

如果你不想预渲染你的路由,另一种使用静态托管的方式是在nuxt.config文件中将ssr属性设置为false。nuxi generate命令将输出一个.output/public/index.html入口点和类似经典客户端端Vue.js应用程序的JavaScript捆绑包。

defineNuxtConfig({
  ssr: false
})

预设

除了Node.js服务器和静态托管服务之外,Nuxt3 项目还可以使用几个经过充分测试的预设进行部署,并进行最少的配置。

你可以在nuxt.config文件中明确设置所需的预设:

export default {
  nitro: {
    preset: 'node-server'
  }
}

或者在运行nuxt build时使用NITRO_PRESET环境变量:

NITRO_PRESET=node-server nuxt build

CDN代理

在大多数情况下,Nuxt可以与不由Nuxt自身生成或创建的第三方内容一起工作,但有时这样的内容可能会引起问题,尤其是Cloudflare 的“Minification and Security Options”。

因此,你应该确保在 Cloudflare 中取消选中 / 禁用以下选项。否则,不必要的重新渲染或水合错误可能会影响你的生产应用程序。

  1. 速度 > 优化 > 自动缩小:取消选中 JavaScript、CSS 和 HTML
  2. 速度 > 优化 > 禁用 "Rocket Loader™"
  3. 速度 > 优化 > 禁用 "Mirage"
  4. Scrape Shield > 禁用 "Email Address Obfuscation"
  5. Scrape Shield > 禁用 "Server-side Excludes"

通过这些设置,你可以确保 Cloudflare 不会向你的 Nuxt 应用程序注入可能引起不必要副作用的脚本。

测试

在Nuxt3中,我们有一个重写版本的@nuxt/test-utils。我们支持Vitest和Jest作为测试运行器。

安装

pnpm add --dev @nuxt/test-utils vitest

设置

在每个使用@nuxt/test-utils辅助方法的describe块中,你需要在开始之前设置测试上下文。

import {describe, test} from 'vitest';
import {setup, $fetch} from '@nuxt/test-utils';

describe('我的测试', async () => {
  await setup({
    // 测试上下文选项
  })
  
  test('我的测试', () => {
    // ...
  })
})

在幕后,setupbeforeAllbeforeEachafterEachafterAll中执行一系列任务,以正确设置Nuxt测试环境。

请参阅下面的选项以了解setup方法。

Nuxt配置

  • rootDir:Nuxt应用所在目录的路径。
    • 类型:string
    • 默认值:'.'
  • configFile:配置文件的名称
    • 类型:string
    • 默认值:'nuxt.config'

时间设置

  • setupTime:允许setupTest完成其工作的时间(可能包括构建或生成Nuxt应用程序的文件,取决于传递的选项)。
    • 类型:number
    • 默认值:60000

功能

  • server:是否启动一个服务器来响应测试套件中的请求。
    • 类型:boolean
    • 默认值: true
  • port:如果提供,将启动的测试服务器端口设置为该值。
    • 类型:number | undefined
    • 默认值: undefined
  • build:是否运行单独的构建步骤。
    • 类型:boolean
    • 默认值: true(如果browserserver被禁用,则为false)
  • browser:在幕后,Nuxt测试工具使用playwright进行浏览器测试。如果设置了此选项,将会启动一个浏览器,并且可以在后续的测试套件中进行控制。
    • 类型:boolean
    • 默认值: false
  • browserOptions
    • 类型:具有以下属性的object
      • type:要启动的浏览器类型,可以是chromiumfirefoxwebkit
      • launch:在启动浏览器时将传递给playwright的选项对象。
  • runner:指定测试套件的运行期。目前推荐使用Vitest。
    • 类型:'vitest' | 'jest'
    • 默认值: vitest

API

$fetch(url)

获取服务器渲染页面的HTML。

import {$fetch} from '@nuxt/test-utils';
const html = await $fetch('/');

fetch(url)

获取服务器渲染页面的响应。

import {fetch} from '@nuxt/test-utils';
const res = await fetch('/');
const {body, headers} = res;

url(path)

获取给定页面的完整URL(包括测试服务器运行的端口)。

import {url} from '@nuxt/test-utils';
const pageUrl = url('/page');
上次编辑于:
贡献者: haozg, haozg