likes
comments
collection
share

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

作者站长头像
站长
· 阅读数 22

缓存配置

next缓存分为请求记忆、数据缓存、完整路由缓存、路由缓存。其中前三种都是针对服务端组件的,只有路由缓存是针对客户端组件的

机制缓存内容缓存位置缓存目的缓存持续时间
请求记忆函数返回值服务端在react组件树中复用数据每个请求生命周期
数据缓存数据服务端在用户请求和部署期间存储数据永久 (可以被重新验证)
完整路由缓存HTML和 RSC payload服务端减少渲染开销,提高性能永久 (可以被重新验证)
路由缓存RSC Payload客户端在导航期间减少服务端请求用户 session 或基于时间

next.js会尽量缓存改善性能,默认使用静态渲染和数据缓存,下面是官网给的首次访问静态路由的流程 🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

  1. 打包构建/a路由,由于是第一次访问,路由缓存、完整路由缓存、请求记忆、数据缓存都会miss;从数据源获取数据后,依次存入数据缓存、请求记忆,生成的RSC payload和HTML存入完整路由缓存
  2. 客户端访问/a路由,命中缓存的RSC payload和HTML,并且把RSC payload存在路由缓存中

目前最新的next15,数据默认不缓存

请求记忆

概念

react扩展了fetch api,使用fetch默认会进行缓存(针对URL和请求参数相同的请求)。所以我们没有必要把请求得到的数据通过props等方式传参,应该直接在每个组件发起请求

async function getItem() { 
// fetch自动请求记忆缓存结果
const res = await fetch('https://.../item/1') 
return res.json()
} 
// getItem调用了2次, 只有第一次有请求花销
const item = await getItem() // 第一次请求,缓存未命中
// 第二次请求在路由任何地方触发缓存都会生效
const item = await getItem() // 第二次请求,缓存击中

请求记忆只适用于fetch的get请求,而且只适用于react组件树

它可以适用于generateMetadatagenerateStaticParamslayout(布局)page(页面)和其他服务端组件

不用fetch请求如何实现请求记忆,直接用react内置的cache函数

最佳实践

  1. 不重新验证
  2. 默认不要退出请求记忆,如果实在要退出可以使用AbortController
const { signal } = new AbortController()
fetch(url, { signal })

数据缓存

工作流程

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

  1. 首次访问/a路由,请求记忆和数据缓存都会miss,从数据源中获取数据后会存入数据缓存和请求记忆
  2. 再次访问/a路由,优先从请求记忆和数据缓存中取数据,缓存没命中才会重新请求

如果配置了cache:'no-store',不管请求多少次都是数据缓存都会miss,直接从数据源中获取,但是请求记忆还是会生效

数据缓存和请求记忆的区别

两者都是用于缓存,提升性能的

  1. 持续时间不同;数据缓存在请求和部署期间都是永久性的 ,请求记忆只在每个请求的生命周期有效

  2. 作用不同,数据缓存用于减少原始数据源的请求数量,请求记忆用于减少重复请求数量

重新验证缓存方法

  1. 基于时间的重新验证

使用next.revalidate,适用于不经常更改而且新鲜度不重要的数据

fetch('https://...', { next: { revalidate: 60 } }) // 60秒后重新验证

配置了next.revalidate后如下图 🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二) 在60秒以内都会使用数据缓存,在60秒以后再有新的请求会触发重新验证,第一次请求还用之前的缓存值,将新的数据加入到缓存。60秒后的第2次请求使用新的缓存

  1. 按需重新验证

分为revalidatePathrevalidateTag两种方式

// actions.tsx
revalidatePath('/')

// app/form/page.tsx
fetch('...',{next:{tags:['a']}})
// actions.tsx
revalidateTag('a')

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二) 配置了revalidateTag后,第一次调用请求正常缓存,触发revalidateTag后会直接从数据缓存中清除对应tag的数据,再次请求时还是会把tag的数据缓存起来,revalidatePath同理

