白筱汐

想都是问题,做都是答案

0%

nuxtjs ssr入门指南

nuxt简介

Nuxt 是一个全栈框架,可以实现 SSG、SSR(服务端渲染),官网地址。这篇文章主要记录使用 Nuxt 实现 SSR 的过程。

创建项目

安装

1
pnpm dlx nuxi@latest init <project-name>

启动

1
pnpm dev

页面布局

在项目的根目录新建 layouts 目录,然后在里面新建 defalut.vue 文件。

1
2
3
4
5
6
7
8

<template>
<div>
<header>header</header>
<slot/>
<footer class="text-red text-center">footer</footer>
</div>
</template>

假设当前页面需要一个固定的头部和尾部内容,中间的内容根据路由动态展示。

slot 插槽表示页面内部要展示的内容,一般展示的就是路由渲染的部分。NuxtPage 组件可以展示路由匹配的组件,你可以把它当中
vue-router 中的 RouterView。

1
2
3
4
5
6

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

路由

Nuxt 使用基于文件的路由系统,在项目的根目录创建 pages 目录以及对应的文件。

1
2
3
4
5
-| pages/
---| parent/
------| child.vue
---| parent.vue
---| index.vue

生成的路由如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[
{
path: '/',
component: '~/pages/index.vue',
name: 'index'
},
{
path: '/parent',
component: '~/pages/parent.vue',
name: 'parent',
children: [
{
path: 'child',
component: '~/pages/parent/child.vue',
name: 'parent-child'
}
]
}
]

重启项目,现在访问 http://localhost:3000/parent/child 就能看到 child 组件里面的内容了。

实现页面跳转可以使用 NuxtLink 组件。

1
2

<nuxt-link to="/parent">parent</nuxt-link>

使用编程式导航,请参考 vue-router

1
2
3
const route = useRoute();
route.push('/parent')
route.back()

动态路由:

在 pages 目录下面新增一个 user-[id].vue 文件,里面的内容如下

1
2
3
4

<template>
<div>user {{$route.params.id}}</div>
</template>

跳转方式:

1
<nuxt-link to="/user-1">user</nuxt-link>

匹配所有页面(404处理):

在 pages 目录下面新增一个 […slug].vue 文件,里面的内容如下:

1
2
3
<template>
<div>404</div>
</template>

集成 unocss(tailwindcss)

安装依赖

1
pnpm add -D @unocss/nuxt @unocss/preset-wind

打开 nuxt.config.ts 文件,新增配置

1
2
3
4
5
6
7
8
9
export default defineNuxtConfig({
devtools: {enabled: true},
modules: [
'@unocss/nuxt',
],
css: [
'@unocss/reset/tailwind.css' // 使用 tailwind 重置基础样式
]
})

为了使 unocss 兼容 tailwindcss 需要新增 unocss.config.ts

1
2
3
4
5
6
7
8
// uno.config.ts
import {defineConfig, presetWind} from 'unocss'

export default defineConfig({
presets: [
presetWind(),
],
})

获取数据

获取数据可以使用 $fetch() 方法。Nuxt 内部集成了ofetch,$fetch 只是它的别名,具体使用方法你可以参考
github 。

1
2
3
4
5
6
async function addTodo() {
const todo = await $fetch('/api/todos', {
method: 'POST',
body: {}
})
}

使用 useFetch() 方法可以把异步返回的数据转化成响应式变量直接给客户端使用。

1
2
3
4
5
6
7
8

<script setup lang="ts">
const {data: count} = await useFetch('/api/count')
</script>

<template>
<p>Page visits: {{ count }}</p>
</template>

内置状态处理

使用内部 useState() 方法可以设置状态,第一个参数 key 是用来缓存的标识,第二个参数可以是一个返回初始值的函数。

useState() 和 ref() 的选择看起来和 ref() 并没有什么两样,但是实际上是有差别的:

  • useState(key, init) 是有缓存性的,如果 key 不变,init 只做初始化,则多次调用同一个 useState,结果是一样的;

  • 服务端友好性,得益于缓存性,即便 init 返回值是不稳定的,也能保证前端注水时前后端状态的一致性,比如初始值是随机值的情况。

1
2
3
4
5
6
7
8
<template>
<div>{{ counter }}</div>
<button @click="counter++">+1</button>
</template>

<script setup lang="ts">
const counter = useState('counter', () => 0)
</script>

集成Pinia以及状态持久化

安装

1
2
pnpm i @pinia/nuxt 
pnpm i -D @pinia-plugin-persistedstate/nuxt

修改 nuxt.config.ts 新增模块 ‘@pinia/nuxt’

1
2
3
4
5
6
7
export default defineNuxtConfig({
// ... 省略部分代码
modules: [
'@pinia/nuxt',
'@pinia-plugin-persistedstate/nuxt',
],
})

在项目根目录下新增 store/useCounter.ts

1
2
3
4
5
6
7
8
9
10
11
export const useCounter = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
},
persist: true // 设置状态持久化
})

异常处理

