はじめに
Cursor(AI搭載IDE)を活用して、React製の不具合管理Webアプリを作成し、社内GitLab Pages上で公開、データはSharePointリストで共有する構成を実装してみました。
きっかけは、Excelによる不具合管理で発生しがちな、同時編集の競合やステータス管理のしづらさでした。
- Cursorの操作方法・プロンプトの書き方
- GitLab CI/CDとGitLab Pagesへのデプロイ設定
- SharePointリストの作成とREST API連携
- 企業環境で直面した課題(カスタムスクリプトのブロック、DNS未設定、CORSエラーなど)のトラブルシューティング
特に、社内環境で動かそうとすると想像以上に多くの制約に直面した点を、できるだけ正直に書いています。同じような状況で悩んでいる方の参考になれば幸いです。
この記事を読む前に:基本的な用語の整理
初めて聞く用語が多いと思うので、最初にざっくり整理しておきます。
| 用語 | 一言説明 |
|---|---|
| React | JavaScriptのUIライブラリ。Webアプリの画面を作るのに使う |
| TypeScript | JavaScriptに型チェックを追加した言語 |
| Vite | Reactアプリを素早くビルドするためのツール |
| Cursor | AIを内蔵したコードエディタ(VS Codeベース) |
| GitLab | ソースコードを管理するサービス(社内版GitHub) |
| GitLab Pages | GitLabが提供する静的ファイルのホスティング機能 |
| GitLab CI/CD | コードをpushしたら自動でビルド・デプロイしてくれる仕組み |
| GitLab Runner | CI/CDのジョブを実際に実行するプログラム |
| SharePointリスト | SharePointで使えるデータ管理機能(Webベースの表) |
| SharePoint REST API | プログラムからSharePointリストを操作するためのAPI |
| CORS | 異なるドメイン間のリクエストを制御するブラウザのセキュリティ機能 |
| 静的ファイル | HTML/CSS/JavaScriptのファイル群。サーバー側で処理せずそのまま配信できる |
前提条件
この記事の手順を試すにあたり、以下の環境・権限が必要です。
開発環境
| 必要なもの | バージョン | 確認方法 |
|---|---|---|
| Node.js | 18以上推奨 | node -v |
| npm | Node.jsに同梱 | npm -v |
| Git | 任意のバージョン | git --version |
| Cursor | 最新版 | cursor.com からインストール |
Node.jsのインストール(未インストールの場合):
nodejs.org から「LTS版」をダウンロードしてインストールしてください。インストール後、ターミナルで以下を実行して確認します。
node -v # v18.x.x などが表示されればOK
npm -v # 9.x.x などが表示されればOK
必要なアカウント・アクセス権限
| 必要なもの | 用途 | 備考 |
|---|---|---|
| 社内GitLabのアカウント | ソースコード管理・CI/CD・Pages | プロジェクトの作成権限が必要 |
| SharePointサイトへのアクセス | リストの作成・編集 | サイトメンバー以上の権限が必要 |
| GitLab管理者への依頼ルート | DNS/SSO設定 | 自分で設定できない場合は管理者に依頼が必要 |
この記事では対応しないこと
- SPFx(SharePoint Framework):別途SharePointアプリカタログへのアクセスと管理者権限が必要なため、この記事では扱いません
- Azure AD認証の実装:SharePoint REST APIへの認証は、ブラウザのセッションを利用する方式のみ扱います
- GitLab管理者向けの設定手順:DNS/SSO設定は管理者への依頼内容として記載していますが、管理者側の具体的な操作手順は対象外です
完成イメージ
行クリック → 編集モーダル(対応内容・コメント付き)
空行クリック → 新規登録モーダル
完了はグレーアウト
システム構成
[Cursor で開発] ← AIがコード生成を手伝ってくれる
│
│ git push(コードをGitLabに送る)
▼
[GitLab CI/CD] ← .gitlab-ci.yml に書いた手順を自動実行
1. npm install(ライブラリをインストール)
2. npm run build(ビルド → dist/ フォルダ生成)
3. dist/ を public/ にリネーム
│
│ デプロイ
▼
[GitLab Pages] ← HTML/JS/CSSを配信するWebサーバー(GitLab環境で利用できる静的ファイルのホスティング機能)
│
│ ブラウザでURL入力
▼
[ブラウザ] ← Node.jsのインストールは不要。URLを開くだけで利用できる
│
│ SharePoint REST API(データのCRUD)
▼
[SharePointリスト] ← データを保存・チーム全員で共有
・BugList(不具合一覧)
・WorkLogList(対応内容)
ポイント:「GitLab Pages」と「SharePointリスト」の役割分担
- GitLab Pages = アプリの「見た目(画面)」を配信するだけ。Spring Bootのようなサーバー側の処理はない
- SharePointリスト = データの保存・共有担当。今回の構成では簡易的なデータストアとして利用する。追加インフラ不要
Step 1:Cursorのセットアップと使い方
1-1. Cursorとは
CursorはVS Codeをベースに作られたAI搭載のコードエディタです。見た目や操作感はVS Codeとほぼ同じなので、VS Codeを使ったことがあればすぐ馴染めます。
普通のエディタとの最大の違いは、AIがプロジェクト全体のコードを理解した上で、自然言語の指示に従って複数ファイルにまたがるコードを自動生成・変更してくれる点です。
主な機能:
| 機能 | 起動方法 | 用途 |
|---|---|---|
| Agent モード |
Ctrl+L(Mac: Cmd+L) |
チャットで指示 → 複数ファイルのコードを自動生成・変更 |
| Tab補完 | コードを書いていると自動表示、Tabで確定 |
次の行をAIが予測して補完 |
| インライン編集 |
Ctrl+K(Mac: Cmd+K) |
選択した範囲だけピンポイントで修正 |
1-2. インストール
- cursor.com にアクセスしてダウンロード
- インストーラーを実行
- 起動するとGoogleまたはGitHubアカウントでのログインを求められる
- 14日間のPro試用が自動的に開始される
- VS Codeを使っていれば「Import VS Code Settings?」→「Yes」で設定が引き継がれる
1-3. プロジェクトの作成
Cursorのターミナルを開いて(Ctrl+`)、以下を順番に実行します。
# ① プロジェクト作成コマンド
# 「bug-tracker」という名前のフォルダが自動生成され、
# React + TypeScriptの雛形が中に作られる
npm create vite@latest bug-tracker -- --template react-ts
# ② 作成されたフォルダに移動
cd bug-tracker
# ③ 必要なライブラリをまとめてインストール
npm install
# ④ 開発サーバーを起動して動作確認
npm run dev
npm run dev 実行後、ブラウザで http://localhost:5173 を開くとViteの初期画面が表示されます。Cursor内蔵のブラウザは正常表示されないことがあるため、Chrome/Edgeで直接開いてください。
作成後のフォルダ構成:
bug-tracker/
├── src/
│ ├── App.tsx ← メインコンポーネント
│ ├── main.tsx ← エントリーポイント
│ └── ...
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts
File → Open Folder で bug-tracker フォルダを開くと、Cursor上でプロジェクトが表示されます。
1-4. Cursor Rules(AIへの事前ルール設定)
Cursor Rulesとは、このプロジェクトでAIに守ってほしいルールを事前に定義しておく設定ファイルです。
これを設定しておくと、毎回のプロンプトで「アロー関数で書いて」「any型は使わないで」などを説明する必要がなくなります。
設定手順:
-
Ctrl+Shift+Pでコマンドパレットを開く -
Cursor Settingsと入力して選択 - 左メニューから「Rules, Sklills, Subagents」を選択
- 「Rules」セクションで「+ New」をクリック
- ルール名(例:
coding-conventions)を入力 - 以下の内容を入力して保存
# Project rules
- このプロジェクトはReact 18 + TypeScript + Viteで構成されている
- 関数はすべてアロー関数で記述する(function宣言は使わない)
- コンポーネントは const Component = (): JSX.Element => { } 形式で書く
- React.FC は使用しない
- UIは日本語で表示する
- any型は使用禁止
- 要件書は bug-tracker-spec.md を参照すること
「Always applied」になっているので、以後のすべてのAI指示に自動で適用されます。
1-5. 要件書の準備
要件書(bug-tracker-spec.md)をプロジェクトルート直下に配置します。この要件書をAIに渡すことで、仕様に沿ったコードを生成してもらいます。
要件書に書くべき内容の例:
# 不具合管理アプリ 要件書
## データモデル
### Bug(不具合)
| フィールド名 | 型 | 説明 |
|-------------|-----|------|
| id | string | BUG-001形式の連番ID |
| title | string | 不具合タイトル |
| category | BugCategory | カテゴリ |
| status | BugStatus | ステータス |
| priority | Priority | 優先度 |
| assignee | string | 担当者名 |
| reporter | string | 登録者名 |
| description | string | 詳細説明 |
| createdAt | string | 登録日時 |
| updatedAt | string | 更新日時 |
### 型定義
type BugStatus = '未着手' | '対応中' | '完了';
type Priority = '高' | '中' | '低';
type BugCategory = '画面/UI' | 'API/バックエンド' | 'DB/データ' | '変更' | 'その他';
## 画面構成
### 一覧画面
- ステータスフィルタボタン(全件/未着手/対応中/完了)
- テーブル列:ID、カテゴリ、タイトル、ステータス、優先度、担当者、更新日
- 空行クリック → 新規登録モーダル
- データ行クリック → 編集モーダル
- 完了行はグレーアウト表示
## コーディング規約
- 関数はすべてアロー関数
- any型禁止
- CSSはCSS Modules使用
1-6. Agent モードでの実装手順
Cursorのチャット(Ctrl+L)を開きます。@ファイル名 と入力するとそのファイルをAIのコンテキスト(参照情報)に含めることができます。
プロンプト例①:型定義とモックデータの作成
@bug-tracker-spec.md のデータモデルセクションを参照して、
以下を作成してください:
1. src/types.ts — Bug, WorkLog, Comment の型定義
2. src/mockData.ts — 3件程度のサンプルデータ
コーディング規約に従ってアロー関数で記述してください。
プロンプト例②:コンポーネント群の作成
@bug-tracker-spec.md の画面構成セクションを参照して、
以下を順番に作成してください:
1. src/components/StatusBadge.tsx — ステータスバッジ
(未着手:グレー、対応中:青、完了:緑)
2. src/components/PriorityBadge.tsx — 優先度バッジ
(高:赤、中:黄、低:グレー)
3. src/components/BugCategoryBadge.tsx — カテゴリバッジ
4. src/components/BugRow.tsx — テーブルの1行
5. src/components/BugTable.tsx — 一覧テーブル(フィルタ付き)
6. App.tsx を更新して BugTable を表示
スタイルはCSS Modulesで作成してください。
プロンプト例③:モーダルの作成
@bug-tracker-spec.md を参照して、
以下を作成してください:
1. src/components/BugFormModal.tsx — 新規登録/編集共通モーダル
- オーバーレイ背景 + 中央配置
- フィールド:タイトル、カテゴリ、ステータス、優先度、担当者、説明
- 保存/キャンセル/削除ボタン
- Escキーで閉じる
2. App.tsx を更新して
- データ行クリック → 編集モーダル表示
- 空行クリック → 新規登録モーダル表示
AIが生成したコードの確認と採用:
エージェントがコードを生成すると、差分(diff)として表示されます。
- 緑色 = 新しく追加されるコード
- 赤色 = 削除されるコード
- Accept ボタン = そのファイルの変更を採用
- Accept All ボタン = すべてのファイルの変更を採用
- Reject = 却下してやり直し
修正したい箇所があればチャットで追加指示を出せます:
StatusBadgeのバッジの色をもう少し薄くしてください
⚠️ 注意:タイムアウトエラーへの対処
一度に多くの指示を出すとエージェントがタイムアウトすることがあります。その場合は:
- プロンプトを1〜2ファイルずつに分割する
-
npm run devを止めてからエージェントに指示する(リソース節約) - モデルを軽量なものに切り替える(チャット入力欄左下の「Auto」をクリック)
1-7. よく使うショートカット
| 操作 | Windows | Mac |
|---|---|---|
| AIチャットを開く | Ctrl+L |
Cmd+L |
| インライン編集 | Ctrl+K |
Cmd+K |
| ターミナルを開く | Ctrl+`` |
Ctrl+`` |
| Tab補完を確定 | Tab |
Tab |
| ファイル検索 | Ctrl+P |
Cmd+P |
| コマンドパレット | Ctrl+Shift+P |
Cmd+Shift+P |
Step 2:GitLab CI/CDとGitLab Pagesの設定
2-1. GitLab Pages・CI/CD・Runnerの関係を理解する
まず「何が何の役割を果たしているか」を整理します。
GitLab Pages とは
GitLab が提供する静的ファイルのホスティング機能です。「静的ファイル」とはHTML/CSS/JavaScriptのことで、これらを置いておくとGitLabが自動的にWebサイトとして公開してくれます。
Spring Bootとの違いで言うと:
- Spring Boot = サーバー側でJavaが動いてHTMLを生成して返す(動的)
- GitLab Pages = ファイルをそのままブラウザに渡すだけ(静的)
Reactアプリは最終的にHTML/JS/CSSの静的ファイルになるため、GitLab Pagesで配信できます。
GitLab CI/CD とは
コードを git push したときに、あらかじめ定義した処理(npm install → npm run build など)を自動で実行してくれる仕組みです。処理内容は .gitlab-ci.yml というファイルに書きます。
GitLab Runner とは
GitLab CI/CDは「何をするか」を管理する司令塔ですが、コマンドを実際に動かす実行係が別に必要です。それがGitLab Runnerです。
イメージとしては、こんな関係です:
-
GitLab(司令塔)=
git pushを検知して「ビルドしてデプロイして」と指示を出す -
GitLab Runner(実行係)= 指示を受け取って
npm installやnpm run buildを実際に実行する
GitLab.comのような公開サービスでは、MicrosoftやAWSのクラウド上にRunnerが用意されています。しかし社内GitLabでは自前でRunnerを用意する必要があります。
今回は自分のPC(Windows)にRunnerをインストールして、そのPCが実行係として動く構成にします。
① あなたが git push する
↓
② GitLab(司令塔)が .gitlab-ci.yml を読んで
「npm install して npm run build して!」とRunnerに指示を出す
↓
③ あなたのPC上のRunner(実行係)がコマンドを実際に実行する
→ npm install
→ npm run build(dist/ フォルダが生成される)
→ dist/ を public/ にリネーム
↓
④ GitLab Pagesに public/ フォルダがデプロイされる
↓
⑤ チームがURLでアクセスできる
ポイント: Runnerは
.\gitlab-runner.exe runを実行している間だけ動きます。PCを再起動したり、ターミナルを閉じるとRunnerが停止し、CI/CDが実行されなくなります。
2-2. vite.config.ts の設定
GitLab Pagesで正しく動くように、ビルド時のベースパスを設定します。
ベースパスとは、「このアプリはどのURLパスに配置されるか」という設定です。GitLab Pagesでは通常、プロジェクトのURLパスがそのままベースパスになります。
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
// GitLabのプロジェクトURLパスに合わせる
// 例:https://your-gitlab.com/groupA/subgroupB/bug-tracker の場合
base: '/groupA/subgroupB/bug-tracker/',
})
確認方法: GitLabのプロジェクトページを開いたときのURLが
https://your-gitlab.com/abc/def/bug-trackerなら、base: '/abc/def/bug-tracker/'です。
2-3. .gitlab-ci.yml の作成
プロジェクトルートに .gitlab-ci.yml を新規作成します。このファイルがCI/CDの設計図です。
# .gitlab-ci.yml
# 注意:Shell Executor(Windowsの自PCをRunnerとして使う場合)の設定
pages:
stage: deploy
script:
# ライブラリのインストール
- npm install
# ビルド(dist/ フォルダが生成される)
- npm run build
# GitLab PagesはArtifactsの「public/」フォルダをデプロイする仕組みなので
# Viteのビルド出力「dist/」をリネームする
- if (Test-Path public) { Remove-Item -Recurse -Force public }
- Rename-Item dist public
artifacts:
paths:
- public # このフォルダがGitLab Pagesとして公開される
only:
- main # mainブランチへのpush時のみ実行
なぜ
dist/をpublic/にリネームするか?
GitLab PagesはArtifacts(ジョブの成果物)の中のpublic/というフォルダを自動的にWebサイトとして公開する仕様になっています。Viteのビルド出力はdist/なので、リネームが必要です。
2-4. GitLab Runnerのセットアップ(詳細手順)
① GitLab上でRunnerを作成してトークンを取得
- GitLabのプロジェクトページを開く
- 左メニューの「Settings」→「CI/CD」をクリック
- 「Runners」セクションを展開
- 「Create project runner」ボタンをクリック
- 以下を設定:
- Platform: Windows
- 「Run untagged jobs」にチェックを入れる(タグなしのジョブも実行するという設定)
- 「Create runner」をクリック
- 次の画面に
glrt-で始まるトークン が表示される → コピーして保存
⚠️ 重要:トークンの種類を間違えない
glrt-で始まる → Runner登録トークン(今回使うもの)glpat-で始まる → Personal Access Token(APIアクセス用・Runner登録には使えない)
トークンは一度しか表示されないので、必ずコピーして保存してください。
② GitLab Runner をPCにダウンロード
PowerShellまたはコマンドプロンプトで実行します:
# インストール先フォルダを作成
mkdir C:\GitLab-Runner
cd C:\GitLab-Runner
# GitLab Runnerの実行ファイルをダウンロード
curl -o gitlab-runner.exe "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe"
SSL証明書エラーが出る場合(社内ネットワーク向け)
以下のエラーが出る場合:
CRYPT_E_REVOCATION_OFFLINE - 失効サーバーがオフラインのため以下のオプションを追加してください:
curl -k --ssl-no-revoke -o gitlab-runner.exe "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe"
③ Runnerを登録
# SSL証明書の検証問題がある場合の対処(社内ネットワーク向け)
$env:GIT_SSL_NO_VERIFY = "true"
# 登録コマンド(①で取得したglrt-から始まるトークンを使う)
.\gitlab-runner.exe register `
--url https://your-gitlab.com `
--token glrt-xxxxxxxxxx `
--tls-ca-file=""
対話形式で以下を入力します:
Enter the GitLab instance URL:
[https://your-gitlab.com]: ← そのままEnter(デフォルト値が入っている)
Enter a description for the runner:
[hostname]: bug-tracker-runner ← わかりやすい名前を入力
Enter an executor:
shell ← 「shell」と入力
「Verifying runner... is valid」と表示されれば登録成功です。
エラーが出た場合:
is not valid→ トークンが間違っている。glrt-で始まるトークンを確認is removed→glpat-のトークンを使ってしまっている。glrt-のトークンで再実行
④ Runnerを起動
.\gitlab-runner.exe run
以下のようなログが流れれば待機状態です:
Starting multi-runner from C:\GitLab-Runner\config.toml...
Configuration loaded
Listening for jobs...
⚠️ このターミナルは閉じないようにしてください。 閉じるとRunnerが停止してCI/CDが実行されなくなります。
PCを再起動したら再度.\gitlab-runner.exe runを実行してください。
2-5. git push でデプロイ
Cursorの別のターミナルを開いて(ターミナルタブの「+」ボタン)、以下を実行します:
cd C:\path\to\bug-tracker
# Gitの初期化(初回のみ)
git init
# GitLabのリポジトリを登録
git remote add origin https://your-gitlab.com/グループ名/プロジェクト名.git
# ファイルをすべてステージング
git add .
# コミット
git commit -m "initial commit"
# ブランチ名をmainに設定
git branch -M main
# pushする
git push -u origin main
「rejected - fetch first」エラーが出た場合:
GitLabでプロジェクト作成時にREADMEが自動生成されていると、履歴が食い違ってエラーになります。git pull origin main --allow-unrelated-histories git push -u origin main
pushが成功したら、GitLabのプロジェクトページで「CI/CD」→「Pipelines」を確認します。
| 表示 | 意味 |
|---|---|
| 🔵 running | ビルド実行中(数分かかります) |
| ✅ passed | デプロイ成功 |
| ❌ failed | エラーあり(ジョブをクリックしてログを確認) |
成功したら「Deploy」→「Pages」でアプリのURLを確認してアクセスします。
Step 3:SharePointリストの作成(詳細手順)
3-1. SharePointリストとは
SharePointリストとは、チームで共有できるWebベースのデータ管理機能です。
Excelとの違いをイメージするとわかりやすいです:
| 比較項目 | Excel | SharePointリスト |
|---|---|---|
| 同時編集 | ファイルが壊れることがある | 行単位で排他制御、安全に共同編集できる |
| リアルタイム反映 | 保存するまで反映されない | 編集がリアルタイムで反映される |
| 更新履歴 | 手動管理 | 「誰がいつ何を変更したか」が自動記録 |
| プログラムからの操作 | VBAなど | REST APIで自由にCRUD操作できる |
| アクセス権限 | ファイル単位 | リスト・列・行単位で細かく設定可能 |
今回SharePointリストを使う理由:
- 追加のサーバーやDBが不要(SharePointに最初から含まれている)
- チーム全員がブラウザから直接アクセスできる
- REST APIでReactアプリから操作できる
- 小規模なチーム利用であれば、DBの代替に近い形で扱える
3-2. BugList(不具合一覧)の作成
① SharePointサイトでリストを作成
- SharePointサイトを開く(例:
https://your-org.sharepoint.com/sites/your-site) - 左メニューの「サイトコンテンツ」をクリック
- 「+ 新規」→「リスト」をクリック
- 「空白のリスト」を選択(または「リスト」アイコンをクリック)
- 名前に
BugListと入力 - 「作成」をクリック
Microsoft Listsからも作成できます:
https://lists.microsoft.comまたは Microsoft 365のアプリランチャーから「Lists」を開いて「+ 新規作成」→「リスト」でも同じものが作れます。
② 列を追加する
リストが作成されたら、テーブルの列を追加します。「+ 列の追加」をクリックして以下を順番に追加してください。
注意: 「Title」列は最初から存在しています。不具合タイトルとして使うので削除しないでください。
| 列名 | 種類の選び方 | 選択肢(種類が「選択肢」の場合) |
|---|---|---|
| Category | 「選択肢」を選択 | 画面/UI, API/バックエンド, DB/データ, 変更, その他 |
| Status | 「選択肢」を選択 | 未着手, 対応中, 完了 |
| Priority | 「選択肢」を選択 | 高, 中, 低 |
| Assignee | 「テキスト」(1行テキスト)を選択 | — |
| Reporter | 「テキスト」(1行テキスト)を選択 | — |
| Description | 「複数行テキスト」を選択 | — |
選択肢列の追加方法(例:Statusの場合):
- 「+ 列の追加」→「選択肢」を選択
- 名前に
Statusと入力 - 「選択肢の追加」欄に
未着手、対応中、完了を1行ずつ入力 - 「保存」をクリック
3-3. WorkLogList(対応内容)の作成
BugListと同じ手順で、リスト名 WorkLogList を作成し、以下の列を追加します:
| 列名 | 種類 | 用途 |
|---|---|---|
| Title(既存) | 1行テキスト | 対応内容のタイトル |
| BugId | テキスト(1行) | 紐づく不具合のID(例:BUG-001) |
| Author | テキスト(1行) | 対応内容の記入者名 |
| Responder | テキスト(1行) | 実際に対応を行った担当者名 |
| LogDate | テキスト(1行) | 対応日時(文字列で管理) |
| Content | 複数行テキスト | 対応内容の詳細 |
| LogCategory | 選択肢 | 調査, 修正, テスト, レビュー, その他 |
3-4. SharePointリストのAPIエンドポイント確認
リストを作成したら、APIエンドポイントをブラウザで直接確認できます。
https://your-org.sharepoint.com/sites/your-site/_api/web/lists/getbytitle('BugList')/items
このURLをブラウザで開くと、JSONまたはXMLでデータが返ってきます。レスポンスが確認できれば、APIが使える状態です。
Step 4:SharePoint REST API連携の実装
4-1. SharePoint REST APIの特徴と注意点
SharePoint REST APIには、通常のREST APIと異なる独自の仕様がいくつかあります。事前に把握しておくと実装がスムーズです。
① 認証はブラウザのセッションを自動使用
SharePointに既にログインしているブラウザからリクエストする場合、追加の認証処理は不要です。credentials: 'include' をfetchに追加するだけで、SharePointのログインセッションが自動的に使われます。
② POST/PATCH/DELETE前に X-RequestDigest トークンが必要(CSRF対策)
SharePoint REST APIはCSRF対策として、データを変更するリクエスト(POST/PATCH/DELETE)に X-RequestDigest というトークンの付与を要求します。このトークンは事前に /_api/contextinfo エンドポイントから取得する必要があります。
③ 更新(PATCH)と削除(DELETE)はHTTPメソッドが特殊
通常のREST APIと違い、SharePoint REST APIでは以下のようになっています:
| 操作 | 通常のREST API | SharePoint REST API |
|---|---|---|
| 一覧取得 | GET | GET(同じ) |
| 登録 | POST | POST(同じ) |
| 更新 | PUT/PATCH | POSTで送り、ヘッダーに X-HTTP-Method: MERGE を付ける |
| 削除 | DELETE | POSTで送り、ヘッダーに X-HTTP-Method: DELETE と IF-MATCH: * を付ける |
④ __metadata の付与が必要
登録・更新時のリクエストボディに __metadata: { type: 'SP.Data.リスト名ListItem' } を含める必要があります。リスト名が BugList なら SP.Data.BugListListItem です。
4-2. APIユーティリティ関数の実装
// src/api/sharepoint.ts
const SITE_URL = 'https://your-org.sharepoint.com/sites/your-site';
// ─────────────────────────────────────────────
// X-RequestDigest トークンの取得
// POST/PATCH/DELETE を実行する前に必ず呼び出す
// ─────────────────────────────────────────────
const getRequestDigest = async (): Promise<string> => {
const res = await fetch(`${SITE_URL}/_api/contextinfo`, {
method: 'POST',
headers: {
'Accept': 'application/json;odata=verbose',
},
credentials: 'include',
});
if (!res.ok) throw new Error('RequestDigestの取得に失敗しました');
const data = await res.json();
return data.d.GetContextWebInformation.FormDigestValue;
};
// ─────────────────────────────────────────────
// BugList:不具合一覧の取得
// ─────────────────────────────────────────────
export const fetchBugs = async () => {
const res = await fetch(
// $orderby=Id desc で新しい順に取得
`${SITE_URL}/_api/web/lists/getbytitle('BugList')/items?$orderby=Id desc`,
{
headers: {
'Accept': 'application/json;odata=verbose',
},
credentials: 'include',
}
);
if (!res.ok) throw new Error(`不具合一覧の取得に失敗: ${res.status}`);
const data = await res.json();
return data.d.results; // アイテムの配列
};
// ─────────────────────────────────────────────
// BugList:不具合の登録
// ─────────────────────────────────────────────
export const createBug = async (bug: {
title: string;
category: string;
status: string;
priority: string;
assignee: string;
reporter: string;
description: string;
}) => {
const digest = await getRequestDigest(); // ← 事前にトークン取得
const res = await fetch(
`${SITE_URL}/_api/web/lists/getbytitle('BugList')/items`,
{
method: 'POST',
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json;odata=verbose',
'X-RequestDigest': digest, // ← 取得したトークンをヘッダーに付与
},
credentials: 'include',
body: JSON.stringify({
// __metadata は SharePoint REST API で必須
__metadata: { type: 'SP.Data.BugListListItem' },
Title: bug.title, // SharePointの列名はPascalCase
Category: bug.category,
Status: bug.status,
Priority: bug.priority,
Assignee: bug.assignee,
Reporter: bug.reporter,
Description: bug.description,
}),
}
);
if (!res.ok) throw new Error(`不具合の登録に失敗: ${res.status}`);
return await res.json();
};
// ─────────────────────────────────────────────
// BugList:不具合の更新
// ※ HTTPメソッドはPOST、ヘッダーでMERGEを指定する
// ─────────────────────────────────────────────
export const updateBug = async (
spId: number, // SharePointのアイテムID(数値)
bug: {
title?: string;
category?: string;
status?: string;
priority?: string;
assignee?: string;
description?: string;
}
) => {
const digest = await getRequestDigest();
const res = await fetch(
// URLの末尾に (spId) を付けて対象アイテムを指定
`${SITE_URL}/_api/web/lists/getbytitle('BugList')/items(${spId})`,
{
method: 'POST',
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json;odata=verbose',
'X-RequestDigest': digest,
'X-HTTP-Method': 'MERGE', // ← 更新はMERGEを指定
'IF-MATCH': '*', // ← 競合チェックなしで強制更新
},
credentials: 'include',
body: JSON.stringify({
__metadata: { type: 'SP.Data.BugListListItem' },
...(bug.title && { Title: bug.title }),
...(bug.category && { Category: bug.category }),
...(bug.status && { Status: bug.status }),
...(bug.priority && { Priority: bug.priority }),
...(bug.assignee !== undefined && { Assignee: bug.assignee }),
...(bug.description !== undefined && { Description: bug.description }),
}),
}
);
if (!res.ok) throw new Error(`不具合の更新に失敗: ${res.status}`);
// 更新成功時はレスポンスボディが空(204 No Content)
};
// ─────────────────────────────────────────────
// BugList:不具合の削除
// ※ HTTPメソッドはPOST、ヘッダーでDELETEを指定する
// ─────────────────────────────────────────────
export const deleteBug = async (spId: number) => {
const digest = await getRequestDigest();
const res = await fetch(
`${SITE_URL}/_api/web/lists/getbytitle('BugList')/items(${spId})`,
{
method: 'POST',
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json;odata=verbose',
'X-RequestDigest': digest,
'X-HTTP-Method': 'DELETE', // ← 削除はDELETEを指定
'IF-MATCH': '*',
},
credentials: 'include',
}
);
if (!res.ok) throw new Error(`不具合の削除に失敗: ${res.status}`);
};
4-3. SharePoint列名とReact型のマッピング
SharePointのレスポンスはPascalCaseの列名(Title, Statusなど)で返ってきます。Reactアプリ内ではcamelCase(title, status)を使うため、変換関数が必要です。
// SharePoint APIのレスポンス → React内の型に変換
const convertFromSharePoint = (item: any): Bug => ({
// アプリ表示用のID(BUG-001形式)
id: `BUG-${String(item.Id).padStart(3, '0')}`,
// SharePointのアイテムID(更新・削除時に使う数値ID)
spId: item.Id,
title: item.Title ?? '',
category: (item.Category ?? 'その他') as BugCategory,
status: (item.Status ?? '未着手') as BugStatus,
priority: (item.Priority ?? '中') as Priority,
assignee: item.Assignee ?? '',
reporter: item.Reporter ?? '',
description: item.Description ?? '',
createdAt: item.Created,
updatedAt: item.Modified,
workLogs: [],
comments: [],
});
4-4. useBugsカスタムHookへの組み込み
データアクセスをカスタムHookに集約しておくと、Phase 1(モックデータ)からPhase 2(SharePoint API)への切り替えがHookの中身を差し替えるだけで済みます。
// src/hooks/useBugs.ts
import { useState, useEffect } from 'react';
import { fetchBugs, createBug, updateBug, deleteBug } from '../api/sharepoint';
export const useBugs = () => {
const [bugs, setBugs] = useState<Bug[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// 初回表示時にSharePointから一覧を取得
useEffect(() => {
fetchBugs()
.then(items => setBugs(items.map(convertFromSharePoint)))
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, []);
// 不具合の登録
const addBug = async (bug: Omit<Bug, 'id' | 'spId'>) => {
try {
await createBug(bug);
// 登録後に一覧を再取得して画面を更新
const items = await fetchBugs();
setBugs(items.map(convertFromSharePoint));
} catch (err: any) {
alert(`登録エラー: ${err.message}`);
}
};
// 不具合の更新
const editBug = async (spId: number, bug: Partial<Bug>) => {
try {
await updateBug(spId, bug);
const items = await fetchBugs();
setBugs(items.map(convertFromSharePoint));
} catch (err: any) {
alert(`更新エラー: ${err.message}`);
}
};
// 不具合の削除
const removeBug = async (spId: number) => {
try {
await deleteBug(spId);
setBugs(prev => prev.filter(b => b.spId !== spId));
} catch (err: any) {
alert(`削除エラー: ${err.message}`);
}
};
return { bugs, loading, error, addBug, editBug, removeBug };
};
企業環境で直面した課題とトラブルシューティング
Reactアプリを社内チームに公開しようとして、4つの大きな制約にぶつかりました。同じ状況の方の参考になるよう、各課題の原因・確認方法・解決策を詳しく記録します。
| 試みた方法 | 結果 | 原因 |
|---|---|---|
| SharePointドキュメントライブラリにHTMLを配置 | JSブロック | カスタムスクリプト無効(テナント設定) |
| GitLab Pages | DNS/SSO未設定でアクセス不可 | 管理者によるDNSとOAuth設定が必要 |
| ローカルファイル直接起動 | スクリプト実行エラー |
file:// プロトコルの制限 |
| SharePointページへの埋め込みWebパーツ | スクリプト不可 | 埋め込みWebパーツが <script> タグを除去 |
すべての課題の根本原因:企業のセキュリティポリシーによるカスタムJavaScript実行の制限
課題① SharePointドキュメントライブラリ:カスタムスクリプトのブロック
npm run build で生成した dist/ フォルダの中身をSharePointのドキュメントライブラリにアップロードし、直接URLでアクセスすると画面が真っ白になります。
原因:
企業テナントのSharePoint Onlineは、デフォルトでカスタムスクリプト(JavaScript)の実行をブロックしています。これはセキュリティポリシーの一環で、テナント管理者が設定しています。
カスタムスクリプトがブロックされているか確認する方法:
以下の内容で test.html を作成してドキュメントライブラリにアップロードし、直接URLでアクセスします:
<!DOCTYPE html>
<html>
<body>
<h1>Static part</h1>
<div id="test"></div>
<script>
document.getElementById('test').innerText = 'JavaScript is working!';
</script>
</body>
</html>
| 表示結果 | 意味 |
|---|---|
| 「Static part」と「JavaScript is working!」が両方表示 | JavaScriptは動作している |
| 「Static part」だけ表示 | カスタムスクリプトがブロックされている |
| 何も表示されない | HTMLの表示自体がブロックされている |
解決策:
- SharePoint管理者にカスタムスクリプトの有効化を依頼する
- または、GitLab Pagesでホスティングする(JavaScriptをブロックしない)
課題② GitLab Pages:DNS/SSO設定
GitLab Pagesのデプロイ自体が成功(パイプラインが緑)しても、チームがURLにアクセスできない状態になることがあります。
GitLab PagesのURLの仕組み:
GitLab PagesはGitLab本体とは別のドメインでWebサイトを配信します。
GitLab本体: https://your-gitlab.com
GitLab Pages: https://project-name-xxxxx.your-pages-domain.com
↑ 別のドメイン
この別ドメインに対して、以下の2つの設定が管理者側で必要です。
原因1 ワイルドカードDNSレコードが未設定
*.your-pages-domain.com → GitLabサーバーのIPアドレス、というDNSレコードが存在しないため、ドメインが解決できずに ERR_NAME_NOT_RESOLVED エラーになります。
GitLabサーバーのIPアドレスを確認する方法:
# コマンドプロンプトまたはPowerShellで実行
nslookup your-gitlab.com
# 出力例:
# 名前: your-gitlab.com
# Address: 10.12.0.50 ← このIPアドレスを管理者に伝える
管理者への依頼内容(DNS設定):
GitLab PagesのDNS設定をお願いします。
種類:Aレコード(ワイルドカード)
ホスト名:*.your-pages-domain.com
IPアドレス:10.12.0.50(GitLabサーバーのIPアドレス)
自分のPCだけで動作確認する方法(hosts ファイルを書き換える):
管理者に依頼する前に、自分のPCだけでDNS設定を仮に当てて動作確認できます。
- メモ帳を「管理者として実行」で開く
-
C:\Windows\System32\drivers\etc\hostsを開く - 一番下に以下を追加して保存:
10.12.0.50 your-project-name.your-pages-domain.com
- ブラウザで
https://your-project-name.your-pages-domain.comにアクセス
原因2 SSO/OAuthのリダイレクトURIが未設定
DNS設定が完了してもSSO認証でエラーになる場合があります。
GitLab PagesはGitLab本体とは別ドメインで動作するため、企業SSOの「許可するリダイレクトURI」にPagesのドメインが含まれていないと認証が通りません。
ブラウザのコンソールや画面に以下のようなエラーが表示されます:
redirect_uri does not match
The redirect URI included is not valid.
管理者への依頼内容(SSO設定):
GitLab Pages用のOAuth設定をお願いします。
GitLabのOAuthアプリケーションの redirect_uri に
以下のドメインを追加してください:
*.your-pages-domain.com
課題③ ローカルファイル直接起動:スクリプト実行エラー
dist/index.html をダブルクリックでブラウザに表示しようとすると、画面が真っ白になります。
原因:
Viteのビルド出力は <script type="module"> を使っています。type="module" のスクリプトは、ブラウザのセキュリティポリシーにより file:// プロトコルでは動作しません。
ブラウザのコンソール(F12)には以下のエラーが表示されます:
Access to script at 'file:///C:/...index.js' from origin 'null'
has been blocked by CORS policy: Cross origin requests are only
supported for protocol schemes: http, https, ...
なぜ file:// だとダメなのか:
file:// で開いたHTMLはoriginが null になります。type="module" のスクリプトはoriginが null の場合、セキュリティ上の理由でブロックされます。
解決策:
Webサーバー経由でアクセスすることで解決します:
# 開発環境での確認
npm run dev
# → http://localhost:5173 でアクセス可能
# ビルド済みファイルを簡易Webサーバーで配信
npx serve dist
# → http://localhost:3000 でアクセス可能
http://localhost:... からアクセスすることでoriginが確定し、CORSエラーが解消されます。
課題④ SharePointページの埋め込みWebパーツ:スクリプトのストリップ
SharePointのモダンページで「埋め込み」Webパーツを使ってHTMLを貼り付けると、以下のメッセージが表示されます:
「スクリプト タグはサポートされていません。埋め込みコードはiframeベースである必要があります。」
原因:
SharePointのモダンページは、XSS(クロスサイトスクリプティング)攻撃を防ぐために、埋め込みWebパーツで <script> タグを自動的に除去します。URLを指定するiframe形式のみ受け付けます。
iframeでの回避の試みと限界:
GitLab PagesのURLをiframeに指定する方法も考えられますが、以下の問題があります:
- GitLab PagesのDNS/SSO設定が必要(同じ壁に当たる)
- SharePointがiframeのクリックジャッキング対策として外部URLのiframeをブロックする場合がある
結論として、スクリプトを含むHTMLをSharePointページに直接埋め込む方法は使えません。
管理者への依頼文テンプレート
GitLab Pagesを使うために必要な依頼事項をまとめます。
【件名】GitLab PagesのDNS・SSO設定依頼
お世話になっております。
社内GitLabのPages機能を使って、チームで使う社内Webアプリを
公開したいと考えております。
以下2点の設定をお願いできますでしょうか。
■ 1. DNS設定(Aレコードの追加)
ホスト名:*.your-pages-domain.com(ワイルドカード)
IPアドレス:10.x.x.x(GitLabサーバーのIPアドレス)
※上記IPアドレスはnslookup your-gitlab.com で確認した値です
■ 2. SSO設定(redirect_URIの追加)
GitLab PagesのOAuthアプリケーションのredirect_uriに
*.your-pages-domain.com を追加
【背景・目的】
不具合管理を脱Excelしてチーム全員がブラウザから利用できる
Webアプリとして整備したいと考えております。
【対象プロジェクト】
https://your-gitlab.com/グループ名/プロジェクト名
ご確認のほど、よろしくお願いいたします。
完成後の構成まとめ
管理者設定が完了した場合の最終的な構成です:
開発者
↓ コードを修正
↓ git push(これだけ)
GitLab CI/CD
↓ Runner(自分のPC)が npm install → npm run build を自動実行
↓ デプロイ
GitLab Pages(https://project.your-domain.com/bug-tracker/)
↓ ブラウザでURLを開くだけ(Node.js不要、インストール不要)
チーム全員
↕ SharePoint REST API(読み書き)
SharePointリスト(BugList / WorkLogList)
↑ チーム全員が同じデータを読み書き → リアルタイム共有
Cursorを使った開発の振り返り
よかった点
要件書を渡すだけでコードの骨格ができる
@要件書.md を読んで型定義を作って のような指示だけで、型定義・モックデータ・コンポーネント・カスタムHookを一気に生成してくれます。フロントエンドにほとんど触れたことのないエンジニアでも、数時間で動くReactアプリの骨格を作ることができました。
エラーメッセージをそのまま貼れば直してくれる
TypeScriptのコンパイルエラーやビルドエラーをチャットに貼り付けると、原因を特定して修正してくれます。
注意点
生成コードは必ずレビューする
AIが生成するコードにはロジックエラーや不要な any 型の使用が含まれることがあります。差分(diff)を必ず確認してから採用してください。
一度に大きすぎる指示はタイムアウトする
多くのファイルを一度に生成させようとするとエージェントがタイムアウトします。1〜2ファイルずつ、ステップを小さく分割して指示するのが安定します。
まとめ
| 項目 | 内容 |
|---|---|
| 開発コスト | Cursorを使うことで、要件書をもとに短時間でUIの骨格を作成できる |
| インフラコスト | 追加のアプリケーションサーバーやDBを用意しなくてよい |
| 運用コスト | git pushを起点に自動デプロイできる |
| 前提条件 | GitLab管理者によるDNS設定とSSO設定が必要 |
| データ共有 | SharePointリスト経由でチーム内のデータ共有ができる |
| チーム利用 | URLを開くだけで利用でき、各端末へのNode.js導入は不要 |
企業環境での公開は、想像以上にセキュリティポリシーの影響を受けます。一方で、管理者側の設定が整えば、追加サーバーを極力増やさずに社内向けWebアプリを運用できる構成として十分実用的だと感じました。
同じような課題にぶつかっている方の参考になれば幸いです。

