Skip to content

NextJs 数据获取与缓存 ​

fetch 数据获取 ​

Next.js 扩展了原生 fetch API,支持缓存(caching) 和重新验证(revalidating)。

服务端组件中使用 ​

tsx
export default async function Page() {
  const res = await fetch('https://api.example.com/posts')
  const data = await res.json()

  return (
    <ul>
      {data.map(item => <li key={item.id}>{item.title}</li>)}
    </ul>
  )
}
export default async function Page() {
  const res = await fetch('https://api.example.com/posts')
  const data = await res.json()

  return (
    <ul>
      {data.map(item => <li key={item.id}>{item.title}</li>)}
    </ul>
  )
}

路由处理程序中使用 ​

tsx
import { NextResponse } from 'next/server'

export async function GET() {
  const res = await fetch('https://api.example.com/posts')
  const data = await res.json()
  return NextResponse.json(data)
}
import { NextResponse } from 'next/server'

export async function GET() {
  const res = await fetch('https://api.example.com/posts')
  const data = await res.json()
  return NextResponse.json(data)
}

缓存机制 ​

自动缓存的条件 ​

默认情况下,以下场景会自动缓存 fetch 结果:

  • 服务端组件内部使用 fetch
  • 只有 GET 方法的路由处理程序

不会缓存的情况 ​

以下情况不会自动缓存:

  • 在 Server Action 中使用
  • 路由处理程序中定义了非 GET 方法
  • 使用 cache: 'no-store' 或 revalidate: 0
  • 函数体内使用了 headers 或 cookies
  • 配置了 const dynamic = 'force-dynamic'
tsx
// 退出缓存
fetch('https://api.example.com/data', { cache: 'no-store' })
// 退出缓存
fetch('https://api.example.com/data', { cache: 'no-store' })

查看缓存日志 ​

在 next.config.mjs 中开启:

js
const nextConfig = {
  logging: {
    fetches: { fullUrl: true }
  }
}
const nextConfig = {
  logging: {
    fetches: { fullUrl: true }
  }
}

重新验证(Revalidating) ​

基于时间的重新验证 ​

tsx
// 方式一:fetch 参数
fetch('https://api.example.com/data', { next: { revalidate: 10 } })

// 方式二:路由段配置
export const revalidate = 10
// 方式一:fetch 参数
fetch('https://api.example.com/data', { next: { revalidate: 10 } })

// 方式二:路由段配置
export const revalidate = 10

按需重新验证 ​

revalidatePath ​

tsx
import { revalidatePath } from 'next/cache'
import { NextRequest } from 'next/server'

export async function GET(request: NextRequest) {
  const path = request.nextUrl.searchParams.get('path')
  if (path) {
    revalidatePath(path)
    return Response.json({ revalidated: true, now: Date.now() })
  }
  return Response.json({ revalidated: false })
}
import { revalidatePath } from 'next/cache'
import { NextRequest } from 'next/server'

export async function GET(request: NextRequest) {
  const path = request.nextUrl.searchParams.get('path')
  if (path) {
    revalidatePath(path)
    return Response.json({ revalidated: true, now: Date.now() })
  }
  return Response.json({ revalidated: false })
}

revalidateTag ​

tsx
// 页面中给 fetch 打标签
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: { tags: ['refresh'] }
  })
  return res.json()
}

// 路由处理程序中触发重新验证
import { revalidateTag } from 'next/cache'

export async function GET(request: NextRequest) {
  const tag = request.nextUrl.searchParams.get('tag')
  revalidateTag(tag)
  return Response.json({ revalidated: true, now: Date.now() })
}
// 页面中给 fetch 打标签
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: { tags: ['refresh'] }
  })
  return res.json()
}

// 路由处理程序中触发重新验证
import { revalidateTag } from 'next/cache'

export async function GET(request: NextRequest) {
  const tag = request.nextUrl.searchParams.get('tag')
  revalidateTag(tag)
  return Response.json({ revalidated: true, now: Date.now() })
}

Server Actions ​

Server Actions 是在服务端执行的异步函数,可在服务端和客户端组件中使用。

定义方式 ​

tsx
// 函数级别
async function getData() {
  'use server'
  // ...
}

// 模块级别(app/actions/actions.ts)
'use server'
export async function getData() {
  const res = await fetch('https://api.example.com/posts')
  const data = await res.json()
  return data
}
// 函数级别
async function getData() {
  'use server'
  // ...
}

// 模块级别(app/actions/actions.ts)
'use server'
export async function getData() {
  const res = await fetch('https://api.example.com/posts')
  const data = await res.json()
  return data
}

使用方式 ​

tsx
import { getData } from '@/actions/actions'

export default async function Page() {
  const data = await getData()
  return (
    <ul>
      {data.map(item => <li key={item.id}>{item.title}</li>)}
    </ul>
  )
}
import { getData } from '@/actions/actions'

export default async function Page() {
  const data = await getData()
  return (
    <ul>
      {data.map(item => <li key={item.id}>{item.title}</li>)}
    </ul>
  )
}

Pages Router vs App Router ​

Pages RouterApp Router
前后端交互先定义 API 接口,再调用直接调用 Server Actions
代码组织接口和页面分离函数可随处使用
简洁度需要手动创建接口无需额外接口

中间件 ​

中间件在请求完成之前执行,可用于日志记录、header 处理、鉴权等。

基本定义 ​

ts
// middleware.ts(项目根目录)
import { NextResponse } from 'next/server'

export function middleware(request) {
  // 中间件逻辑
}

export const config = {
  matcher: '/api/:path*',  // 匹配路径
}
// middleware.ts(项目根目录)
import { NextResponse } from 'next/server'

export function middleware(request) {
  // 中间件逻辑
}

export const config = {
  matcher: '/api/:path*',  // 匹配路径
}

中间件链 ​

ts
// middlewares/logging.ts
export const logging = (next) => {
  return async (request) => {
    console.log('logging')
    return next(request)
  }
}

// middlewares/headers.ts
export const headers = (next) => {
  return async (request) => {
    console.log('headers')
    return next(request)
  }
}

// middleware.ts
import { logging } from '@/middlewares/logging'
import { headers } from '@/middlewares/headers'

function chain(functions, index = 0) {
  const current = functions[index]
  if (current) {
    return current(chain(functions, index + 1))
  }
  return () => NextResponse.next()
}

export default chain([logging, headers])

export const config = {
  matcher: '/api/:path*',
}
// middlewares/logging.ts
export const logging = (next) => {
  return async (request) => {
    console.log('logging')
    return next(request)
  }
}

// middlewares/headers.ts
export const headers = (next) => {
  return async (request) => {
    console.log('headers')
    return next(request)
  }
}

// middleware.ts
import { logging } from '@/middlewares/logging'
import { headers } from '@/middlewares/headers'

function chain(functions, index = 0) {
  const current = functions[index]
  if (current) {
    return current(chain(functions, index + 1))
  }
  return () => NextResponse.next()
}

export default chain([logging, headers])

export const config = {
  matcher: '/api/:path*',
}