主题
性能优化
一、优化图片
使用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属性控制脚本加载时机
beforeInteractive:在可交互(水合)之前加载
afterInteractive:在可交互之后尽早加载(默认)
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 -D2. 修改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,
};