Next.js 构建博客之功能拓展

  1. Next.js 构建博客之资源抓取
  2. Next.js 构建博客之博客搭建
  3. Next.js 构建博客之打包 SSG
  4. Next.js 构建博客之常见问题处理

这是 Next.js 构建博客的第五篇文章,上一篇文章介绍了 Next.js 如何处理常见的问题,这一篇主要介绍给博客进行功能增强。

图片放大缩小

在详情页面会经常遇到图片,很多时候为了考虑排版只会放一个等比例缩小的图片,而不是任由图片展示初始尺寸,这个时候为了为了查看图片就需要考虑功能的增强了。

这里介绍一下怎么来进行添加,不过在添加之前需要想一下,图片放大缩小这个功能,我们需要用服务器渲染还是客户端渲染?

我的建议是客户端渲染即可,因为服务器渲染一方面适合比较通用的部分,另外则是 seo 抓取跟图片本身其实关联不大,默认情况下 html 携带图片 alt 属性就足够了。

博客的渲染使用了 @bytemd/react,看一个官方文档的示例

js 复制代码
import gfm from "@bytemd/plugin-gfm";
import { Editor, Viewer } from "@bytemd/react";

const plugins = [
  gfm(),
  // Add more plugins here
];

const App = () => {
  const [value, setValue] = useState("");

  return (
    <Editor
      value={value}
      plugins={plugins}
      onChange={(v) => {
        setValue(v);
      }}
    />
  );
};

传递 value 属性就得到一个完整的 view,不过这里不太符合我们要求,因为我们需要给 <img /> 添加 onClik 事件,有两种思路可以做到:

  1. 一种是@bytemd/react 自定义插件
  2. 另外一种就是拦截整体整体区域的点击,利用冒泡机制即可。

下面是一个示例,使用第二种方式

js 复制代码
 const [visible, setVisible] = useState(false);
  const [activeIndex, setActiveIndex] = useState(0);
  useEffect(() => {
    const dom = document.querySelector(".markdown-body") as HTMLDivElement;
    const callback = (e: MouseEvent) => {
      const dom = e.target as HTMLImageElement;
      if (!/img/i.test(dom.nodeName)) {
        return;
      }
      const index = imgAll.indexOf(dom.src);
      setActiveIndex(index);
      setVisible(true);
    };
    dom.addEventListener("click", callback);
    return () => {
      dom.removeEventListener("click", callback);
    };
  }, [imgAll]);

之后简单封装一下预览图片组件,这里使用了 react-viewer

js 复制代码
"use client";
import { Dispatch, SetStateAction, useEffect, useMemo } from "react";
import Viewer from "react-viewer";

interface Props {
  imgAll: string[];
  visible: boolean;
  setVisible: Dispatch<SetStateAction<boolean>>;
  activeIndex: number;
}

export function Preview({ visible, imgAll, setVisible, activeIndex }: Props) {
  const images = useMemo(() => {
    return imgAll.map((f) => {
      return {
        src: f,
        alt: f.split("/").at(-1),
      };
    });
  }, [imgAll]);
  // 防止点开抖动
  const id = "article_style";

  useEffect(() => {
    if (!visible) {
      const dom = document.querySelector(`#${id}`);
      if (dom) {
        // 延迟去除,防止抖动
        setTimeout(() => {
          document.head.removeChild(dom);
        }, 500);
      }
      return;
    }
    const { clientWidth } = window.document.documentElement;
    const screenDifference = window.innerWidth - clientWidth;
    const content = `
      html body{
        overflow-Y:hidden;
        ${
          screenDifference > 0 ? `width:calc(100% - ${screenDifference}px)` : ""
        }
      }
  `;
    const style =
      document.querySelector(`#${id}`) || document.createElement("style");
    style.id = id;
    style.innerHTML = content;
    document.head.appendChild(style);
  }, [visible]);

  return (
    <Viewer
      visible={visible}
      activeIndex={activeIndex}
      onClose={() => {
        setVisible(false);
      }}
      onMaskClick={() => {
        setVisible(false);
      }}
      images={images}
    />
  );
}

之后引入

js 复制代码
const Preview = dynamic(() => import("./preview").then((e) => e.Preview), {
  ssr: false,
});

点击量

很多时候需要对文章点击量进行一个整体衡量,包括站点访问量之类的,这里用的是 不蒜子 - 极简网页计数器