退出缓存方法

  1. 直接配置cache:'no-store',这种方式是针对配置的请求有效
fetch('...',{cache:'no-store'})
  1. 路由段配置,这种方式会影响这个路由段的所有请求
export const dynamic = 'force-dynamic'

完整路由缓存

概念

next.js自动渲染路由和缓存路由,这是内置的优化,可以让页面加载速度变快

在服务端,next.js使用react编排渲染,渲染会通过路��段和Suspense被分成chunks块,每一个chunks的渲染都会经过两个步骤:

  1. react把服务端组件渲染为特殊的数据格式,优化为流式渲染(Streaming),即RSC payload
  2. Next.js使用RSC payload和客户端组件渲染指令,在服务端上渲染HTML

我们不需要等所有工作完成才渲染,这就是next.js的流式渲染

RSC payload是什么

RSC payload全称为服务端组件payload(React Server Component Payload),它是react服务端组件树的渲染结果,用于react客户端更新浏览器的DOM结构,包括:

  • 服务端组件的渲染结果
  • 客户端组件占位符和引用结果
  • 服务端组件传给客户端组件的属性 RSC payload不会打包到客户端代码中,它属于服务端组件代码,因此可以减少包体积大小

静态路由默认开启完整路由缓存,由于动态路由在请求的时候才渲染,因此不会被完整路由缓存

下面这个图片清晰的展示了静态路由和动态路由的区别

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

作用

  1. 性能提升,通过缓存整个路由页面,用户重复访问同一页面时,可以直接从缓存中加载而不是服务器生成,大大减少了页面加载时间,提升了用户体验
  2. 降低成本,减少了服务器的计算资源和带宽需求,在高流量的情况下效果显著,可以降低运营成本
  3. SEO优化,对于SSG页面而言有利于SEO优化,可以帮助爬虫直接抓取完整的HTML内容

优缺点

优点

  1. 有利于加快页面加载速度,尤其是内容不怎么需要更新的页面
  2. 减轻服务端压力;服务器不需要频繁地处理相同的页面请求,特别是在高并发场景下,可以有效分配资源
  3. 更好的用户体验;快速响应和即时交互提升了用户体验

缺点

  1. 内容更新问题,如果内容需要经常更新,缓存可能导致用户看到的是过时的信息,除非缓存被更新或清除
  2. 缓存管理增加了一定的维护成本和复杂性,需要有效的缓存管理策略来保证内容的新鲜度,同时避免缓存未命中率过高
  3. 存储限制;大量的缓存数据可能会占用客户端或边缘服务器的存储空间,尤其在资源丰富的应用中
  4. 初次部署成本,对于ISR和SSG策略,首次构建部署可能需要比较长的时间,因为所有页面都要预先生成

失效方式

完整路由缓存有两种方式失效:

  1. 重新验证
  2. 重新部署

退出方式

  1. 将静态渲染改为动态渲染

路由缓存

概念

路由缓存也叫客户端缓存或者预取缓存,Next.js的客户端缓存是基于内存缓存,存储RSC Component,缓存持续时间在整个用户session中

路由缓存的工作流程如下图

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

  1. 首次访问路由/a,路由缓存miss,它会根据路由段顺序从根路由开始,从上到下依次到目标路由存入路由缓存,顺序依次为/layout,/a(page)
  2. 导航到/b会触发部分命中,因为这个路由和/a路由共享根路由/layout命中,/b miss,将/b加入路由缓存
  3. 再次导航/a,路由缓存生效

路由缓存可以改善用户导航体验,前进/后退导航速度会加快,因为有路由缓存,没有访问过的路由导航速度也会加快,因为有prefetch和部分渲染

路由导航不��触发full page reload(完整路由重新加载)

完整路由缓存和路由缓存有什么区别

  1. 存储位置不同,完整路由缓存是在服务端上的,路由缓存是在客户端的
  2. 存储期限不同,完整路由缓存永久性存储RSC payload和HTML,在多个用户请求期间有效;路由缓存临时存储RSC Payload,它只在用户会话有效
  3. 完整路由缓存只存储静态路由,路由缓存静态路由和动态路由都存储
  4. 重新验证或退出路由缓存会让完整路由缓存失效,因为渲染的输出依赖数据;完整路由缓存的重新验证或退出不会影响路由缓存

