You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 14服务端API读取credentials.json失败,求解决方案

解决Next.js 14中Google Calendar认证读取credentials.json的问题

核心问题原因

Next.js的API路由在webpack打包环境下,process.cwd()的指向逻辑和普通Node.js项目不同,且@google-cloud/local-auth的authenticate方法会尝试通过模块解析加载文件,而非直接读取文件系统,导致原本的路径配置失效。此外,直接将凭证文件放在根目录也可能因Next.js的文件处理规则被忽略。

正确的文件放置与代码调整方案

1. 文件放置位置

将credentials.json放在项目根目录,同时务必把它添加到.gitignore中,避免泄露敏感的OAuth凭证信息。

2. 代码调整要点

方式一:修正文件路径解析逻辑

在Next.js的服务文件中,放弃依赖process.cwd(),改用import.meta.url配合path.resolve获取准确的文件路径:

import { fileURLToPath } from 'url';
import path from 'path';
import process from 'process';
import { authenticate } from '@google-cloud/local-auth';
import { google } from 'googleapis';

const SCOPES = ['https://www.googleapis.com/auth/calendar'];

// 基于当前文件路径解析根目录下的凭证文件
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 根据你的服务文件路径(app/api/_services/calendarService.ts)调整层级,确保回到根目录
const CREDENTIALS_PATH = path.resolve(__dirname, '../../../../credentials.json');
const TOKEN_PATH = path.resolve(__dirname, '../../../../token.json');

export class CalendarService {
  // 其余方法保持不变,仅路径替换为上面的定义
  private async loadSavedCredentialsIfExist() {
    try {
      const content = await fs.readFile(TOKEN_PATH);
      const credentials = JSON.parse(content);
      return google.auth.fromJSON(credentials);
    } catch (err) {
      return null;
    }
  }

  private async saveCredentials(client: any) {
    const content = await fs.readFile(CREDENTIALS_PATH);
    const keys = JSON.parse(content);
    const key = keys.installed || keys.web;
    const payload = JSON.stringify({
      type: 'authorized_user',
      client_id: key.client_id,
      client_secret: key.client_secret,
      refresh_token: client.credentials.refresh_token,
    });
    await fs.writeFile(TOKEN_PATH, payload);
  }

  private async authorize() {
    let client: any = await this.loadSavedCredentialsIfExist();
    if (client) {
      return client;
    }
    client = await authenticate({
      scopes: SCOPES,
      keyfilePath: CREDENTIALS_PATH,
    });
    if (client?.credentials) {
      await this.saveCredentials(client);
    }
    return client;
  }

  public makeEvent() {
    this.authorize().then(async (auth) => {
      // await this.insertEvent(auth)
    }).catch(console.error)
  }
}

方式二:改用环境变量存储凭证(更推荐)

直接将凭证内容存入环境变量,彻底规避文件读取问题:

  • 在项目根目录创建.env.local文件,添加:
GOOGLE_CLIENT_ID=你的OAuth客户端ID
GOOGLE_CLIENT_SECRET=你的OAuth客户端密钥
GOOGLE_REFRESH_TOKEN=已获取的刷新令牌(如有)
  • 修改认证逻辑,直接从环境变量构建OAuth客户端:
import { google } from 'googleapis';

const SCOPES = ['https://www.googleapis.com/auth/calendar'];

export class CalendarService {
  private async loadSavedCredentialsIfExist() {
    // 可保留从token.json读取的逻辑,或直接从环境变量获取refresh_token
    try {
      const content = await fs.readFile(TOKEN_PATH);
      const credentials = JSON.parse(content);
      return google.auth.fromJSON(credentials);
    } catch (err) {
      return null;
    }
  }

  private async authorize() {
    let client = await this.loadSavedCredentialsIfExist();
    if (client) {
      return client;
    }

    // 直接用环境变量创建OAuth2客户端
    const oAuth2Client = new google.auth.OAuth2(
      process.env.GOOGLE_CLIENT_ID,
      process.env.GOOGLE_CLIENT_SECRET,
      'http://localhost:3000/api/callback' // 替换为你的授权回调地址
    );

    // 若已存在refresh_token,直接设置
    if (process.env.GOOGLE_REFRESH_TOKEN) {
      oAuth2Client.setCredentials({
        refresh_token: process.env.GOOGLE_REFRESH_TOKEN
      });
      await this.saveCredentials(oAuth2Client);
      return oAuth2Client;
    }

    // 无refresh_token时生成授权链接,需前端跳转完成授权
    const authUrl = oAuth2Client.generateAuthUrl({
      access_type: 'offline',
      scope: SCOPES,
    });
    throw new Error(`请访问此链接完成授权: ${authUrl}`);
  }

  // 其余方法按需调整
}

3. 额外注意事项

  • .env.local必须添加到.gitignore,防止敏感信息泄露。
  • 服务端使用的环境变量无需加NEXT_PUBLIC_前缀,直接通过process.env.XXX读取即可。
  • 不要将凭证文件放在.next目录,该目录是构建产物目录,会被自动覆盖。

内容的提问来源于stack exchange,提问作者Oleksandr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 07:47:50