nuxt简介
Nuxt 是一个全栈框架,可以实现 SSG、SSR(服务端渲染),官网地址。这篇文章主要记录使用 Nuxt 实现 SSR 的过程。
创建项目
安装
1
| pnpm dlx nuxi@latest init <project-name>
|
启动
页面布局
在项目的根目录新建 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' ] })
|
为了使 unocss 兼容 tailwindcss 需要新增 unocss.config.ts
1 2 3 4 5 6 7 8
| 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) => { nuxt.vueApp.config.errorHandler = (err, vm, info) => { console.log(err, vm, info) }
nuxt.hook('vue:error', (err) => { console.log('vue:error:', err); })
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: '', 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" } }
|
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;
if (!lsLogin && to.path == '/config') { return navigateTo('/login') }
})
|
打包和预览
打包方式:
- SSR: nuxt build
- SPA: ssr:false + nuxt generate
- SSG: nuxt generate
- 预览: nuxt preview