持续时间

路由缓存的持续时间取决于两个因素:会话和自动失效期间

  1. 会话,缓存在导航期间都有效,直到页面刷新
  2. 自动失效期间,路由段在特定时间内自动失效,取决于资源怎么定义prefetch
  • Default Prefetching (prefetch={null} 或者 未指定): 30秒
  • Full Prefetching: (prefetch={true} 或者 router.prefetch): 5分钟

从v14.2.0-canary.53版本开启了实验性支持配置自动失效时长

重新验证方式

  1. 在server action中,使用revalidatePath或revalidateTag;使用cookies.set或者cookies.delete
  2. 调用router.refresh会让路由缓存失效,向服务端发起新的请求获取当前路由

退出方式

没有办法退出路由缓存,但是你可以退出prefetch,通过在<Link>标签加prefetch属性为false实现

<Link href='/a' prefetch={false}></Link>

server action

概念

server action是指在服务端执行的异步函数,它们可以在客户端组件和服务端组件中使用,用于处理表单提交和数据突变

可以通过在函数顶部定义use server实现

//app/page.tsx
// Server Component
export default function Page() { 

async function create() {
'use server' 
// ... 
} 
return ( 
// ... 
)}

也可以在actions文件夹中定义

//app/action.ts
'use server' 
export async function create() { 
// ...
}

// app/page.tsx
import create from "./action.ts"
create()

server action既可以在客户端组件使用又可以在服务端组件使用,但是客户端组件只支持第二种方式使用

使用示例

  1. 新建next项目,使用typescript,项目名next-demo

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

npx create-next-app@latest --typescript next-demo
cd next-demo
pnpm install

项目目录如下

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

2.首先进入global.css,把默认的难看背景色去掉

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二) 3. 在app目录下新建shoplist文件夹page.tsx

// app/shoplist/page.tsx
import { getShopList, addShopItem } from "./actions";
export default async function Page() {
  const list = await getShopList();
  return (
    <div className="p-2">
      <form action={addShopItem}>
        <input
          className="border border-gray-300 border-solid rounded-sm"
          name="item"
        />
        <button
          className="mx-1 px-2 py-1 text-white bg-blue-300 hover:bg-blue-500 rounded-md"
          type="submit"
        >
          添加购物项
        </button>
      </form>
      <ol>
        {list.map((item, i) => (
          <li key={i}>
            {i + 1}.{item}
          </li>
        ))}
      </ol>
    </div>
  );
}


  1. 在shoplist文件夹下增加actions.tsx
// shoplist/actions.tsx
"use server";
import { revalidatePath } from "next/cache";

const data = ["毛巾", "牙刷", "餐具"];

export async function getShopList() {
  return data;
}
export async function addShopItem(formData: FormData) {
  const item = formData.get("item");
  if (item) {
    data.push(item + "");
  }
  revalidatePath("/shoplist");
  return data;
}


  1. 运行项目访问/shoplist
pnpm dev

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二) 点击添加购物项,调用了serverAction的addShopItem方法,可以看到发起了一个POST请求,查看请求体可以看到提交项包括ACTION_ID和数据项

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

ACTION_ID是什么东西?

查看页面结构,可以看到表单元素中自动添加了一个带ACTION_ID的input标签,可以理解为它是这个server action对应的唯一标志

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

数据提交后返回的是RSC payload

在实际项目中,每个表单手动控制请求的error,status,pending效率太低了,next.js提供了乐观更新和useFormStatus/useActionState来控制表单

性能优化

内置组件优化

<Image>

Next.js的内置图片组件<Image>对<img>标签做了优化,实现了懒加载,还可以根据设备尺寸调整图片大小;它还实现了视觉稳定性,为了防止图片在加载时出现布局偏移