首先安装依赖

sh 复制代码
pnpm i busuanzi.pure.js

之后在每次路由发生变化的时候进行监听

js 复制代码
"use client";
import { usePathname } from "next/navigation";
import { fetch } from "busuanzi.pure.js";
import { useUpdateEffect } from "ahooks";

// 给文章添加点击量
export default function Statistics() {
  const pathname = usePathname();
  useUpdateEffect(() => {
    fetch();
  }, [pathname]);

  return null;
}

在 app/page.tsx 页面引入

js 复制代码
const Statistics = dynamicNext(() => import("./statistics"), { ssr: false });

return () => {
  <Statistics></Statistics>;
};

在需要地方引入,例如我需要某一篇文章的访问量,那我就

html 复制代码
<span>
  <i className="qzf qzf-eye" />
  <span id="busuanzi_value_page_pv" suppressHydrationWarning> 0 </span>次
</span>

其他的方式可以看一下文章,最后需要注意一下,需要设置 meta 属性为 no-referrer-when-downgrade,具体原因看不蒜子在 Chrome 85 版本后所有页面统计是同一个数据

js 复制代码
export const metadata: Metadata = {
  referrer: "no-referrer-when-downgrade",
};

添加收录

这里以 Google 为例,可以访问此网站,根据示例一步步来。

然后在 app/layout.tsx 中添加下面这样的代码

js 复制代码
  return (
    <html lang="zh">
        <meta
          name="google-site-verification"
          content="4FVbyJeMZIl9kKhdo9gaJLqZviP6Z5En9GbS5VD8g6w"
        />
      </head>
      <body>

      </body>
    </html>
  );

给代码添加复制和在线运行功能

这块因为这段时间心情很糟糕,代码并没有写完,所以下面全部都是伪代码形式。

因为渲染 md 用的组件是 @bytemd/react,它其实是支持插件拓展的,放一张官方的图

有两个步骤可以完成添加的功能

  • The HTML AST could be manipulated by several rehype plugins
  • Some extra DOM manipulation after the HTML being rendered

最后一种对 ssr 没有帮助,相当于客户端渲染了,不过对 dom 添加之类的操作十分方便我们做一些定制,例如添加复制,我们可以写一个 copy 组件,然后在 dom 元素出现的时候直接使用 react-dom render() 指定元素就下可以了。

结合一个官方给出的示例来进行看下

js 复制代码
export default function mathPlugin(): BytemdPlugin {
  return {
    remark: (processor) => processor.use(remarkMath),
+   viewerEffect({ markdownBody }) {
+     const renderMath = async (selector: string, displayMode: boolean) => {
+       const katex = await import('katex').then((m) => m.default)
+
+       const els = markdownBody.querySelectorAll<HTMLElement>(selector)
+       els.forEach((el) => {
+         katex.render(el.innerText, el, { displayMode })
+       })
+     }
+
+     renderMath('.math.math-inline', false)
+     renderMath('.math.math-display', true)
+   },
  }
}

相当于就是操作 dom 元素了。

最后

如果文章有错误之类的欢迎指出,顺便下一篇文章就是收尾了,主要介绍使用 Github Actions 来完成自动发布,在使用的时候只需要监听 issues 的变化就行。

相关推荐
前端的日常几秒前
网页视频录制新技巧,代码实现超简单!
前端
前端的日常2 分钟前
什么是 TypeScript 中的泛型?请给出一个使用泛型的示例。
前端
ccc10186 分钟前
老师问我localhost和127.0.0.1,有什么区别?
前端
Struggler28113 分钟前
Chrome插件开发
前端
前端 贾公子26 分钟前
Monorepo + vite 怎么热更新
前端
然我1 小时前
不用 Redux 也能全局状态管理?看我用 useReducer+Context 搞个 Todo 应用
前端·javascript·react.js
前端小巷子1 小时前
Web 实时通信:从短轮询到 WebSocket
前端·javascript·面试
神仙别闹1 小时前
基于C#+SQL Server实现(Web)学生选课管理系统
前端·数据库·c#
web前端神器1 小时前
指定阿里镜像原理
前端
枷锁—sha1 小时前
【DVWA系列】——CSRF——Medium详细教程
android·服务器·前端·web安全·网络安全·csrf