GASウェブアプリが「読み込み中」で止まる・遅い — google.script.run の null 化と getValues 往復を計測で潰す
戻り値に Date が混ざると google.script.run が全体を null にする問題を JSON 文字列化で解消し、シート1枚ずつの getValues(1枚0.3〜1秒)を Sheets API batchGet に置き換えて約5秒→1秒にする。切り替え前に本番データで新旧を突き合わせ、日時シリアルの1ms誤差を丸める手順つき
約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 のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/gas-google-script-run-null-getvalues/raw を読み込んで、この指示書どおりに実装して"
中身
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回にまとめる
appsscript.jsonに拡張サービスを追加する:"dependencies": {"enabledAdvancedServices": [{"userSymbol": "Sheets", "serviceId": "sheets", "version": "v4"}]}- 全シートを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で頼むだけで、仕事が完成」。
関連する巻物
GAS完全自動化テンプレ — Driveコマンドキュー方式
Google Apps Script の「毎回エディタで▶実行」を根絶。Drive 経由のコマンドキューで、初回1クリック以降は AI がすべての GAS 関数をリモート実行できるようになるテンプレート指示書。
人間の手入力台帳を壊さずに自動更新する — GAS Web App upsert 設計
各PC/各拠点の点検結果を、人間が手運用しているスプレッドシート台帳へ自動反映する。手入力列とコメントを絶対に壊さない突合設計、タブ/列の解決、並行POST対策、配布シークレットの落とし穴まで。
数式まみれの業務スプレッドシートを、Webアプリから壊さずに編集させる型
ArrayFormula と per-row 数式が混在する台帳を、セル単位 allowlist・dry-run 既定・適用前バックアップ・触っていないセルの数式不変検査で安全に書き換える設計手順。列ごとの数式復元と、テストが緑のまま壊れる典型例つき。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア