Next.js 构建博客之常见问题处理

  1. Next.js 构建博客之资源抓取
  2. Next.js 构建博客之博客搭建
  3. Next.js 构建博客之打包 SSG

这是 Next.js 构建博客的第四篇文章,上一篇文章介绍了 Next.js 如何打包成 SSG 文件,这篇文章重点介绍一下在开发中容易遇到的问题。

图片盗链

有一些网站会有图片防盗处理,例如掘金,为了减少网站的压力,在其他网站访问资源的时候会直接 403,判断原理是在 http 请求中会有 referer 和 host 参数,当参数不一致就认定为非法。

绕过这个的方式也很简单,就是对 referer 进行修改,默认值是 strict-origin-when-cross-origin。

对于同源的请求,发送来源、路径以及查询字符串。对于在相同安全级别的情况下(HTTPS→HTTPS)的跨源请求,仅发送来源。在目标的安全级别下降的情况下(HTTPS→HTTP)则不发送 Referer 标头。

那么直接修改 referer 为 no-referrer,整个 Referer 首部会被移除。访问来源信息不随着请求一起发送。

下面是一个具体的实现代码,对图片加载失败进行拦截处理。

js 复制代码
"use client";

import { useEffect } from "react";

const map = new WeakMap();

// 拦截图片错误,并且正确加载
export default function AssetsWatch() {
  const replace = (dom: HTMLImageElement) => {
    if (map.get(dom)) {
      return;
    }
    const src = dom.src;
    map.set(dom, 1);
    dom.src = `${process.env.NEXT_PUBLIC_BASE_PATH}/error.svg`;
    fetch(src, {
      mode: "cors",
      referrerPolicy: "no-referrer",
    })
      .then((response) => {
        if (response.ok) {
          return response.blob();
        }

        throw new Error("Image request failed");
      })
      .then((blob) => {
        const imageUrlObject = URL.createObjectURL(blob);
        dom.src = imageUrlObject;
      })
      .catch((error) => {
        dom.alt = `图片加载失败`;
        dom.title = `图片加载失败,已回滚到默认图片`;
        dom.setAttribute("data-src", src);
        console.error("Error:", error.message);
      });
  };

  useEffect(() => {
    // 初始遍历一遍,因为插入时间已经很晚了
    const forEach = () => {
      Array.from(document.images).forEach((img) => {
        const dom = new Image();
        dom.src = img.src;
        dom.onerror = () => {
          replace(img);
        };
      });
    };
    const callback = (e: ErrorEvent) => {
      const dom = e.target as HTMLElement;
      if (!dom || !/img/i.test(dom.nodeName)) {
        return;
      }
      replace(dom as HTMLImageElement);
    };

    window.addEventListener("error", callback, true);
    forEach();
    return () => {
      window.removeEventListener("error", callback);
    };
  }, []);

  return null;
}

如果不太明白,可以参考我这篇文章阅读 如何优雅处理图片异常

dynamic 和 Suspense 使用场景

dynamic

dynamic 是 React.lazy 和 Suspense 结合体,一般有三种使用场景

  1. 跳过 ssr

有一些场景不需要 ssr,例如我添加一个点击量的组件或者添加一个查看图片的功能,这种情况下 ssr 没有任何帮助,这个时候就可以使用 dynamic。

js 复制代码
import dynamicNext from "next/dynamic";

const Statistics = dynamicNext(() => import("./statistics"), { ssr: false });

return (
  <>
    <Statistics></Statistics>
  </>
);
  1. 延迟加载

通过延迟加载来减少初始渲染路线,来提高初始加载性能。例如延迟加载客户端组件或者库,在用户点击的时候才进行渲染。

page.tsx

js 复制代码
"use client";

import { useState } from "react";
import dynamic from "next/dynamic";
const ComponentA = dynamic(() => import("../components/A"));

export default function () {
  return (
    <>
      <ComponentA />
    </>
  );
}

components/A.tsx

js 复制代码
"use client";

import { useState } from "react";

const names = ["Tim", "Joe", "Bel", "Lee"];

export default function Page() {
  const [results, setResults] = useState();

  return (
    <div>
      <input
        type="text"
        placeholder="Search"
        onChange={async (e) => {
          const { value } = e.currentTarget;
          // Dynamically load fuse.js
          const Fuse = (await import("fuse.js")).default;
          const fuse = new Fuse(names);

          setResults(fuse.search(value));
        }}
      />
      <pre>Results: {JSON.stringify(results, null, 2)}</pre>
    </div>
  );
}
  1. 添加自定义加载组件
js 复制代码
import dynamic from "next/dynamic";

const WithCustomLoading = dynamic(
  () => import("../components/WithCustomLoading"),
  {
    loading: () => <p>Loading...</p>,
  }
);

export default function Page() {
  return (
    <div>
      <WithCustomLoading />
    </div>
  );
}

Suspense

在一些组件中难免会使用到客户端组件,例如添加点击事件,或者使用 useState 等,这个时候就不是服务器组件了,一般要么把整个页面都变成客户端组件,但是这个会导致失去 seo 功能,另外一种则是使用 Suspense 对需要使用客户端的组件进行剥离,下面是一个示例。

js 复制代码
import { Suspense } from "react";
import SearchBar from "./search-bar";

function SearchBarFallback() {
  return <>placeholder</>;
}

export default function Page() {
  return (
    <>
      <nav>
        <Suspense fallback={<SearchBarFallback />}>
          <SearchBar />
        </Suspense>
      </nav>
      <h1>Dashboard</h1>
    </>
  );
}

初始情况下 html 会加载 fallback 组件内容,之后水合过程将使用 SearchBar 组件。

不要使用重定向

因为博客的首页和 pages 页面其实是一个东西,所以想着 / 直接重定向到 page/1 就行,但是发现在使用过程中会有很明显白屏现象,就是 page 页面下的 loading 没有生效。

所以建议还是不要在首屏使用重定向这个方式。

不要使用 style 样式

博客的 UI 框架部分使用了 antd,在页面加载的过程中会有一个骨架屏,不过因为 antd5 的版本使用 style 来重构样式,在组件运行的时候注入 <style /> 方便定制和切换主题,导致 Next.js 使用的时候资源不会被缓存且导致骨架屏最初样式没有被加载出来。

目前 issues 有相关讨论,但是还没解决。

解决方法:

  1. 切换低版本 antd
  2. 换一个 loading 方案

最后

如果文章有书写错误地方欢迎指出。下一篇会介绍如何给博客添加点击量以及图片放大缩小等功能。

相关推荐
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光3 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   3 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   3 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web3 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho5 小时前
【TypeScript】知识点梳理(三)
前端·typescript