一、如何在 Nuxt3 中更好的处理页面布局
可以通过 <NuxtLayout>
和 <NuxtPage>
来实现不同布局
app.vue
vue
<template>
<NuxtLayout> <!-- 布局容器 默认加载 layouts/default.vue -->
<NuxtPage /> <!-- 页面路由出口 -->
</NuxtLayout>
</template>
<script setup lang="ts">
</script>
布局一:layouts/default.vue
vue
<template>
<div>
<header>常规导航栏</header>
<main>
<slot /> <!-- 页面内容插入位置 -->
</main>
<footer>网站底部信息</footer>
</div>
</template>
布局二:layouts/login.vue
vue
<template>
<div class="login-layout">
<div class="auth-bg">登录 header</div>
<slot /> <!-- 登录表单将插入这里 -->
<div>登录 footer</div>
</div>
</template>
首页 | 登录页 |
---|---|
![]() |
![]() |
路由一:首页
javascript
<script setup>
// 不指定 layout 则默认使用 default.vue
</script>
<template>
<div>首页内容</div>
</template>
路由二:登录页
javascript
<script setup>
definePageMeta({
layout: 'login' // 指定使用 layouts/login.vue 布局
})
</script>
<template>
<div class="login-form">
<!-- 登录表单内容 -->
表单内容
</div>
</template>
二、如何在 Nuxt3 中使用 Pina
安装 pina
json
npm install @pinia/nuxt
注册,修改 nuxt.config.ts
typescript
export default defineNuxtConfig({
modules: ['@pina/nuxt']
})
store/index.ts
typescript
export const useAppStore = defineStore('app', {
state: () => {
return {
theme: 'dark',
userData: {
name: 'Jack',
age: 18
}
}
},
getters: {
name: (state) => state.userData.name
},
actions: {
initHead() {
useHead({
title: '四叶草'
})
}
}
})
在页面上导入 store ,则可以使用
typescript
import { useAppStore } from '@/store/index'
const appStore = useAppStore()
除了上面那种方式外,也可以全局挂在到 nuxtApp 上。
在项目根目录上,创建 plugins
目录(注意,文件名需要一致)
typescript
// store.ts (文件名随便)
import { useAppStore } from '@/store'
export default defineNuxtPlugin((nuxtApp) => {
const appStore = useAppStore()
// 挂载到 nuxtApp
nuxtApp.provide('appStore', appStore)
})
使用
typescript
import { useNuxtApp } from '#app'
const { $appStore } = useNuxtApp();

pina 在 nuxt 中的前期配置就是这些了,pina 具体使用可以查看我另外一遍文章
三、如何在 Nuxt3 中使用第三方组件库
这里我使用的是 TDesgin 组件库。
安装 TDesign
json
npm install tdesign-vue-next @tdesign-vue-next/nuxt
注册,修改 nuxt.config.ts
typescript
export default defineNuxtConfig({
modules: ['@tdesign-vue-next/nuxt']
})
使用
javascript
<template>
<t-button>Hello Nuxt + TDesgin</t-button>
</template>

- Message 的使用:也可以直接挂载到 nuxtApp 上
typescript
import { MessagePlugin } from "tdesign-vue-next";
export default defineNuxtPlugin((nuxtApp) => {
const appStore = useAppStore();
nuxtApp.provide("message", MessagePlugin);
});
javascript
<template>
<t-button @click="showMessage">显示提示信息</t-button>
</template>
<script setup>
import { useNuxtApp } from '#app';
const { $message } = useNuxtApp();
const showMessage = () => {
$messsage.info({ content: "这里是 Message 信息" });
}
</script>

