前言
Next.js内置了许多优化功能,包括自动压缩和缓存等。本篇将介绍在Next.js中相关性能优化内容。
对Next.js14项目搭建还不熟悉的,可先参考我的另一篇文章:给上市公司从0到1搭建Next.js14项目
1. Image
使用Next.js提供的Image组件来渲染图片。Image组件扩展了HTML 元素,具有图像自动优化的特性
1. 本地图片
对于本地图片,使用import方式导入图像文件。Next.js 将根据导入的文件自动确定图像的宽度和高度,也可使用width和height手动指定图片的宽高
js
import Image from 'next/image'
import profilePic from './me.png'
export default function Page() {
return (
<Image
src={profilePic}
alt="Picture of the author"
/>
)
}
提示:不要使用 require 导入图像文件,使用require之后只能声明为客户端组件
js
<Image src={require('@/assets/images/arrow.png')} width={24} height={24} alt="加载中" />
2. 远程图片
对于远程图片,由于不能事先分析出图片的大小,因此需手动提供图片的宽高
js
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(最大内容渲染)的性能
js
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. 图片懒加载
默认情况下,图片将懒加载,即达到可视区时才会去加载
js
<Image src={profilePic} alt="" loading={"lazy"}/>
也可手动指定图片立即加载,即使图片不在可视区
js
<Image src={profilePic} alt="" loading={"eager"}/>
5. 不优化图片
使用Image组件默认会将图片转为webp格式,在某些情况下,也会更改图片的宽高。如果不想图片的大小被改变,可使用unoptimized,源图像将按原样提供
js
<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" 进行指定
js
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>
);
}
2. script
使用Next.js提供的Script组件来加载第三方脚本文件,Next.js将确保脚本只加载一次
1. 在路由布局文件中引入
js
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. 在根布局文件中引入
js
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:在浏览器空闲时加载
3. 懒加载
默认情况下,Next.js的服务端组件会自动进行代码分割。这意味着页面上用到的服务端组件,会被分割成独立的块,只有在需要时才会被加载。但对于客户端组件来说,需要开发者手动进行控制以实现按需加载
1. 动态加载组件
Next.js提供了next/dynamic这个包,它结合了React.lazy和Suspense两个API,来实现客户端组件的懒加载
js
'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等浏览器对象时,在服务端预呈现会报错,因为在服务端没有该对象。此时可禁用客户端组件的预呈现功能,直接在客户端渲染
js
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
2. 动态加载第三方包
可以使用import()函数按需加载外部库。这个例子使用外部库fuse.js进行模糊搜索。只有在用户输入搜索输入后,才会在客户机上加载该模块。
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>
)
}
3. 自定义加载样式
next/dynamic结合了React.lazy和Suspense两个API,在Suspense中可自定义加载组件,在next/dynamic中同样可以自定义加载组件,只需传入第二个参数
js
import dynamic from 'next/dynamic'
const WithCustomLoading = dynamic(
() => import('../components/WithCustomLoading'),
{
loading: () => <p>Loading...</p>,
}
)
4. 预加载,预连接,DNS预解析
Next.js暂未直接支持这些配置,可使用ReactDOM将link标签安全地插入到文档的<head>
中
- 新建app/preload-resources.tsx
js
'use client'
import ReactDOM from 'react-dom'
export function PreloadResources() {
ReactDOM.preload('...', { as: '...' })
ReactDOM.preconnect('...', { crossOrigin: '...' })
ReactDOM.prefetchDNS('...')
return null
}
ReactDOM.preload('...', { as: '...' }) 相当于:
js
<link rel="preload" href="..." as="..." />
ReactDOM.preconnect('...', { crossOrigin: '...' }) 相当于:
js
<link rel="preconnect" href="..." crossorigin />
ReactDOM.prefetchDNS('...') 相当于:
js
<link rel="dns-prefetch" href="..." />
- 修改app/layout.tsx
js
import PreloadResources from './preload-resources';
const RootLayout = ({ children }: React.PropsWithChildren) => (
<html lang="en">
<body>
<PreloadResources />
{children}
</body>
</html>
);
export default RootLayout;
5. 打包分析
- 安装
js
npm i cross-env -D npm i @next/bundle-analyzer -D
- 修改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);
- 修改 package.json
将启动参数build修改为
js
"build": "cross-env ANALYZE=true next build",
- 运行 npm run build 进行打包,.next文件夹下会生成analyze文件夹
.next/analyze/nodejs.html:显示的是服务端文件包的大小,即.next/server文件夹下的资源
.next/analyze/client.html:显示的是客户端文件包的大小,即.next/static文件夹下的资源
6. gzip压缩
默认情况下,Next.js 在使用 next start 或自定义服务器时,会使用 gzip 来压缩渲染的内容和静态文件。
如果想关闭gzip压缩,修改next.config.mjs
js
const nextConfig = {
compress: false,
};
结尾
对Next.js感兴趣的,可先关注我,后续将继续更新相关内容