在项目根目录下新增 plugins/error.ts,Nuxt会自动加载 plugins 里面的插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default defineNuxtPlugin((nuxt) => {
// vue 错误
nuxt.vueApp.config.errorHandler = (err, vm, info) => {
console.log(err, vm, info)
}

nuxt.hook('vue:error', (err) => {
console.log('vue:error:', err);
})

// nuxt 启动出错
nuxt.hook('app:error', (err) => {
console.log('app:error:', err);
})
})

项目配置

设置全局配置,在根目录下面新增 app.config.ts 文件。

1
2
3
4
5
6
7
8
9
export default defineAppConfig({
title: 'hello nuxt3',
theme: {
dark: true,
colors: {
primary: '#ff0000',
}
}
})

使用方式

1
2
3
const appConfig = useAppConfig()

console.log(appConfig)

设置运行时配置

1
2
3
4
5
6
7
8
9
10
export default defineNuxtConfig({
// ... 省略部分代码
runtimeConfig: {
apiSecret: '', // 只能用于服务端的key
// 公共配置
public: {
apiBase: '/api'
}
}
})

使用方式

1
2
3
4
5
6
7
8
const runtimeConfig = useRuntimeConfig()
// 服务端环境
if (process.server) {
console.log(runtimeConfig.apiSecret)
}

console.log(runtimeConfig.public.apiBase)

设置环境变量

在本地新建 .env 文件,内容如下:

1
2
3
4
BASE_URL=http://localhost:3000
NUXT_APP_BASE_URL='/'
NUXT_API_SECRET=api_secret_token
NUXT_PUBLIC_API_SECRET=/api

在本地新建 .env.local 文件,内容如下:

1
2
3
4
BASE_URL=http://localhost:8080
NUXT_APP_BASE_URL='/'
NUXT_API_SECRET=api_secret_token_prod
NUXT_PUBLIC_API_BASE=/prod

默认本地的启动命令会运行:nuxt dev,读取 .env 的环境变量配置。

如果读到相应的key它会覆盖之前 runtimeConfig 里面的内容。

查看运行时的环境变量:

1
2
const baseUrl = process.env.BASE_URL
console.log(baseUrl)

建议按照官网的约定去配置环境变量,NUXT_API_SECRET 对应 apiSecret,NUXT_PUBLIC_API_BASE 对应 public 里面的 apiBase。

1
2
const runtimeConfig = useRuntimeConfig()
console.log(runtimeConfig.public.apiBase)

配置一个启动命令,来设置不同环境的域名,例如:

在 package.json 里面新增一个一条 script,代表你本地使用测试环境或者生产环境的域名,并且配置端口号为 8080。

1
2
3
4
5
6
{
"scripts": {
"dev": "nuxt dev",
"dev:local": "nuxt dev --port=8080 --dotenv .env.local"
}
}

配置Meta

Nuxt 提供 useSeoMeta、useHead 方法我们可以给每个页面单独设置头部信息。

1
2
3
4
5
6
7

<script>
// 单独的页面设置标题
useHead({
title: 'Config',
})
</script>

也可以使用 Nuxt 提供的 <Title>, <Base>, <NoScript>, <Style>, <Meta>, <Link>, <Body>, <Html>, <Head> 组件来设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup lang="ts">
import { ref } from 'vue'
const title = ref('Hello World')
</script>

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

<h1>{{ title }}</h1>
</div>
</template>

通过配置文件全局设置头部信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default defineNuxtConfig({
app: {
head: {
charset: 'utf-8', // 快捷方式
viewport: 'width=device-width, initial-scale=1', // 快捷方式
title: '我的网站标题',
meta: [
{name: 'description', content: '我的网站描述'},
{name: 'keywords', content: 'vue,react,node,go'},
{name: 'charset', content: 'utf-8'},
],
"link": [],
"style": [],
"script": []
}
},
})

路由中间件

匿名中间件,具体页面执行:

1
2
3
4
5
6
definePageMeta({
middleware(to, from) {
console.log(to, from);
console.log('匿名中间件,具体页面执行')
}
})

具名中间件,在某些页面使用:

在项目根目录新建 middleware/a.ts

1
2
3
4
export default defineNuxtRouteMiddleware((to, from) => {
console.log(to, from)
console.log('a route middle')
})

在具体页面使用,可以使用多个路由中间件

1
2
3
definePageMeta({
middleware: ['a']
})

全局路由中间件:

在 middleware 目录新建 auth.global.ts,文件名称需要以 global.ts 结尾。

1
2
3
4
export default defineNuxtRouteMiddleware((to, from) => {
console.log(to, from)
console.log('全局路由中间件')
})

实现一个导航守卫的功能:

1
2
3
4
5
6
7
8
9
10
11
12
export default defineNuxtRouteMiddleware((to, from) => {
// 假设用户未登录
const lsLogin = false;

// 终止导航
// return abortNavigation()

if (!lsLogin && to.path == '/config') {
return navigateTo('/login')
}

})

打包和预览

打包方式:

  • SSR: nuxt build
  • SPA: ssr:false + nuxt generate
  • SSG: nuxt generate
  • 预览: nuxt preview