如何在自建服务器的Next.js应用中每小时运行定时任务并共享环境变量?
解决方案:Next.js 集成定时任务同步 API 数据
方案一:使用 concurrently 并行启动服务与定时脚本
这是最简洁的实现方式,无需修改 Next.js 核心配置,直接通过并行命令启动两个进程。
步骤1:安装依赖
npm install concurrently node-schedule --save-dev
concurrently:用于同时运行多个命令node-schedule:支持 cron 表达式的灵活定时任务调度(比setInterval更适合复杂时间规则)
步骤2:编写定时同步脚本
在项目根目录创建scripts/fetch-app-info.js:
const fs = require('fs'); const path = require('path'); const schedule = require('node-schedule'); const fetch = require('node-fetch'); // 启动时先执行一次,确保初始化就有最新数据 fetchAppInfo(); // 每小时整点执行(cron表达式:0 * * * *) schedule.scheduleJob('0 * * * *', fetchAppInfo); async function fetchAppInfo() { try { // 从环境变量获取API地址 const apiUrl = process.env.NEXT_PUBLIC_APP_INFO_API; if (!apiUrl) throw new Error('未配置APP_INFO_API环境变量'); const res = await fetch(apiUrl); if (!res.ok) throw new Error(`API请求失败:${res.status}`); const data = await res.json(); // 将数据写入public目录,方便SSR读取 const outputPath = path.join(process.cwd(), 'public/app-info.json'); fs.writeFileSync(outputPath, JSON.stringify(data, null, 2)); console.log(`[${new Date().toISOString()}] 成功更新app-info数据`); } catch (err) { console.error(`[${new Date().toISOString()}] 更新app-info失败:`, err.message); } }
若项目未安装
node-fetch,需执行npm install node-fetch
步骤3:修改package.json启动命令
替换原有start和dev脚本:
{ "scripts": { "dev": "concurrently \"next dev\" \"node scripts/fetch-app-info.js\"", "build": "next build", "start": "concurrently \"next start\" \"node scripts/fetch-app-info.js\"" } }
现在执行npm start或npm run dev时,会同时启动 Next.js 服务和定时同步脚本。
方案二:集成到自定义Next.js服务器
如果希望更一体化,可将定时任务嵌入 Next.js 自定义服务器中,无需额外进程。
步骤1:创建自定义服务器文件
在项目根目录创建server.js:
const { createServer } = require('http'); const { parse } = require('url'); const next = require('next'); const schedule = require('node-schedule'); const fs = require('fs'); const path = require('path'); const fetch = require('node-fetch'); const dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler(); // 启动定时任务 fetchAppInfo(); schedule.scheduleJob('0 * * * *', fetchAppInfo); async function fetchAppInfo() { try { const apiUrl = process.env.NEXT_PUBLIC_APP_INFO_API; if (!apiUrl) throw new Error('未配置APP_INFO_API环境变量'); const res = await fetch(apiUrl); if (!res.ok) throw new Error(`API请求失败:${res.status}`); const data = await res.json(); const outputPath = path.join(process.cwd(), 'public/app-info.json'); fs.writeFileSync(outputPath, JSON.stringify(data, null, 2)); console.log(`[${new Date().toISOString()}] 成功更新app-info数据`); } catch (err) { console.error(`[${new Date().toISOString()}] 更新app-info失败:`, err.message); } } app.prepare().then(() => { createServer((req, res) => { const parsedUrl = parse(req.url, true); handle(req, res, parsedUrl); }).listen(process.env.PORT || 3000, (err) => { if (err) throw err; console.log(`> Ready on http://localhost:${process.env.PORT || 3000}`); }); });
步骤2:修改package.json启动命令
{ "scripts": { "dev": "node server.js", "build": "next build", "start": "NODE_ENV=production node server.js" } }
关键注意事项
- 环境变量访问:两种方案中,定时脚本都能直接读取 Next.js 的环境变量(同一shell环境下启动),
.env.local等配置文件的变量会自动加载。 - SSR读取数据:在页面的
getStaticProps或getServerSideProps中读取生成的JSON文件:import fs from 'fs'; import path from 'path'; export async function getStaticProps() { const filePath = path.join(process.cwd(), 'public/app-info.json'); const appInfo = JSON.parse(fs.readFileSync(filePath, 'utf8')); return { props: { appInfo }, revalidate: 0 // 无需静态刷新,依赖定时任务更新文件 }; } - 错误处理:定时任务必须添加
try-catch,避免单次API请求失败导致脚本崩溃,同时记录日志便于排查。 - 文件权限:确保Node.js进程有写入
public目录的权限,避免写入失败。
内容的提问来源于stack exchange,提问作者Ardalan
相关产品推荐
相关产品推荐

