📝

AIに教えたいGASウェブアプリ開発のポイント

に公開

GASウェブアプリでできること

Google Apps Script(GAS)は、軽量スクリプトの自動化ツールとして語られることが多いのですが、Webアプリを構築できることも知られてきたのではないかと思います。
Google Workspace(GWS)の各機能と組み合わせると、意外なほど本格的な Web アプリを構築できます。

  • フロントは JavaScript / HTML / CSS で自由に作れる
  • バックエンドは GAS(サーバー側 JS)で動く
  • Google Drive・スプレッドシート・メールなど、GWS API がシームレスに使える
  • デプロイすれば、組織内ユーザーに追加費用なしで提供できる
  • Clasp を使えば Git 管理も可能、TypeScript にも対応
  • ChatGPT / Claude / Gemini などの AI を活用したコーディングが可能

特に「社内向けの非コア業務用アプリ」を作る上では、GAS は “ちょうど良いプラットフォーム” になり得ます。
起動の遅さや実行時間制限など、コア業務で使うには制約がありますが、遅延が許されるような社内アプリとしては十分に実用レベルです。


GASでここまでできる ― SPA構成の社内アプリ

実際に GAS では、次のような「そこそこ本格的なSPA」まで作れます。

  • Vue 3(Global Build)+ Tailwind CSS の UI
  • Toast UI Editor による Markdown ベースの記事編集
  • Drive やスプレッドシート、スクリプトプロパティを使った簡易データストア
  • UrlFetchApp による外部API連携

これまで作ってきて実務で使っているものの例としては次のようなものがあります。

  • FAQサイト(スプレッドシートのFAQを読み込んで複数キーワードやカテゴリに対応)
  • AIチャットボット(LLMは外部モデルをAPIで使用、FAQデータを読み込み)
  • 検索サーバー(全文+ベクトル検索、ベクトル化は外部モデルをAPIで使用)
  • 上記のアプリを組み合わせてRAGチャットボット構築

また、現在構築中のものとして、社内Zennのようなナレッジ共有のアプリがあります。

  • ブログシステム(SPA:Vue3+Tailwind CSS+Toast UI Editor)

ただ、次のような制限もあり、工夫して対応しています。

  • GASがコールドスタンバイなのでいちいち遅い
    • →フロント側でのキャッシュや非同期処理でカバー
  • AIチャットボットで利用したいストリーミングが利用できない
    • →疑似ストリーミング出力でそれっぽく表示
  • Node.js 環境のように NPM や Vite を直接使えない。
    • UMD 形式のライブラリを中心に選べば、SPA 的なアプリは十分構築できる

GASで開発を加速させる:Clasp と Git と AI

GAS開発はGoogleのウェブ画面のエディタから行うことができ、シンプルなスクリプトであればこれで十分です。

もう少し複雑なシステムを構築しようとする場合は、Clasp(Command Line Apps Script)を使用することで状況が一変します。clasp clone, clasp pull, clasp push といったコマンドで、Googleとローカル間のコードを連携させられます。

  • ローカルで VSCode を使って編集できる
  • Git でバージョン管理でき、GitHub とも連携できる
  • TypeScript も使える
  • AI (ChatGPT / Claude / Gemini) でのコード生成がスムーズになる

しかし、2025年11月現在、AI の基盤モデル(GPT5.1, Gemini3, ClaudeSonnet4.5)のGAS に関する学習量は例えばJavaScript やTypeScript に比べて少ないので、こうした言語に引っ張られて、GASとしては間違いのコードを出力することが多く発生します。そこで、正しい前提をAIに食べさせる必要があります。

たとえば:

  • GAS は fetch/CORS に対応していない
  • HTMLService は ES Modules を使えない
  • ActiveUser が取得できる条件
  • Drive API の制約
  • SPA 構成の注意点 …など

これらは AI が頻繁に誤回答するポイントでもあり、エラー対応に追われることになります。


AIに食べさせたいGAS構築ルール

