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 的变化就行。

相关推荐
hunter2062061 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb1 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角1 小时前
CSS 颜色
前端·css
浪浪山小白兔2 小时前
HTML5 新表单属性详解
前端·html·html5
lee5763 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579653 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
光头程序员3 小时前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript
limit for me3 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者3 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
qq_392794484 小时前
前端缓存策略:强缓存与协商缓存深度剖析
前端·缓存