Next.js 14中基于谷歌表格实现ISR(无fetch)的问题求助
问题:Next.js 14 实现基于Google Sheets的增量静态再生(ISR)
我需要给Web应用的schedule页面实现ISR,要求每3000秒重新从Google Spreadsheets拉取日程数据(用户填表后数据会同步到表格)。当前用Next.js 14(不支持getServerSideProps),之前在单独文件app/agendaData.js里获取数据再在page.tsx调用,试过export const revalidate = 3000和force_cache,但页面始终是静态构建的(因为Google API没用到fetch)。按照文档要求把数据逻辑移到了app/schedule/page.js里,当前代码如下,求帮忙搞定ISR:
const serviceAccountAuth = new JWT({ //google api credentials scopes: [ 'https://www.googleapis.com/auth/spreadsheets', ], }); const theData= unstable_cache(async () => { try { const doc = new GoogleSpreadsheet(process.env.GOOGLE_SHEET_ID, serviceAccountAuth); await doc.loadInfo() const sheet = doc.sheetsByIndex[0]; const rows = await sheet.getRows(); let rowData: any[] | PromiseLike<any[] | undefined> | undefined = []; rows.map((row, index = 0) => { let rowNum = row['_rowNumber']; let day = row['_rawData'].shift() let newObj = { 'id': rowNum, 'day': day, 'data': row['_rawData'] } rowData.push(newObj); }) return rowData; } catch (error) { console.log(error) await theData() } }, { revalidate: 60}) // @ts-ignore // @ts-ignore export default async function Schedule(){ let data; do { try { data = await theData() } catch (error){ console.log(error) }} while (data === undefined) let day1 = data.filter(item => item['day'] === '1'); // @ts-ignore let day2 = data.filter(item => item['day'] === '2'); // @ts-ignore let day3 = data.filter(item => item['day'] === '3') return HTML }
解决方案
核心问题分析
你的代码没触发ISR的原因有三个:一是unstable_cache缺少缓存键(Next.js需要它来管理缓存生命周期);二是错误处理里的无限递归可能导致缓存逻辑异常;三是页面级的revalidate配置未添加。
修改后的完整代码
import { JWT } from 'google-auth-library'; import { GoogleSpreadsheet } from 'google-spreadsheet'; const serviceAccountAuth = new JWT({ // 替换为你的Google服务账号凭证 email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL, key: process.env.GOOGLE_PRIVATE_KEY?.replace(/\\n/g, '\n'), scopes: ['https://www.googleapis.com/auth/spreadsheets'], }); // 配置带唯一缓存键的unstable_cache const fetchAgendaData = unstable_cache(async () => { try { const doc = new GoogleSpreadsheet(process.env.GOOGLE_SHEET_ID, serviceAccountAuth); await doc.loadInfo(); const sheet = doc.sheetsByIndex[0]; const rows = await sheet.getRows(); const rowData = []; // 用forEach替代map,避免不必要的数组生成 rows.forEach((row) => { const rowNum = row._rowNumber; const day = row._rawData.shift(); rowData.push({ id: rowNum, day, data: row._rawData, }); }); return rowData; } catch (error) { console.error('拉取Google Sheets数据失败:', error); // 返回空数组避免页面崩溃,不要递归调用自身 return []; } }, ['agenda-schedule-data'], { // 唯一缓存键,必须设置 revalidate: 3000, // 3000秒后缓存失效,和页面ISR时间保持一致 }); // 页面级ISR配置,告诉Next.js该页面需要定时重新生成 export const revalidate = 3000; export default async function Schedule() { const data = await fetchAgendaData(); const day1 = data.filter(item => item.day === '1'); const day2 = data.filter(item => item.day === '2'); const day3 = data.filter(item => item.day === '3'); // 替换成你的实际JSX渲染逻辑 return ( <div className="schedule-container"> <h1>日程表</h1> <section> <h2>第一天</h2> {day1.map(item => ( <div key={item.id} className="schedule-item"> {item.data.map((content, idx) => ( <p key={idx}>{content}</p> ))} </div> ))} </section> <section> <h2>第二天</h2> {day2.map(item => ( <div key={item.id} className="schedule-item"> {item.data.map((content, idx) => ( <p key={idx}>{content}</p> ))} </div> ))} </section> <section> <h2>第三天</h2> {day3.map(item => ( <div key={item.id} className="schedule-item"> {item.data.map((content, idx) => ( <p key={idx}>{content}</p> ))} </div> ))} </section> </div> ); }
关键修改点说明
- 添加缓存键:
['agenda-schedule-data']是Next.js识别缓存条目的唯一标识,没有它unstable_cache无法正确管理缓存的更新和失效。 - 页面级
revalidate:必须在页面组件顶部导出这个配置,和缓存函数的revalidate时间同步,确保页面静态内容和数据缓存一起每3000秒更新一次。 - 修复错误处理:移除原代码中catch块的
await theData()递归调用,改成返回空数组,避免无限循环导致的内存溢出或缓存异常。 - 优化数据处理:用
forEach替代map(因为不需要返回新数组),移除不必要的@ts-ignore和冗余类型定义,让代码更整洁。 - 环境变量处理:Google服务账号的私钥里的
\\n要替换成真实换行符\n,否则JWT认证会失败。
内容的提问来源于stack exchange,提问作者Ryan
相关产品推荐
相关产品推荐

