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

如何使用Next.js实现向Google Sheet写入数据的功能?

解决方案

第一步 调整权限配置

  • 替换Google API鉴权范围:原有spreadsheets.readonly是只读权限,写入需要修改为可写权限范围,推荐使用https://www.googleapis.com/auth/spreadsheets(全表读写权限)
  • 打开你的Google Sheet,点击右上角「共享」,将服务账号邮箱(即GOOGLE_SHEETS_CLIENT_EMAIL对应的值)的权限从「查看者」调整为「编辑者」

第二步 改造sheet.js代码

将公共鉴权逻辑抽离,新增写入功能函数,修改后完整代码如下:

import { google } from 'googleapis';

// 抽离公共鉴权逻辑,读写复用
async function getAuthSheets() {
  // 替换为可写权限范围
  const scopes = ['https://www.googleapis.com/auth/spreadsheets'];
  const jwt = new google.auth.JWT(
    process.env.GOOGLE_SHEETS_CLIENT_EMAIL,
    null,
    (process.env.GOOGLE_SHEETS_PRIVATE_KEY || '').replace(/\\n/g, '\n'),
    scopes
  );
  return google.sheets({ version: 'v4', auth: jwt });
}

// 原有读取方法,修改为复用鉴权逻辑
export async function getDataFromSheets() {
  try {
    const sheets = await getAuthSheets();
    const response = await sheets.spreadsheets.values.get({
      spreadsheetId: process.env.SPREADSHEET_ID,
      range: 'sheet'
    });

    const rows = response.data.values;
    if (rows.length) {
      return rows.map((row) => ({
        title: row[0],
        description: row[1],
      }));
    }
  } catch (err) {
    console.log(err);
  }
  return [];
}

// 新增写入方法:追加行到表格末尾
export async function appendDataToSheets(data) {
  // data格式为 [title, description]
  try {
    const sheets = await getAuthSheets();
    const response = await sheets.spreadsheets.values.append({
      spreadsheetId: process.env.SPREADSHEET_ID,
      range: 'sheet', // 写入的表格范围
      valueInputOption: 'RAW', // RAW为直接存储输入内容,USER_ENTERED会自动解析公式、日期等格式
      resource: {
        values: [data]
      }
    });
    return { success: true, data: response.data };
  } catch (err) {
    console.log(err);
    return { success: false, error: err.message };
  }
}

第三步 新增Next.js API路由处理写入请求

不要直接在客户端调用写入方法,会暴露敏感环境变量,需要在pages/api目录下新建write-sheet.js文件,代码如下:

import { appendDataToSheets } from '../../libs/sheets';

export default async function handler(req, res) {
  // 只允许POST请求
  if (req.method !== 'POST') {
    return res.status(405).json({ message: '仅支持POST请求' });
  }

  const { title, description } = req.body;
  // 简单参数校验
  if (!title || !description) {
    return res.status(400).json({ message: 'title和description不能为空' });
  }

  const result = await appendDataToSheets([title, description]);
  if (result.success) {
    return res.status(200).json(result);
  } else {
    return res.status(500).json(result);
  }
}

第四步 客户端添加写入触发逻辑

可以在index.js中加一个表单,提交时调用上面的API接口,示例代码如下:

// 在Home组件中新增表单逻辑
import { useState } from 'react';

export default function Home({ data }) {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await fetch('/api/write-sheet', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ title, description })
    });
    // 提交后清空输入框,需要即时更新列表可引入useRouter调用router.reload()
    setTitle('');
    setDescription('');
  };

  return (
    <div className={styles.container}>
      <Head>
        <title>Nextsheet 💩</title>
        <meta
          name="description"
          content="Connecting NextJS with Google Spreadsheets as Database"
        />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <h1>Welcome to Nextsheet 💩</h1>
        <p>Connecting NextJS with Google Spreadsheets as Database</p>
        <ul>
          {data && data.length ? (
            data.map((item, index) => (
              <li key={index}>
                {item.title} - {item.description}
              </li>
            ))
          ) : (
            <li>Error: do not forget to setup your env variables 👇</li>
          )}
        </ul>
        {/* 新增写入表单 */}
        <form onSubmit={handleSubmit} style={{ marginTop: '2rem' }}>
          <div>
            <input
              type="text"
              value={title}
              onChange={(e) => setTitle(e.target.value)}
              placeholder="输入标题"
              required
            />
          </div>
          <div style={{ margin: '1rem 0' }}>
            <input
              type="text"
              value={description}
              onChange={(e) => setDescription(e.target.value)}
              placeholder="输入描述"
              required
            />
          </div>
          <button type="submit">提交到Google Sheet</button>
        </form>
      </main>
    </div>
  )
}
// 原有getStaticProps逻辑保持不变
export async function getStaticProps(context) {
  const sheet = await getDataFromSheets();
  return {
    props: {
      data: sheet.slice(1, sheet.length), // 原注释是移除表头,所以索引应该从1开始而非0,这里顺带修正了原代码的小问题
    },
    revalidate: 1, // In seconds
  };
}

常见问题排查

  • 检查服务账号的编辑权限是否配置正确,不要设置为仅查看
  • 确认valueInputOption参数是否符合你的写入需求
  • 写入的range范围要和实际表格的结构匹配,避免写入到错误位置

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 23:06:05