Skip to content

性能优化 ​

一、优化图片 ​

使用Next.js提供的Image组件来渲染图片。Image组件扩展了HTML <img>元素,具有图像自动优化的特性

1. 本地图片 ​

对于本地图片,使用import方式导入图像文件。Next.js 将根据导入的文件自动确定图像的宽度和高度,也可使用width和height手动指定图片的宽高

tsx
import Image from "next/image";
import profilePic from "./me.png";

export default function Page() {
  return (
    <Image
      src={profilePic}
      alt="Picture of the author"
      // width={500} automatically provided
      // height={500} automatically provided
    />
  );
}

提示:不要使用 require 导入图像文件,使用require之后只能声明为客户端组件

tsx
<Image
  src={require("@/assets/images/arrow.png")}
  width={24}
  height={24}
  alt="加载中"
/>

2. 远程图片 ​

对于远程图片,由于不能事先分析出图片的大小,因此需手动提供图片的宽高

tsx
import Image from "next/image";

export default function Page() {
  return (
    <Image
      src="https://s3.amazonaws.com/my-bucket/profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  );
}

并且需配置域名才能正常使用,修改next.config.mjs

js
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "s3.amazonaws.com",
      },
    ],
  },
};

3. 图片优先级 ​

为图像添加优先级属性,该属性将使图像成为页面的最大内容绘制(LCP)元素。这样可以让Next.js特别优先加载这个图像,从而提升LCP(最大内容渲染)的性能

tsx
import Image from "next/image";
import profilePic from "../public/me.png";

export default function Page() {
  return <Image src={profilePic} alt="Picture of the author" priority />;
}

4. 图片懒加载 ​

默认情况下,图片将懒加载,即达到可视区时才会去加载

tsx
<Image src={profilePic} alt="" loading={"lazy"} />

也可手动指定图片立即加载,即使图片不在可视区

tsx
<Image src={profilePic} alt="" loading={"eager"} />

5. 不优化图片 ​

使用Image组件默认会将图片转为webp格式,在某些情况下,也会更改图片的宽高。如果不想图片的大小被改变,可使用unoptimized,源图像将按原样提供

tsx
<Image src={profilePic} alt="" unoptimized />

也可全局统一配置使用原样图片,修改next.config.mjs

js
const nextConfig = {
  images: {
    unoptimized: true,
  },
};

6. 图片自适应 ​

图片的宽高要么通过本地导入,程序自动确定图片的宽高;要么手动指定图片的宽高。除此以外,图片组件可通过指定 fill 属性自动填充父元素

默认情况下,使用了 fill 属性的图片组件将被分配为 position: "absolute",因为父元素也需使用position: "relative"、position: "fixed", 或 position: "absolute" 进行指定

tsx
import Image from "next/image";

import One from "@/assets/images/th.jpg";

export default async function Page() {
  return (
    <div style={{ width: 150, height: 250, position: "relative" }}>
      <Image src={One} alt="图片" fill={true} objectFit="cover" />
    </div>
  );
}

二、script ​

使用Next.js提供的Script组件来加载第三方脚本文件,Next.js将确保脚本只加载一次

1. 在路由布局文件中引入 ​

tsx
import Script from "next/script";

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <>
      <section>{children}</section>
      <Script src="https://example.com/script.js" />
    </>
  );
}

2. 在根布局文件中引入 ​

tsx
import Script from "next/script";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
      <Script src="https://example.com/script.js" />
    </html>
  );
}

3. 脚本加载时机 ​

Script 组件可通strategy属性控制脚本加载时机

  1. beforeInteractive:在可交互(水合)之前加载

  2. afterInteractive:在可交互之后尽早加载(默认)

  3. lazyOnload:在浏览器空闲时加载

三、懒加载 ​

默认情况下,Next.js的服务端组件会自动进行代码分割。这意味着页面上用到的服务端组件,会被分割成独立的块,只有在需要时才会被加载。但对于客户端组件来说,需要开发者手动进行控制以实现按需加载

