マキモノ
Google Workspace無料✅ 公式検証済みv1.0.0 / 更新

GASウェブアプリが「読み込み中」で止まる・遅い — google.script.run の null 化と getValues 往復を計測で潰す

戻り値に Date が混ざると google.script.run が全体を null にする問題を JSON 文字列化で解消し、シート1枚ずつの getValues(1枚0.3〜1秒)を Sheets API batchGet に置き換えて約5秒→1秒にする。切り替え前に本番データで新旧を突き合わせ、日時シリアルの1ms誤差を丸める手順つき

出品者: seisaku-team@orgiast.jp📖 読込 約2,329トークン (約4円)💰 コスパ 45倍
トークン節約メーター88%節約
ゼロからAIに作らせた場合約12万トークン
このMDを読ませた場合約1.5万トークン

約10.5万トークンの節約 (API料金換算で約160円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。

この巻物について

「GASウェブアプリが「読み込み中」で止まる・遅い — google.script.run の null 化と getValues 往復を計測で潰す」は、Google WorkspaceカテゴリのAI指示書(MDファイル)です。戻り値に Date が混ざると google.script.run が全体を null にする問題を JSON 文字列化で解消し、シート1枚ずつの getValues(1枚0.3〜1秒)を Sheets API batchGet に置き換えて約5秒→1秒にする。切り替え前に本番データで新旧を突き合わせ、日時シリアルの1ms誤差を丸める手順つきこの巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約10.5万トークン(API料金換算で約160円)・88%のトークンを節約できます。

カテゴリ
Google Workspace
対応AI
claude-code、cursor、codex-cli
ライセンス
商用利用可 (再販不可)
価格
無料
ゼロから開発時
約12万トークン
この巻物使用時
約1.5万トークン
節約量
約10.5万トークン (約160円)
更新日
2026-10-05

使い方 (AIに渡す3つの方法)

いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。

⬇ .md をダウンロード
claude "https://makimono-md.vercel.app/api/v1/files/gas-google-script-run-null-getvalues/raw を読み込んで、この指示書どおりに実装して"
claude-codecursorcodex-cliライセンス: 商用利用可 (再販不可)

中身

GAS ウェブアプリが「読み込み中」で止まる・遅いときの切り分けと修正

Google Apps Script(GAS)のウェブアプリ(HtmlService + google.script.run)で、「画面が重い」「読み込み中のまま」と報告されたときに、推測ではなく計測で原因を特定して直す手順。2つの原因が重なりやすいので、両方を順に潰す。

0. まず「遅い」のか「止まっている」のかを分ける

  • 1分待っても「読み込み中」のままなら、遅いのではなく止まっている。原因1を先に疑う。
  • いずれ表示されるが数秒かかるなら、原因2。

画面側で切り分けられるように、最初に次を入れておく:

// 初回表示前に失敗したら、消えるトーストではなく画面にエラー文を残す
function reportError(where, e) {
  const message = String((e && (e.message || e)) || "不明なエラー");
  if (!state) document.querySelector("#app").textContent = "読み込みに失敗しました: " + message;
}
window.addEventListener("error", (ev) => reportError("window.error", ev.error || ev.message));
window.addEventListener("unhandledrejection", (ev) => reportError("unhandledrejection", ev.reason));
// google.script.run の成功ハンドラ内で描画が例外を投げたときも reportError を呼ぶ

注意: 利用者の画面からスクリプトプロパティへエラーを書かせる方法は、ウェブアプリを「アクセスしているユーザー」として実行していると記録できないことがある(編集権限のない利用者)。画面にエラー文を残す方法を必ず併用する。

原因1: 戻り値に Date が1つでもあると、全体が null で届く

  • google.script.run は、サーバ関数の戻り値のどこかに Date が含まれていると、エラーにせずに戻り値全体を null にして成功ハンドラへ渡す。
  • 症状: 画面に Cannot read properties of null (reading '<最初に読むキー>') が出るか、何も出ずに「読み込み中」のまま。
  • 罠: シートの行(getValues() の結果)をそのまま返すと、日時列が Date になって混ざる。特定の条件(例: 期限切れの一覧に行が入ったとき)のときだけその行が戻り値に入るので、ある日を境に突然壊れる。サーバ側で JSON.stringify を通す経路(トリガーやテスト)では再現しない。

確定のさせ方(推測しない)

戻り値の中で Date の場所を列挙する関数を用意し、エディタ実行かトリガー経由で実行する:

function findUnsafeValues() {
  var hits = [];
  (function walk(v, path) {
    if (hits.length >= 20) return;
    if (v instanceof Date) { hits.push(path); return; }
    if (v && typeof v === "object") Object.keys(v).forEach(function (k) { walk(v[k], path + "." + k); });
  })(getBootstrap(), "bootstrap");
  return hits;
}

修正

画面へ大きなオブジェクトを返す関数は、JSON 文字列で返す。どこに Date が混ざっても、ISO 形式の文字列になって届く。

function getBootstrapJson() { return JSON.stringify(getBootstrap()); }
google.script.run.withSuccessHandler((json) => {
  if (!json) throw new Error("サーバから画面データが届きませんでした(応答が空)");
  state = JSON.parse(json);
  render();
}).getBootstrapJson();

原因2: シートを1枚ずつ getValues すると、往復だけで秒単位かかる

  • getDataRange().getValues() は1回ごとにサーバと往復するので、空のシートでも1枚 0.3〜1秒かかる。10枚読めば約5秒。データ量はほとんど関係ない。

計測(シートごとの時間を出す)

function profileReads() {
  var out = {}, t0 = Date.now();
  SHEET_KEYS.forEach(function (k) { var t = Date.now(); modelRead(k); out[k] = Date.now() - t; });
  out.total = Date.now() - t0;
  return out;
}

修正: Sheets API の batchGet で1回にまとめる

  1. appsscript.json に拡張サービスを追加する: "dependencies": {"enabledAdvancedServices": [{"userSymbol": "Sheets", "serviceId": "sheets", "version": "v4"}]}
  2. 全シートを1回で読み、getValues() と同じ形に揃える:
function modelReadMany(keys) {
  try {
    var res = Sheets.Spreadsheets.Values.batchGet(SpreadsheetApp.getActiveSpreadsheet().getId(), {
      ranges: keys.map(function (k) { return "'" + SHEET_NAME[k].replace(/'/g, "''") + "'"; }),
      valueRenderOption: "UNFORMATTED_VALUE", dateTimeRenderOption: "SERIAL_NUMBER" });
    var out = {};
    keys.forEach(function (k, i) {
      var width = HEADER[k].length, rows = (res.valueRanges[i] && res.valueRanges[i].values) || [];
      out[k] = rows.slice(1).map(function (src) {
        var row = src.slice(0, width); while (row.length < width) row.push("");   // 末尾の空セルは省略されて返る
        row.forEach(function (v, c) {
          if (IS_DATETIME_COLUMN(k, c) && typeof v === "number")
            // シリアル0 = 1899-12-30 00:00(シートのTZ)。浮動小数の誤差で1ms手前に落ちるので必ず丸める
            row[c] = new Date(Date.UTC(1899, 11, 29, 24 - TZ_OFFSET_HOURS) + Math.round(v * 86400000));
        });
        return row;
      }).filter(function (r) { return r.some(function (v) { return v !== ""; }); });
    });
    return out;
  } catch (e) {
    var fb = {}; keys.forEach(function (k) { fb[k] = modelRead(k); }); return fb;   // 失敗したら従来経路
  }
}

切り替える前に、本番データで新旧を突き合わせる(必須)

型の違い(日付が通し番号で返る、など)で静かに壊れるのを防ぐため、シートごとに比較し、不一致があれば行・列・両方の値を出す:

keys.filter(function (k) { return JSON.stringify(many[k]) !== JSON.stringify(modelRead(k)); });

実例: 不一致は、日時列が 03:45:03.999Z と 03:45:04.000Z の1msずれだけだった。Math.round(v * 86400000) で解消。不一致が0件になってから切り替える。

結果の目安

  • 画面データの読み込み: 約5.1秒 → 約1.1秒(シートの読み込みは5,120ms → 約390ms)
  • 「読み込み中」で止まる問題: JSON 文字列で渡すことで解消

よくある質問

+「GASウェブアプリが「読み込み中」で止まる・遅い — google.script.run の null 化と getValues 往復を計測で潰す」とは何ですか?

戻り値に Date が混ざると google.script.run が全体を null にする問題を JSON 文字列化で解消し、シート1枚ずつの getValues(1枚0.3〜1秒)を Sheets API batchGet に置き換えて約5秒→1秒にする。切り替え前に本番データで新旧を突き合わせ、日時シリアルの1ms誤差を丸める手順つき

+どれくらいトークン(費用)を節約できますか?

ゼロから開発すると約12万トークンかかりますが、この巻物を使えば約1.5万トークンで済みます。差し引き約10.5万トークン(API料金換算で約160円)・88%の節約です。

+どうやって使いますか?

無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。

+どのAIツールに対応していますか?

claude-code、cursor、codex-cli に対応しています。

+商用利用できますか?

ライセンスは「商用利用可 (再販不可)」です。

🤝 自分でAIを動かすのは、まだ不安…という方へ

この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。

AI代行堂を見る →

関連する巻物

この巻物、誰かのトークンも救えます

𝕏 で節約レシートをシェア