NextJS 13.5.6 API路由缓存异常问题求助
NextJS 13.5.6 API路由缓存异常问题
项目流程
- 浏览器调用NextJS内部API
- NextJS API转发请求至外部微服务API
问题现象
- 修改微服务数据后,NextJS API仍返回旧缓存数据
- 排除浏览器缓存:更换浏览器、使用隐身模式测试,问题依旧
- 排除外部API缓存:API网关日志显示外部API返回最新数据
已尝试的修复措施
- 所有
fetch请求均添加{ cache: 'no-store' }配置 - 未启用Redis或其他第三方缓存机制
项目结构
apiBuilder ├── nextJSApiBuilder.ts └── remoteApiBuilder.ts app ├── api │ └── config │ └── route.ts └── page.tsx
- 页面通过
nextJSApiBuilder.ts调用/api/config /api/config/route.ts通过remoteApiBuilder.ts调用外部API- 所有页面均使用
use client,未启用SSR
疑问点
- 查阅NextJS数据缓存文档后仍无法定位原因
- 看到建议在
route.ts中添加export const revalidate = 0;,但疑惑:服务端运行的route.ts为何需要强制设为动态?
附:相关代码实现
nextJSApiBuilder.ts
const localApiBuilder = async ( url: string, method: string, payload: any = undefined ) => { const headers = new Headers(); try { const options: RequestInit = { method: method, headers: headers, cache: "no-store", }; if (payload !== undefined) { options.body = JSON.stringify(payload); } const res = await fetch( process.env.NEXT_PUBLIC_INTERNAL_API_URL + url, options ); const data = await res.json(); if (!res.ok) { throw new Error(JSON.stringify(data)); } return data; } catch (e: any) { throw e; } }; export default localApiBuilder;
remoteApiBuilder.ts
import RemoteApiCustomError from "@/model/remoteApiResponse/RemoteApiCustomError"; import RemoteApiErrorType from "@/model/remoteApiResponse/RemoteApiErrorType"; import RemoteApiFailedResponse from "@/model/remoteApiResponse/RemoteApiFailedResponse"; import RemoteApiSuccessResponse from "@/model/remoteApiResponse/RemoteApiSuccessResponse"; const remoteApiBuilder = async <T>(url: string, body: T | undefined) => { const controller = new AbortController(); const timoutId = setTimeout(() => controller.abort(), 20000); const headers = new Headers(); headers.append("Content-Type", "application/json"); headers.append( "Apim-Subscription-Key", process.env.NEXT_PUBLIC_API_KEY as string ); const options: RequestInit = { method: body ? "post" : "get", headers: headers, signal: controller.signal, cache: "no-store", }; if (body) { options.body = typeof body === "string" ? body : JSON.stringify(body); } try { const response = await fetch( process.env.NEXT_PUBLIC_BACKEND_URL + url, options ); const data = await response.json(); console.log(data); if (!response.ok) { const errorResponse = data as RemoteApiFailedResponse; console.log("response is not ok"); console.log(errorResponse); throw new RemoteApiCustomError( RemoteApiErrorType.API_FAILED_ERROR, errorResponse.errorMessage ); } const possiblyFailedResponse = data as RemoteApiSuccessResponse; if (possiblyFailedResponse.errorCode !== 0) { throw new RemoteApiCustomError( RemoteApiErrorType.NON_ZERO_ERROR, possiblyFailedResponse.errorMessage ); } console.log( "fetch success response", possiblyFailedResponse.response, url, options ); return possiblyFailedResponse.response; } catch (error: any) { if (error.name === "AbortError") { throw new RemoteApiCustomError( RemoteApiErrorType.TIME_OUT_ERROR, "Request timed out" ); } else if (error instanceof RemoteApiCustomError) { throw error; } else { throw new RemoteApiCustomError( RemoteApiErrorType.UNKNOWN_ERROR, JSON.stringify(error) ); } } finally { clearTimeout(timoutId); } }; export default remoteApiBuilder;
api/config/route.ts
import FNPConfig from "@/model/config/FNPConfig"; import { NextRequest, NextResponse } from "next/server"; import remoteApiBuilder from "@/apiBuilder/remote.api.builder/remote.api.builder"; import translateRemoteApiCustomError from "@/utils/translateRemoteApiCustomError/translateRemoteApiCustomError"; import performRequestBasicCheck from "@/utils/performRequestBasicCheck/performRequestBasicCheck"; export async function GET(request: NextRequest) { performRequestBasicCheck(request, request.method, "GET"); try { const response: FNPConfig[] = (await remoteApiBuilder( "/RetrieveFNPConfig", undefined )) as FNPConfig[]; return NextResponse.json(response ? response[0] : [], { status: 200 }); } catch (e: any) { return translateRemoteApiCustomError(e); } }
页面调用函数
import { GET } from "@/constants/config"; import FNPConfig from "@/model/config/FNPConfig"; import localApiQueryBuilder from "@/apiBuilder/local.api.builder.ts/local.api.builder"; const localConfigApi = async (): Promise<FNPConfig> => { try { const response: FNPConfig = (await localApiQueryBuilder( "/config", GET )) as FNPConfig; return response ? response : new FNPConfig(); } catch (e: any) { throw e; } }; export default localConfigApi;
问题根源与解决方案
根源分析
在NextJS App Router中,API路由默认采用静态渲染缓存:即使你在fetch中添加了cache: 'no-store',如果API路由本身没有动态依赖(如request的查询参数、headers、cookies等),NextJS会默认将其标记为静态路由,缓存首次请求或构建时的响应结果。
你的/api/config/route.ts虽然接收了NextRequest参数,但并未使用任何动态属性,因此NextJS判定其为静态路由,直接返回缓存数据,不会重新执行内部的remoteApiBuilder调用。
解决方案
方案1:强制路由动态化(推荐)
在api/config/route.ts顶部添加以下代码,强制禁用路由缓存:
export const revalidate = 0; // 标记为动态路由,每次请求都重新执行
方案2:添加动态依赖触发动态路由
如果不想全局禁用缓存,可以在GET方法中读取任意动态属性(即使不用),触发NextJS将路由标记为动态:
export async function GET(request: NextRequest) { // 读取headers触发动态路由判定 request.headers.get('user-agent'); performRequestBasicCheck(request, request.method, "GET"); // ... 剩余逻辑不变 }
方案3:设置响应缓存控制头
在返回响应时显式添加Cache-Control头,明确禁止缓存:
return NextResponse.json(response ? response[0] : [], { status: 200, headers: { 'Cache-Control': 'no-store, max-age=0' } });
验证步骤
- 应用上述任意方案后,重启开发服务器(生产环境需重新构建)
- 修改微服务数据,调用NextJS API验证是否返回最新结果
- 查看服务端日志,确认每次请求都会执行
remoteApiBuilder中的console.log逻辑
内容的提问来源于stack exchange,提问作者Kyaw Soe Hein
相关产品推荐
相关产品推荐