- 图标库的使用:也可以进行全局注册使用
typescript
import * as Icons from "tdesign-icons-vue-next";
export default defineNuxtPlugin((nuxtApp) => {
const icons = Icons;
for (const i in icons) {
// 全局注册
nuxtApp.vueApp.component(i, icons[i]);
}
});
vue
<template>
<div>
<logo-adobe-illustrate-icon />
</div>
</template>
<script setup lang="ts">
// import { LogoAdobeIllustrateIcon } from 'tdesign-icons-vue-next' // 局部使用
</script>
四、如何在 Nuxt3 中实现自定义指令的注册
如:权限指令
typescript
// authBtn.ts
import store from '@/store'
function checkPermission (el, binding) {
// 获取绑定的值,此处为权限
const { value } = binding
// 获取所有的功能指令
const authId = store.getters.userInfo.authId
let btnAuth = []
if (authId === '1') { // 管理员权限
btnAuth = ['export', 'delete', 'change']
}
// 当传入的指令集为数组时
if (value && value instanceof Array) {
// 匹配对应的指令
const hasPermission = btnAuth.some(point => {
return value.includes(point)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
// eslint-disabled-next-line
throw new Error('v-permission value is ["admin","editor"]')
}
}
export default {
// 在绑定元素的父组件被挂载后调用
mounted (el, binding) {
checkPermission(el, binding)
},
// 在包含组件的 VNode 及其子组件的 VNode 更新后调用
update (el, binding) {
checkPermission(el, binding)
}
}
typescript
// plugins/directives.ts
import authBtn from '@/directives/authBtn'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.directive('authBtn', authBtn);
})
五、如何在 Nuxt3 中使用 Axios
在 Nuxt3 中也提供了 useFetch
,$Fetch
,useAsyncData
来发送请求。(后面找个时间研究一下这三个函数)
对于使用 axios 习惯的开发者,也可以在 nuxt3 中配置使用 axios
安装 Axios
json
npm install axios
定义 Axios
typescript
// request.ts
import axios from 'axios'
import { useNuxtApp } from '#app'
const axiosInstance = axios.create({
baseURL: '/api',
});
axiosInstance.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
axiosInstance.interceptors.request.use((config) => {
let authorization = {};
const authStr = localStorage.getItem('AUTHORIZATION'); // localStorage 也可以挂在到 nuxtApp 上
if (authStr) {
authorization = JSON.parse(authStr)
}
if (authorization && authorization['access_token']) {
config.headers['Authorization'] = `${authorization['token_type']} ${authorization['access_token']}`;
}
return config;
}, (error) => {
return Promise.reject(error);
});
axiosInstance.interceptors.response.use((response) => {
return response.data;
}, (error) => {
if (process.client) { // 在客户端进行处理,不做判断会报错
const { $message, $appStore } = useNuxtApp();
if (error.response.status === 401) {
if (error.response.data) {
let data = error.response.data;
let codes = ['disabled_credentials', 'invalid_credentials', 'invalid_verify_code'];
if (!codes.includes(data.code)) {
$appStore.logout();
$appStore.isLogin = false;
} else if (data.code !== 'invalid_token_expired') {
$message.error(error.response.data.message);
}
}
}
}
return Promise.reject(error);
});
export default axiosInstance;
通过 provide
挂载到 nuxtApp
typescript
import axios from '@/utils/request.ts'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.provide("axios", axios);
})
页面上使用
typescript
import { useNuxtApp } from '#app'
const { $axios } = useNuxtApp()
六、如何在 Nuxt3 中做好请求路由的转发
nuxt.config.ts
typescript
import Config from "./config";
export default defineNuxtConfig({
nitro: {
devProxy: {
"/api": {
target: Config.baseUrl,
changeOrigin: true,
prependPath: true,
},
},
},
})
可以通过 process 对象,来配置请求地址是开发环境地址,还是生成环境地址