图片尺寸对于LCP来说是个很重要的优化点

文档

推荐将图片资源放在根目录的public文件夹下,新建images目录存放所有静态图片资源,支持的图片格式:

  • .png
  • .jpg
  • .jpeg
  • .webp

Next.js会根据你导入的图片自动决定图片的widthheight属性,可以有效减少累计布局偏移(CLS)

import Image from "next/image"

export default function Page(){
 return <>
 // 访问路径为 public/images/logo.png
 // 必填属性 src/width/height/alt
  <Image src={`/images/logo.png`} width={270} height={90} alt="logo"></Image>
 </>
}

远程图片需要在next.config.js中配置才能访问

module.exports = {
  images: { 
    remotePatterns: [ 
        { 
          protocol: 'https', 
          hostname: 's3.amazonaws.com', 
          port: '', 
          pathname: '/my-bucket/**', 
        }, 
        ],
    }
}

上面的配置指的是允许访问s3.amzaonaws.com/my-bucket/** 下的的所有图片

不确定远程图片宽高,可以使用layout配合objectFit属性

import Image from 'next/image'
export default function Page(){
  return <div className="relative w-full h-full">
   <Image 
       src={`https://xxx.xxx`} 
       alt="" 
       fill
       objectFit="contain"></Image>
  </div>
}

<Link>

用于后台预获取资源

客户端组件导航的重要方式之一,除此之外客户端组件还能使用useRouterrouter.push/router.back/router.forward/router.replace 等方式导航

//app/page.tsx
import Link from "next/link"
export default function Page(){
 return <>
   {/* 等价于跳转/order */}
   <Link href={'/order'}></Link>
   {/* 等价于跳转/order?id=1 */}
   <Link href={{
    pathname:'/order',
    search:{
     id:1
    }
   }}></Link>
 </>
}

href属性必填,支持传入字符串或者对象,文档

<Script>

用于加载和控制第三方脚本

加载策略strategy可选值:

  • beforeInteractive 在水合(页面可交互)之前加载,一般用于加载通用脚本
  • afterInteractive 页面可交互后加载,一般用于加载统计脚本
  • lazyOnload 在浏览器空闲时加载,一般用于加载优先级不高的脚本
  • worker 在web worker中加载(实验特性)

<Script>可以在layout(布局)或者Page页面使用,不管被多少页面引用,都只会加载一次。也就是页面导航<Script>不会重复加载

<Script>还支持三个事件:

  • onLoad 脚本加载完成后执行
  • onReady 脚本加载完,组件挂载后执行逻辑
  • onError 脚本加载失败后执行的逻辑 这三个方法都只能在客户端组件使用
// app/layout.tsx
import Script from 'next/script'
export default function Page() { 
return ( 
  <> 
    <Script src="https://example.com/script.js"/> 
  </> 
)}

React的很多性能优化方法都适用于Next.js

性能监测

可以通过自定义组件实现

//app/components/web-vitals.ts
'use client' 
import { useReportWebVitals } from 'next/web-vitals' 

export function WebVitals() { 
  useReportWebVitals((metric) => { 
   console.log(metric) 
})}

在根布局中引入

//app/layout.tsx
import { WebVitals } from "./components/web-vitals.ts"
export default function Layout(){
 return (
  <html>
   <body>
    {children}
    <WebVitals/>
   </body>
  </html>
 )
}

懒加载

Next.js支持懒加载:dynamic函数和React.lazy

'use client' 
import { useState } from 'react'
import dynamic from 'next/dynamic' 

// 引入客户端组件
const ComponentA = dynamic(() => import('../components/A'))
const ComponentB = dynamic(() => import('../components/B'))
const ComponentC = dynamic(() => import('../components/C'), { ssr: false }) // 设置ssr为false跳过SSR渲染

export default function ClientComponentExample() { 
const [showMore, setShowMore] = useState(false) 
return ( 
<div> 
  <ComponentA /> {/* 立即加载,单独的客户端组件*/}

{showMore && <ComponentB />}  {/* showMore为真的时候按需加载 */}
<button onClick={() => setShowMore(!showMore)}>Toggle</button> 


<ComponentC /> {/* 只有客户端加载 */} 
</div> )}

