nuxt个人博客开发历程
关于nuxt博客开发历程, 遇到很多问题, 也解决了很多问题, 对于博客细节功能也是因为各个原因阉割了一些,但是也算完成了 nuxt 个人博客的初版。
什么是同构注水(Isomorphic JavaScript)
同构注水是一种前端开发技术,它使得在浏览器和服务器之间共享同一代码的 JavaScript 应用程序成为可能。在使用同构注水技术时,开发人员可以编写一组通用代码,这些代码可以同时运行在浏览器和服务器上,从而提供更快速的加载速度和更好的用户体验。
具体来说,同构注水技术通过在服务器上预先渲染应用程序的初始 HTML 和 CSS,并将其发送到浏览器,从而实现更快速的页面加载速度和更好的搜索引擎优化。在浏览器中,这些初始数据被用于在客户端重建应用程序的初始状态,从而提供更好的用户体验。
什么是nuxt
nuxt 是vue的SSR框架,它提供了一种直观和可扩展的方式,可以用Vue.js创建类型安全、性能和生产级的全栈web应用程序和网站。
特点: - 开发更快 - 打包更小 - 支持 `vite` - 支持 `vue3` - 支持自动引入 - 支持文件路由 - 支持布局系统 - 支持多种渲染模式 - 支持 `typescript` - 支持 `composition-api`
-
在composables目录下的模块的同名导出会自动被引入
-
在components下的组件也将被自动引入
-
如果你在components下嵌套文件
textcomponents - post -detail.vue可以通过**<PostDetail />**访问
-
-
约定式路由:在pages下的文件将被自动导入路由,包括子路由,嵌套路由,动态路由([id].vue),匹配未命中路由(...slug.vue)
- 路由组件:NuxtLink、NuxtPage、NuxtChild
- 定义路由元信息:definePageMeta({foo:"bar"})
-
状态共享可以通过composables实现, 也可以通过 @nuxt/pinia集成
-
数据获取本次选用的是useFetch封装
-
......
博客接口设计
针对于同构项目nuxt的后端接口路由编写也采用了约定式的方式, 在server/api下的文件默认被注册为后端匹配路由
text-api -index.ts -> 封装useFetch请求函数 -https.ts -> 导出request实例 env区分开发生产环境 -server -api -> 约定式接口 -koa -> 请求后端服务器接口 -https.ts -> 导出request实例 env区分开发生产环境
客户端渲染方式
- clientOnly组件, 他可以将内部包裹的代码不在服务端水合,而是在客户端渲染,这也说明该段代码不利于seo优化
text<div class="comment" :class="[isCenter ? 'isCenter' : '']"> <ClientOnly> <Comment :path="`/post/${post_id}`" /> </ClientOnly> </div>
遇到的一些点
-
vueuse/motion 报错
- 版本原因, 通过将 ^2.0.0-beta.12 改成 ^2.0.0-beta.23
-
由于服务端无法获取到dom,导致有一些自定义指令如v-lazy,v-typing,message会受到影响需要区分对应环境
textimport AXMessage from "./src/AXMessage.vue"; import { render, VNode, h, RendererNode, RendererElement } from "vue"; enum EMessageType { SUCCESS = "success", DANGER = "danger", INFO = "info", WARNING = "warning", } let seed = 0; let body: any = ""; if (process.client) { body = document.body; } const instance: VNode< RendererNode, RendererElement, { [key: string]: any; } >[] = []; const close = (id: number) => { // 获取该实例的index const idx = instance.findIndex((vm: any) => vm.props.id === id); // 找不到就不关 if (idx === -1) { return; } // 获取该实例 const vm = instance[idx]; const removedHeight = vm.el!.offsetHeight; // 删除实例 instance.splice(idx, 1); for (let i = idx; i < instance.length; i++) { // 直接赋值组件的 top 为减去移除组件后的高度 const pos = parseInt(instance[i].el!.style["top"]) - removedHeight - 16; // topOffset = topOffset - removedHeight - 16 同理 // 将所有dim的高度降低一个dom位 instance[i].component!.props.topOffset = pos; } }; export default function (type: EMessageType, message: string, duration = 3000) { // 据顶部距离 let topOffset = 20; // 创建message容器 const container = document.createElement("div"); // 计算当前元素距离顶部的偏移量 instance.forEach((vm: any) => { // 每有一个就加 当前高度 + 间隔空隙 topOffset += (vm.el.offsetHeight || 0) + 16; }); // 保存id const id = seed; const vm = h(AXMessage, { id, type, message, duration, topOffset, onClose() { // 离开前去除数组内dom close(id); }, onDestroy() { // 动画结束后清除dom container.remove(); }, }); // 渲染组件到container上 render(vm, container); // 添加 container 到 body body.appendChild(container); // 保存组件实例 instance.push(vm); seed++; } export { EMessageType }; -
setup通过expose暴露方法给外部
textdefineExpose({ blur: blur }); -
国际化
- modules: ["@nuxtjs/i18n"],i18n: { vueI18n: { legacy: false, locale: "zh", messages: { zh: { "nav": { "首页": "首页", "归档": "归档", "关于": "关于", "分类": "分类", "标签": "标签", "友链": "友链", "登录": "登录", "搜索": "搜索", "个人主页": "主页", "退出登录": "退出" },...
-
全局路由拦截中间件
textimport message, { EMessageType } from "@/components/message"; import { useCaChe } from "@/hooks/useCache"; const { getCache } = useCaChe("token"); const { getCache: getWalineCache } = useCaChe("WALINE_USER"); export default defineNuxtRouteMiddleware((to, from) => { if (to.path === "/mine") { if (!getCache() || !getWalineCache(true)?.display_name) { message(EMessageType.INFO, "请登录~"); return navigateTo("/login"); } } else { return true; } }); export default defineNuxtRouteMiddleware((to, from) => { if (to.fullPath === "/") { return navigateTo("/home"); } }); -
seo优化
-
app: { pageTransition: false, head: { title: "a1ex的博客", meta: [ { name: "description", content: "这是a1ex的一个技术博客", }, { name: "keywords", content: "a1ex, blog, 博客, 前端, vue", }, { name: "viewport", content: "width=device-width,initial-scale=1", }, ], style: [], link: [ // { rel: "stylesheet", href: "https://markdown-it.github.io/index.css" }, { rel: "stylesheet", href: "//at.alicdn.com/t/c/font_3531202_ch99hg9lb5.css", } ], },},
-
语义化标签, 跳转采用nuxtlink,不用编程式
-
动态title
textuseHead({ title: () => `分类${linkName.value ? "-" + linkName.value : ""}`, });
-
-
...