七、如何在 Nuxt3 中做好路由拦截
Nuxt3 有自己的一套路由机制,会自动注册 pages 目录下的文件,并通过文件名规则进行路由注册,这里不做过多介绍。比较简单。
路由拦截主要是用在权限校验,在 vue项目中可以通过路由钩子进行拦截。
在 nuxt 也提供了 defineNuxtRouteMiddleware
来定义拦截器。
在项目根路径创建 middleware
目录,创建 router.global.ts
文件(global) 代表全局拦截
typescript
// 全局路由守卫
import Config from '@/config';
export default defineNuxtRouteMiddleware((to, from) => {
if (process.client) { // 需要在客户端进行拦截,否则会报错
let authorization = {};
const authStr = localStorage.getItem('AUTHORIZATION');
if (authStr) {
authorization = JSON.parse(authStr);
}
if (authorization && authorization['access_token']) {
// 校验登录是否过期
const expires_in = authorization['expires_in'];
const login_time = Number(authorization['login_time']);
const now = Date.now();
if (now > login_time + expires_in * 1000) {
clientStore.logout();
window.location.href = '/';
clientStore.isLogin = false;
}
}
if (!Config.router.includes(to.name as string)) {
// 不存在路由
// window.location.href = '/';
// navigateTo('/') 重定向
// 抛出错误信息,也可以不抛出, Nuxt 会自动抛出
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' })
}
}
});
去掉 .global
,则非全局中间件,如果需要在某个页面中使用该中间件,则可以通过 definePageMeta
进行配置
typescript
definePageMeta(
{ middleware: 'auth' } // middleware/auth.ts
)
除了使用 definePageMeta
配置路由拦截外,还能使用 addRouteMiddleware
来进行配置
typescript
// plugins/middleware.ts
export default defineNuxtPlugin(() => {
// 全局中间件:所有路由都会执行
addRouteMiddleware('global-auth', () => {
// 逻辑
}, { global: true })
// 命名中间件:需要在页面中显式使用 通过 definePageMeta
addRouteMiddleware('named-auth', () => {
// 逻辑
})
})
addRouteMiddleware
和 definePageMeta
的区别
-
使用位置不同 :
- addRouteMiddleware :在插件(plugins)中使用,用于动态注册中间件
- definePageMeta :在页面组件中使用,用于静态声明中间件
-
作用范围不同 :
- addRouteMiddleware :可以注册全局中间件( global: true )或命名中间件
- definePageMeta :只能为当前页面配置中间件
-
使用场景 :
- addRouteMiddleware :适合需要动态注册或全局使用的中间件,比如全局的身份验证
- definePageMeta :适合特定页面的中间件需求,比如某个管理页面的权限检查
-
执行顺序 :
- 全局中间件最先执行
- 然后是布局中间件(如果有)
- 最后是页面中间件
-
灵活性 :
- addRouteMiddleware 更灵活,可以在运行时动态添加中间件
- definePageMeta 是静态配置,在编译时就确定了
八、如何在 Nuxt3 中 更友好的处理页面报错
比如:访问不存在的页面

体验不是很好,我们可以在 项目根目录下创建 error.vue
,来自定义错误页面
vue
<template>
<div>错误页面</div>
</template>
<script></script>

九、如何在 Nuxt3 中做 SEO
Nuxt3 中 提供了 useHead
首先我们需要在服务端,拿到配置好的 SEO 参数。通过 useHead
渲染后,返回给到客户端。
可以通过 useAsyncData
实现在服务端发送请求。
vue
<!-- layout.vue -->
<template></template>
<script setup>
const res = await useAsyncData('siteData', () => {
return $fetch(`${url}`, {
method: 'post',
headers: {
// ...
},
body: {
// ... 参数
}
})
});
</script>
此时通过 useAsyncData
发送请求。 在页面显示时,我们是看不到发送请求的。在使用 useHead
初始化 SEO
typescript
useHead({
title: `${res.data.title}`,
meta: [
{ name: 'description', content: `${res.data.desc}` },
{ name: 'keywords', content: `${res.data.keywords}` },
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: `${res.data.favicon}` },
]
})
举例:
typescript
useHead({
title: "我是标题",
meta: [
{ name: "description", content: "我是 description" },
{ name: "keywords", content: "我是 keywords" },
],
link: [
{
rel: "icon",
type: "image/x-icon",
href: "https://static.tdesign.tencent.com/vue/apple-touch-icon.png",
},
],
});


上述是全局设置,单页面也是可以进行设置的,可以通过 definePageMeta
进行设置。
十、如何在 Nuxt3 中全局加载资源
在 nuxt.config.ts 中进行配置
typescript
export default defineNuxtConfig({
css: ["~/assets/index.css"], // 加载 css
vite: {
css: {
preprocessorOptions: {
less: {
additionalData: '@import "@/assets/mixin.less";' // 加载 less
},
scss: {
additionalData: '@use "~/assets/func.scss" as *;', // 加载 scss
},
},
},
},
app: {
head: {
script: [
{
src: `https://static.tdesign.tencent.com/vue/assets/index-bba43f31.js`, // 加载 javascript
},
],
},
},
})