この記事は、単に GAS の技術解説をするためのものではありません。

“AI が誤回答しないよう、GAS+GWS 環境の正しい仕様をまとめること”
にあります。

あなたが日々遭遇しているように、GAS は通常のサーバー環境(Node.js や JavaScript そのもの)とは異なり、
癖の強い独自仕様があります。

そのため、AIに教える際には 「誤りやすいポイントだけを厳選してまとめた基準」 が必須です。

この記事の後半は、そうした “AIに教えるためのガイドライン” として整理しています。
主要な基盤モデルに取り込まれるまで・・・GASアプリ開発にAIを本格導入するための踏み台となるべくこの記事を書きました。少しずつメンテしていければと思います。

下記のガイドラインをAIに渡すことで、快適なGASウェブアプリ開発ライフを送っていただくことを祈っています。


# AI用ガイドライン(GAS+GWS環境の実践仕様)
GASでのウェブアプリ開発においては、GWS環境で実行できることを想定し、以下の点に注意して要件定義・設計・実装を行ってください。
---

# 1. 実行ユーザーとアクセスユーザー

## 実行ユーザー
- 「デプロイ時の設定」で決定  
  - **デプロイユーザーで実行**
  - **アクセスユーザーで実行**
- 実行者はコード側で変更不可。

## アクセスユーザーの取得
GWS環境のみ、以下でアクセスユーザーのメールアドレスを取得可能:

```js
Session.getActiveUser().getEmail();
```

取得できる:メールアドレス(GWS内ユーザー)  
取得できない:氏名・部署・アイコン(Directory APIが必要)  
個人アカウント(@gmail.com)は **常に空文字**---

# 2. GAS は CORS 非対応(fetch は原理的に不可)

- `Access-Control-Allow-Origin` を付けても **無効**
- HTMLService は iframe 内で実行 → 他GAS URLはすべて **クロスオリジン扱い**
- よって **GAS Webアプリへの fetch / XHR は原理的に使えない**
- GASへの通信方法:google.script.run

結論:**GASサーバー関数を呼ぶには google.script.run 一択。**

---

# 3. HTMLService の制約(ESM不可)

利用不可:
- `<script type="module">`
- ES Modules / import
- TipTap(ESM依存)

利用可能(UMD/CDN):
- **Vue 3(global build)**
- **Tailwind CSS**
- **Toast UI Editor**

SPA構成で利用可能な技術(CDN前提)としてVue3+Tailwind CSSがあげられる。

---

# 4. GASの同期モデルとフロント非同期

## GAS(サーバー)
- 完全同期実行  
- UrlFetchApp / DriveApp などはすべてブロッキング

唯一の並列化:
- **UrlFetchApp.fetchAll()**

## フロント(ブラウザ)
- 完全非同期(Promise / async 可能)
- ただし **GASへ fetch は不可(CORS問題)**

---

# 5. Drive・フォルダ操作の注意点

## Drive は append 不可
- 読み → 追記 → 上書き のみ

---

# 6. レスポンシブ対応(スマホ最適化)

## ビューポートは GAS 側で付与する必要がある
HTML側では無効。必ず evaluate() 後に付ける:

```js
evaluate().addMetaTag('viewport','width=device-width,initial-scale=1');
```

## モバイルUI基本方針
- サイドバーはモバイル幅で自動折りたたみ
- 開閉時にオーバーレイを表示
- 外側タップで自動クローズ
- isMobile() による動的UI制御

---

# 7. GAS → 別GASウェブアプリ を UrlFetchApp.fetch する場合の認証(GWS環境で必須)

AIが「認証なしで叩く」よう回答しやすいが **GWS環境では認証必須**なのでNG。

## 正しい呼び方(OAuth トークン付与)
```js
const token = ScriptApp.getOAuthToken();

UrlFetchApp.fetch(targetUrl, {
  method: 'post',
  headers: { Authorization: `Bearer ${token}` },
  contentType: 'application/json',
  payload: JSON.stringify({ ... }),
});
```

Discussion