1. 动态加载组件 ​

Next.js提供了next/dynamic这个包,它结合了React.lazy和Suspense两个API,来实现客户端组件的懒加载

tsx
"use client";

import { useState } from "react";
import dynamic from "next/dynamic";

// 客户端组件
const ComponentA = dynamic(() => import("../components/A"));
const ComponentB = dynamic(() => import("../components/B"));

export default function ClientComponentExample() {
  const [showMore, setShowMore] = useState(false);

  return (
    <div>
      {/* 立即加载,但在一个单独的客户端包中 */}
      <ComponentA />

      {/* 只在满足条件时按需加载 */}
      {showMore && <ComponentB />}
      <button onClick={() => setShowMore(!showMore)}>Toggle</button>
    </div>
  );
}

如果客户端组件使用到了window等浏览器对象时,在服务端预呈现会报错,因为在服务端没有该对象。此时可禁用客户端组件的预呈现功能,直接在客户端渲染

tsx
const ComponentC = dynamic(() => import("../components/C"), { ssr: false });

2. 动态加载第三方包 ​

可以使用import()函数按需加载外部库。这个例子使用外部库fuse.js进行模糊搜索。只有在用户输入搜索输入后,才会在客户机上加载该模块。

tsx
"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>
  );
}

3. 自定义加载样式 ​

next/dynamic结合了React.lazy和Suspense两个API,在Suspense中可自定义加载组件,在next/dynamic中同样可以自定义加载组件,只需传入第二个参数

tsx
import dynamic from "next/dynamic";

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

四、预加载,预连接,DNS预解析 ​

Next.js暂未直接支持这些配置,可使用ReactDOM将link标签安全地插入到文档的<head>中

1. 新建app/preload-resources.tsx ​

tsx
"use client";

import ReactDOM from "react-dom";

export function PreloadResources() {
  ReactDOM.preload("...", { as: "..." });
  ReactDOM.preconnect("...", { crossOrigin: "..." });
  ReactDOM.prefetchDNS("...");

  return null;
}

ReactDOM.preload('...', { as: '...' }) 相当于:

html
<link rel="preload" href="..." as="..." />

ReactDOM.preconnect('...', { crossOrigin: '...' }) 相当于:

html
<link rel="preconnect" href="..." crossorigin />

ReactDOM.prefetchDNS('...') 相当于:

html
<link rel="dns-prefetch" href="..." />

2. 修改app/layout.tsx ​

tsx
import PreloadResources from "./preload-resources";

const RootLayout = ({ children }: React.PropsWithChildren) => (
  <html lang="en">
    <body>
      <PreloadResources />
      {children}
    </body>
  </html>
);

export default RootLayout;

五、打包分析 ​

1. 安装 ​

bash
npm i cross-env -D
npm i @next/bundle-analyzer -D

2. 修改next.config.mjs ​

js
import bundleAnalyzer from "@next/bundle-analyzer";

const withBundleAnalyzer = bundleAnalyzer({
  enabled: process.env.ANALYZE === "true", //当环境变量ANALYZE为true时开启
});

const nextConfig = {};

export default withBundleAnalyzer(nextConfig);

3. 修改 package.json ​

将启动参数build修改为:

json
"build": "cross-env ANALYZE=true next build",

4. 运行 npm run build 进行打包,.next文件夹下会生成analyze文件夹 ​

.next/analyze/nodejs.html:显示的是服务端文件包的大小,即.next/server文件夹下的资源

.next/analyze/client.html:显示的是客户端文件包的大小,即.next/static文件夹下的资源

六、gzip压缩 ​

默认情况下,Next.js 在使用 next start 或自定义服务器时,会使用 gzip 来压缩渲染的内容和静态文件。

如果想关闭gzip压缩,修改next.config.mjs

js
const nextConfig = {
  compress: false,
};