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 Router | App 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*',
}
Cao Note