nuxt个人博客开发历程

Category:

Tags:

nuxt个人博客开发历程

关于nuxt博客开发历程, 遇到很多问题, 也解决了很多问题, 对于博客细节功能也是因为各个原因阉割了一些,但是也算完成了 nuxt 个人博客的初版。

什么是同构注水(Isomorphic JavaScript)

同构注水是一种前端开发技术,它使得在浏览器和服务器之间共享同一代码的 JavaScript 应用程序成为可能。在使用同构注水技术时,开发人员可以编写一组通用代码,这些代码可以同时运行在浏览器和服务器上,从而提供更快速的加载速度和更好的用户体验。

具体来说,同构注水技术通过在服务器上预先渲染应用程序的初始 HTML 和 CSS,并将其发送到浏览器,从而实现更快速的页面加载速度和更好的搜索引擎优化。在浏览器中,这些初始数据被用于在客户端重建应用程序的初始状态,从而提供更好的用户体验。

什么是nuxt

nuxt 是vue的SSR框架,它提供了一种直观和可扩展的方式,可以用Vue.js创建类型安全、性能和生产级的全栈web应用程序和网站。

特点:

- 开发更快
- 打包更小
- 支持 `vite`
- 支持 `vue3`
- 支持自动引入
- 支持文件路由
- 支持布局系统
- 支持多种渲染模式
- 支持 `typescript`
- 支持 `composition-api`
  1. 在composables目录下的模块的同名导出会自动被引入

  2. 在components下的组件也将被自动引入

    1. 如果你在components下嵌套文件

      text
      components - post -detail.vue

      可以通过**<PostDetail />**访问

  3. 约定式路由:在pages下的文件将被自动导入路由,包括子路由,嵌套路由,动态路由([id].vue),匹配未命中路由(...slug.vue)

    1. 路由组件:NuxtLink、NuxtPage、NuxtChild
    2. 定义路由元信息:definePageMeta({foo:"bar"})
  4. 状态共享可以通过composables实现, 也可以通过 @nuxt/pinia集成

  5. 数据获取本次选用的是useFetch封装

  6. ......

博客接口设计

针对于同构项目nuxt的后端接口路由编写也采用了约定式的方式, 在server/api下的文件默认被注册为后端匹配路由

text
-api -index.ts -> 封装useFetch请求函数 -https.ts -> 导出request实例 env区分开发生产环境 -server -api -> 约定式接口 -koa -> 请求后端服务器接口 -https.ts -> 导出request实例 env区分开发生产环境

客户端渲染方式

  1. clientOnly组件, 他可以将内部包裹的代码不在服务端水合,而是在客户端渲染,这也说明该段代码不利于seo优化
text
     <div class="comment" :class="[isCenter ? 'isCenter' : '']">        <ClientOnly>          <Comment :path="`/post/${post_id}`" />        </ClientOnly>      </div>

遇到的一些点

  1. vueuse/motion 报错

    1. 版本原因, 通过将 ^2.0.0-beta.12 改成 ^2.0.0-beta.23
  2. 由于服务端无法获取到dom,导致有一些自定义指令如v-lazy,v-typing,message会受到影响需要区分对应环境

    text
    import 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 }; ​
  3. setup通过expose暴露方法给外部

    text
    defineExpose({ blur: blur });
  4. 国际化

    1. modules: ["@nuxtjs/i18n"],i18n: { vueI18n: { legacy: false, locale: "zh", messages: { zh: { "nav": { "首页": "首页", "归档": "归档", "关于": "关于", "分类": "分类", "标签": "标签", "友链": "友链", "登录": "登录", "搜索": "搜索", "个人主页": "主页", "退出登录": "退出" },...
  5. 全局路由拦截中间件

    text
    import 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"); } }); ​
  6. seo优化

    1. 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", } ], },},

    2. 语义化标签, 跳转采用nuxtlink,不用编程式

    3. 动态title

      text
      useHead({  title: () => `分类${linkName.value ? "-" + linkName.value : ""}`, });
  7. ...