【Next.js 14】性能优化相关知识点

前言

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属性控制脚本加载时机

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

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

  3. 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>

  1. 新建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="..." />
  1. 修改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. 打包分析

  1. 安装
js 复制代码
npm i cross-env -D npm i @next/bundle-analyzer -D
  1. 修改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);
  1. 修改 package.json

将启动参数build修改为

js 复制代码
"build": "cross-env ANALYZE=true next build",
  1. 运行 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感兴趣的,可先关注我,后续将继续更新相关内容

相关推荐
秦jh_13 分钟前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑21326 分钟前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程
Dread_lxy27 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
涔溪1 小时前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与2 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun2 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇2 小时前
ES6进阶知识一
前端·ecmascript·es6
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法