在服务端组件中使用dynamic加载客户端组件,只有客户端组件的部分会被懒加载

打包分析

Next.js也内置了类似于Bundle-anlayzer的功能

  1. 安装
pnpm add @next/bundle-analyzer // 如果使用别的包管理工具用对应语法安装@next/bundle-analyzer
  1. 配置next.config.js文件
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true',})
const nextConfig = {} 
module.exports = withBundleAnalyzer(nextConfig)
  1. 在package.json增加scripts脚本
// package.json
{
  "scripts":{
   "analyze":"ANALYZE=true pnpm build"
  }
}
  1. 运行脚本
pnpm analyze

如图,浏览器会打开多个页面展示包的体积

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

SEO

Next.js内置的Metadata是专用于SEO的,静态使用metadata,动态使用generateMetadata函数。它可以帮助搜索引擎更好的抓取网页内容因此对于SEO优化很有帮助

使用示例

// app/demo/page.tsx
import type {MetaData} from "next"
export const metadata:MetaData = {
   title:'lyllovelemon', // 设置文档标题
   description:'next.js测试应用' // 设置文档描述
}
export default function Page(){
 ...
}

metadata字段参考文档

注意:metadata和generateMetadata函数只能在服务端组件使用;

在同一个路由段不要同时使用metadata和generateMetadata函数

推荐在不需要取数据的场景使用静态metadata,元数据需要数据请求获取的场景使用generateMetadata函数

// app/demo2/[id]/page.tsx
import {Metadata,ResolvingMetadata} from "next"

type Props = {
 params:{id:string}
 searchParams:any
}
export async function generateMetadata({
params,
searchParams
}:Props,
parent:ResolvingMetadata):Promise<Metadata>{
    const id = params.id
    const demo = await fetch(`https://.../${id}`).then((res) => res.json()) // 假设数据为{title:"柠檬酱",id:1}
    return {
        title:demo.title,
        description:`demo详情-${id}`
    }
}
export default function Page(){
 ...
}

Next.js会等待generateMetadata数据请求完成再开始客户端渲染UI,保证<head>中有<meta>标签

generateMetadata接收params和searchParams作为参数,可以获取路由段的params和searchParams

路由段访问路由searchParamsparams
demo2/[id]/page.tsxdemo2/1/page.tsxnull{id:1}
demo2/page.tsxdemo2?id=lyllovelemon{id:'lyllovelemon'}null
demo2/[...id]/page.tsxdemo2/1/2/page.tsxnull{id:[1,2]}

上面示例代码等价于在head标签中注入meta标��

<head>
 <meta name="title" content="柠檬酱"/>
 <meta name="description" content="demo详情-1"/>
 ...
</head>

最佳实践

首先需要了解SEO的<html>标签权重

关键词优化

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

对应Next.js的配置为

是不是超级简单

内容优化

对应的Next.js配置为

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

HTML语义化

HTML5标签都是语义化标签,推荐使用这些

  • <h1>~<h5> 一个页面最好只用一个<h1>标签,和title字段一一对应
  • <nav> 导航标签
  • <header>
  • <footer>
  • <section> 章节、页眉页脚
  • <aside> 侧边栏和引述内容
  • <article> 独立的文档、页面、应用

其次,页面性能也会影响网站排名,推荐使用lighthouse对页面进行优化

指定<rel="canonical">

如果你的网站有多个链接,推荐使用这个meta标签,用来给多个相似网站指定权威网址

<link rel="canonical" href="目标网站绝对路径">

内外链优化

优先使用HTTPS

HTTPS网站比HTTP网站排名高

拿我线上做的项目搜索效果如下

🔥🔥🔥念头通达:手把手带你学next:缓存配置、性能优化和SEO(二)

转载自:https://juejin.cn/post/7388891045818351642
评论
请登录