TECH PLAY

サイオステクノロジー(Tech.Lab)

サイオステクノロジー(Tech.Lab) の技術ブログ

546

こんにちは! 今月も「OSSのサポートエンジニアが気になった!OSSの最新ニュース」をお届けします。 Linux 環境でよく使用される sudo において、影響度の高い脆弱性 (CVE-2025-32463) が報告されました。 Linux sudo chrootの脆弱性(CVE-2025-32463)により、非特権ユーザーがroot権限を取得可能に https://rocket-boys.co.jp/security-measures-lab/linux-sudo-chroot-cve-2025-32463-root-escalation/ 2025/7/3、Cloud Native Computing Foundation (CNCF) の 2024年アニュアルレポートの日本語版が公開されました。 CNCF アニュアル レポート 2024 日本語版を公開 https://prtimes.jp/main/html/rd/p/000000374.000042042.html 2025/7/16、Google が CVE-2025-6558 の脆弱性に対する Chrome の緊急アップデートをリリースしました。 Urgent: Google Releases Critical Chrome Update for CVE-2025-6558 Exploit Active in the Wild https://thehackernews.com/2025/07/urgent-google-releases-critical-chrome.html ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2025年7月】OSSサポートエンジニアが気になった!OSS最新ニュース first appeared on SIOS Tech. Lab .
アバター
挨拶 ども!お久しぶりです。Claudeを活用した開発手法の模索に真剣に取り組んでいる龍ちゃんです。 合間でStreamlitアプリの開発もちょこちょこやっているんですが、前回は「 DevContainerでStreamlit開発を始める方法:Docker+VSCode 」でStreamlitの開発コンテナ環境を構築しました。 開発環境は整ったものの、「いざ本番環境にデプロイしようとしたら、設定が開発環境のままで困った…」という経験、皆さんもありませんか?実際に私も最初の頃、開発用の設定のままAzureにデプロイして、エラー情報が丸見えになってしまったことがあります。 今回はそんな課題を解決するため、 本番環境用Dockerfileの構築手法 を共有していきたいと思います! 今回の内容 : 「Streamlit 本番環境用Dockerfileの作成」 セキュリティを考慮したコンテナ設計 開発環境と本番環境の適切な設定分離 プロダクション向けStreamlit最適化 事前準備 事前準備として、 前回のVSCode環境で、コンテナを用いてStreamlit環境を構築した記事 の内容をベースに進めていきます。 まだ環境が整っていない方は、その記事を参考に環境を構築するか、 サンプルリポジトリ を参照してください。 前提条件 : Docker Desktop がインストール済み 基本的なStreamlitアプリが作成済み VSCodeでの開発経験(推奨) 今回の内容はStreamlitのDockerビルドに関するものですが、環境が完全に一致していなくても動作します。使える部分だけを取り入れていただければ問題ありません。 今回の目標 今回の目標は、 本番環境用のDockerfileを作成し、実際にローカルでビルド・動作確認すること です。 また、Streamlit用のプロダクション環境向け設定ファイル(config.prod.toml)の作成も目指しています。 具体的な手順 : 本番環境用Dockerfileの作成 Streamlit設定ファイル(config.prod.toml)の最適化 Dockerコマンドを用いた動作確認 最終的な成果物 : セキュリティを考慮した本番環境用Dockerfile プロダクション最適化されたStreamlit設定 即座にデプロイ可能なDockerイメージ 今回作成するディレクトリ構造は以下のようになります: . ├── .devcontainer │ ├── Dockerfile │ ├── compose.yml │ └── devcontainer.json ├── .gitignore ├── .streamlit │ └── config.prod.toml # 本番環境用Streamlit設定ファイル ├── Dockerfile # 本番環境用Dockerfile ├── README.md ├── requirements.txt ├── setup.cfg └── src ├── chat.py ├── demo.py └── main.py 開発環境と本番環境の設定戦略 ここで重要なのが、 開発環境と本番環境の設定を明確に分離する ことです。実際のプロジェクトでどちらを選ぶべきか、迷うところですよね。 開発環境の特徴 開発環境では、特別な設定を行っていませんでした。これはStreamlitのデフォルト設定に開発者向けの便利機能がすでに組み込まれているからです: エラーメッセージの詳細表示 : デバッグに便利 ファイル変更時の自動リロード : ホットリロード機能 開発者ツールの表示 : パフォーマンス情報など 詳細なデバッグ情報 : トラブルシューティングに有用 本番環境での課題 一方、本番環境ではこれらの機能は不要どころか、 ユーザー体験を損なう要因 になります: エラーの詳細情報がエンドユーザーに表示される(セキュリティリスク) 不要な開発者ツールが表示されて混乱を招く そもそもソースコードが変更されることがないのでリロード機能は不要 リソース使用量が最適化されていない 設定分離のメリット そこで、本番環境専用の設定ファイル(config.prod.toml)を作成することで: セキュリティ向上 : 不要な情報の非表示化 パフォーマンス最適化 : 開発機能の無効化 ユーザー体験向上 : クリーンなインターフェース 運用効率化 : 環境に応じた適切な設定 また、VSCode開発時に必要な情報(.vscodeディレクトリなど)や開発時にのみ必要な情報を本番環境のビルドに含めないことで、 より軽量なDockerイメージ を実現できます。 開発環境では、DevContainerで開発するために専用イメージを使用していましたが、これには多くの設定が含まれた重いイメージになっています。本番環境ではこれらを削ぎ落として、極力小さなベースイメージを使用したいという意図があります。 環境分離のメリットとしては、 イメージの軽量化 や 不要情報の削除によるセキュリティリスクの回避 が挙げられます。 本番環境用Dockerfileの詳細解説 それでは、本番環境用Dockerfileについて詳しく見ていきます。 ベースイメージの選択に関しては、こちらの「 Dockerイメージの選び方ガイド 」が参考になります。 Dockerfileのベースとしては、 公式リファレンスの「Deploy Streamlit using Docker」 を参照しています。 # 本番環境用 - Python 3.11 slim イメージ FROM python:3.11-slim # 非rootユーザーでの実行(セキュリティ対策) RUN groupadd -r appuser && useradd -r -g appuser appuser # 作業ディレクトリの設定 WORKDIR /app # システムの更新と必要最小限のパッケージをインストール RUN apt-get update \ && apt-get install -y --no-install-recommends \ curl \ ca-certificates \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Pythonの設定(パフォーマンス最適化) ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 # ヘルスチェック HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8501/_stcore/health || exit 1 # 依存関係ファイルをコピー COPY requirements.txt . # Pythonパッケージのインストール RUN pip install --no-cache-dir -r requirements.txt # アプリケーションコードをコピー COPY ./src . # Streamlit設定ファイルを作成 RUN mkdir -p .streamlit COPY .streamlit/config.prod.toml .streamlit/config.toml # ファイルの所有権を変更(セキュリティ対策) RUN chown -R appuser:appuser /app # 非rootユーザーに切り替え USER appuser # ポート公開 EXPOSE 8501 # Streamlitアプリの起動 CMD ["streamlit", "run", "main.py"] ベースイメージの選択理由 こちらが本番環境のDockerfileになります。ベースイメージとしては、 Python 3.11のslimイメージ を使用し、開発環境と同じバージョンに揃えています。 slimイメージを選択した理由: 必要最小限のパッケージのみ含有(セキュリティリスク軽減) イメージサイズが小さい(デプロイ時間短縮) 本番環境に不要な開発ツールが含まれていない セキュリティ対策のポイント セキュリティのために 非rootユーザーでの実行 を行うためにユーザーを作成し、システムの更新と必要最小限のパッケージのインストールも実施しています。 パフォーマンス最適化の工夫 Pythonアプリケーションを動かすための手順としては、まず依存ファイルをコピーして必要なパッケージをインストールし、その後、srcディレクトリ内のアプリケーションコードとStreamlitの設定ファイルをコピーしています。 設定ファイルの適切な管理 特に重要な点は Streamlitの設定ファイル(config.toml)の作成部分 です。.streamlitディレクトリを作成し、ホスト側にある.streamlitディレクトリ内の config.prod.toml ファイルを、コンテナ内では単に config.toml としてコピーしています。 これにより、設定項目が本番環境用のconfigとして正しく認識され、プロダクション用の設定を適切に管理できます。また、config.prod.tomlというファイル名にすることで、開発環境では読み込まれないという利点もあります。 公開ポートとしては8501を指定し、最終的にStreamlitを起動するコマンドを実行しています。 Stremlit HealthCheckについて 公式のこちらのページ でDockerのコンテナが起動しているかをHealth Checkを行っています。これはStreamlitを確実に動作させるために必要なので追加しておきます。 ヘルスチェックにより、コンテナオーケストレーション(Kubernetes、Docker Swarm等)が自動的に異常なコンテナを検知・再起動できるようになります 本番環境用Streamlit設定(config.prod.toml) このファイルは 本番環境用のStreamlit設定ファイル です。詳細な内容については、 Streamlit公式のドキュメント を参照してください。 ファイル内には一通り必要な設定項目にコメントを付けていますので、それぞれの設定が必要かどうか判断していただければと思います。 実装としては以下のようになります: # Streamlit 本番環境用設定ファイル # 各設定項目の詳細説明付き [server] # ブラウザを自動で開かない(サーバー環境での実行に適している) # デフォルト: false headless = true # Streamlitアプリが待ち受けるポート番号 # デフォルト: 8501 port = 8501 # サーバーがリッスンするIPアドレス # "0.0.0.0" で全てのネットワークインターフェースからの接続を許可 # デフォルト: (未設定) address = "0.0.0.0" # 静的ファイル配信機能を無効化(セキュリティ向上) # staticディレクトリからのファイル配信を禁止 # デフォルト: false enableStaticServing = false # ファイル変更時の自動再実行を無効化(本番環境では不要) # 開発環境でのみ有効にすべき機能 # デフォルト: false runOnSave = false [browser] # Streamlitへの使用統計送信を無効化(プライバシー保護) # 本番環境では通常無効化する # デフォルト: true gatherUsageStats = false # ブラウザが接続すべきサーバーアドレス # コンテナ環境では "0.0.0.0" を指定 # デフォルト: "localhost" serverAddress = "0.0.0.0" # ブラウザが接続すべきポート番号 # server.portと同じ値を設定 # デフォルト: server.portの値 serverPort = 8501 [global] # 開発モードを無効化(本番環境用の設定) # 開発者向け機能や詳細なエラー表示を制限 # デフォルト: false developmentMode = false # 直接実行時の警告表示を無効化 # "python script.py" での実行時の警告を非表示 # デフォルト: true showWarningOnDirectExecution = false [client] # エラー詳細の表示レベル設定 # "full": 完全なエラー情報を表示(本番環境でも詳細確認のため有効) # 他の選択肢: "stacktrace", "type", "none" # デフォルト: "full" showErrorDetails = "full" # ツールバーの表示モード設定 # "viewer": 開発者オプションを非表示(本番環境に適している) # 他の選択肢: "auto", "developer", "minimal" # デフォルト: "auto" toolbarMode = "viewer" # マルチページアプリでのサイドバーナビゲーション表示 # pages/ディレクトリベースのナビゲーションを有効化 # デフォルト: true showSidebarNavigation = true [ui] # Streamlitのトップバーを非表示 # より洗練されたUIを実現(ブランディング向上) # カスタム設定項目 hideTopBar = true [theme] # ダークテーマを適用 # "light" または "dark" から選択可能 # ユーザー体験向上のためのテーマ設定 base = "dark" セキュリティの機能(CORSやXSRF)などの保護機能を無効化にすることもできますが、ここはデプロイ先の構成によって検討するべき内容です。十分に注意して設定項目を決定してください。 Streamlit公式フォーラムでも言及されています 。 動作確認 それでは、実際に作成したDockerfileとconfig.prod.tomlを使って動作確認を行ってみます。 Dockerイメージのビルド まずはDockerイメージをビルドしてみましょう: # Dockerイメージのビルド docker build -t python-app:test . コンテナの起動と確認 次に、ビルドしたイメージを使ってコンテナを起動します: # コンテナの実行 # DevContainerで8501を使用している可能性を考慮して8502に接続 docker run -d -p 8502:8501 --name python-app-test python-app:test # ログ確認 docker logs python-app-test # ブラウザでアクセス # http://localhost:8502 クリーンアップ 動作確認が完了したら、コンテナを停止・削除します: # コンテナの停止と削除 docker stop python-app-test docker rm python-app-test # 不要なイメージの削除(必要に応じて) docker rmi python-app:test よくあるエラーと対処法 ポートが既に使用されている場合 # エラー: port is already allocated # 対処法: 別のポートを使用 docker run -d -p 8503:8501 --name python-app-test python-app:test 設定ファイルが見つからない場合 # .streamlit/config.prod.toml の存在確認 ls -la .streamlit/ # ファイルパスの確認 COPY .streamlit/config.prod.toml .streamlit/config.toml 権限エラーが発生する場合 # ファイル所有権の確認 docker exec -it python-app-test ls -la /app まとめ 今回は Streamlit本番環境用Dockerfileの構築 について詳しく見てきました。 開発環境と本番環境の設定を適切に分離することで、セキュリティとパフォーマンスの両方を向上させることができました。特に config.prod.toml による設定最適化と、 非rootユーザー でのコンテナ実行は、実際のプロジェクトでも重要なポイントになります。 今回作成したDockerfileは、Azure Container InstancesやAWS Fargate等のクラウドサービスにそのままデプロイできる構成になっています。 次回は、 GitHub Actions + Bicep を使った 完全自動化デプロイパイプライン の構築に挑戦します!今回のDockerfileがそのまま活用できるので、ぜひお楽しみに! それでは、次回の記事でまたお会いしましょう! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Streamlit本番環境Docker構築ガイド|セキュリティ設定分離完全対応 first appeared on SIOS Tech. Lab .
アバター
PS SLの佐々木です。 今回はClaude Codeを使っていてToDoリストがどのように管理されているのかやタスク完遂まで具体的にどのような作業が行われているのかを知りたく~/.claudeディレクトリを観察してみました。なので備忘録的な内容になっています。 ~/.claude ディレクトリの構造 Claude Codeを使い始めると、ユーザーのホームディレクトリに ~/.claude という隠しディレクトリが自動的に作成されます。このディレクトリには、Claude Codeの動作を支える重要なファイルが格納されています。本記事では、実際にNext.jsプロジェクトをセットアップする際の挙動を通して、これらのファイルの詳細な役割と仕組みを解説します。 ~/.claude/ ├── .credentials.json ├── projects/ ├── shell-snapshots/ ├── statsig/ └── todos/ 各ファイル・ディレクトリの役割 .credentials.json Claude CodeがAnthropic APIに接続するための認証情報を保存するファイルです。APIキーなどの重要な認証データが含まれています。 projects/ Claude Codeのセッション履歴を記録するディレクトリです。各プロジェクトでの作業内容が詳細なJSONログとして保存されます。 shell-snapshots/ コマンドライン作業の履歴やセッションのスナップショットを保存するディレクトリです。(多分) statsig/ 使用統計やアナリティクス関連のデータを保存するディレクトリです。(多分) todos/ Claude Codeの特徴的な機能であるTODO管理システムのデータが保存されるディレクトリです。 TODOシステムの詳細な動作 初期状態 Claude Codeを実行すると、 todos ディレクトリ内に空のJSONファイルが作成されます: $ ls ~/.claude/todos/ 567fef6f-1c7f-4b9e-883c-1f5e7478d125-agent-567fef6f-1c7f-4b9e-883c-1f5e7478d125.json $ cat ~/.claude/todos/567fef6f-1c7f-4b9e-883c-1f5e7478d125-agent-567fef6f-1c7f-4b9e-883c-1f5e7478d125.json [] ファイル名はセッションIDベースで生成され、初期状態では空の配列となっています。 TODOタスクの自動生成 ユーザーが複雑なタスクを依頼すると、Claude Codeは自動的にタスクを分解してTODOリストを作成します。 入力例: Next.jsのプロジェクトをセットアップしてください。 Next.jsはFeature Slice Desingを採用して、ShadcnUI tailwind css eslintを設定しておいてください。 Claude Codeの応答: ● Next.jsプロジェクトをセットアップします。Feature Slice Design、ShadcnUI、TailwindCSS、ESLintを設定します。 ● Update Todos ⎿ ☐ Next.jsプロジェクトを初期化 ☐ TailwindCSSを設定 ☐ ShadcnUIを設定 ☐ ESLintを設定 ☐ Feature Slice Designのディレクトリ構造を作成 この時点で、TODOファイルには以下のデータが保存されます: [ { "content": "Next.jsプロジェクトを初期化", "status": "pending", "priority": "high", "id": "1" }, { "content": "TailwindCSSを設定", "status": "pending", "priority": "high", "id": "2" }, { "content": "ShadcnUIを設定", "status": "pending", "priority": "high", "id": "3" }, { "content": "ESLintを設定", "status": "pending", "priority": "high", "id": "4" }, { "content": "Feature Slice Designのディレクトリ構造を作成", "status": "pending", "priority": "medium", "id": "5" } ] タスクステータスの動的更新 Claude Codeは作業の進行に合わせて、TODOのステータスを自動的に更新します: pending : 未着手 in_progress : 作業中 completed : 完了 作業完了後のTODOファイル: [ { "content": "Next.jsプロジェクトを初期化", "status": "completed", "priority": "high", "id": "1" }, { "content": "TailwindCSSを設定", "status": "completed", "priority": "high", "id": "2" }, { "content": "ShadcnUIを設定", "status": "completed", "priority": "high", "id": "3" }, { "content": "ESLintを設定", "status": "completed", "priority": "high", "id": "4" }, { "content": "Feature Slice Designのディレクトリ構造を作成", "status": "completed", "priority": "medium", "id": "5" } ] セッション履歴の詳細記録 projects/ ディレクトリには、Claude Codeでの全ての対話が詳細に記録されます。ファイル形式はNDJSON(改行区切りJSON)で、以下の情報が含まれます: ログエントリの構造 { "parentUuid": "親メッセージのID", "isSidechain": false, "userType": "external", "cwd": "/home/ka-sasaki/claude-product/my-next-app", "sessionId": "567fef6f-1c7f-4b9e-883c-1f5e7478d125", "version": "1.0.55", "gitBranch": "", "type": "assistant", "message": { "role": "assistant", "content": [ { "type": "tool_use", "name": "Bash", "input": { "command": "npx create-next-app@latest my-next-app", "description": "Next.jsプロジェクトを初期化" } } ] }, "uuid": "メッセージのユニークID", "timestamp": "2025-07-18T08:02:37.376Z" } 対話パターンの分析 Claude Codeの対話は必ず以下のパターンで進行します: Assistant : ツール実行の指示 User : ツール実行結果の報告 Assistant : 次のアクションの決定 User : 結果の報告… この交互パターンにより、Claude Codeは複雑なタスクを段階的かつ確実に実行できます。 実際の作業フローの例 ShadcnUI初期化の4ステップ: Bashコマンド実行 { "type": "assistant", "content": [{ "type": "tool_use", "name": "Bash", "input": { "command": "echo \\"\\" | npx shadcn@latest init", "description": "デフォルト設定でShadcnUIを初期化" } }] } コマンド実行結果の受信 { "type": "user", "content": [{ "tool_use_id": "...", "type": "tool_result", "content": "Success! Project initialization completed..." }] } TODOリストの更新指示 { "type": "assistant", "content": [{ "type": "tool_use", "name": "TodoWrite", "input": { "todos": [ {"id": "3", "content": "ShadcnUIを設定", "status": "completed", ...} ] } }] } TODO更新結果の確認 { "type": "user", "content": [{ "tool_use_id": "...", "type": "tool_result", "content": "Todos have been modified successfully..." }], "toolUseResult": { "oldTodos": [...], "newTodos": [...] } } まとめ Claude Codeの ~/.claude ディレクトリは、単なる設定ファイルの保存場所ではなく、高度なプロジェクト管理システムの中核を担っています。TODOシステムによる自動的なタスク分解と進捗管理、詳細なセッション履歴の記録により、開発者は複雑なタスクを効率的に進められます。 構造が理解できると作業内容を修正したり、意図しない挙動をしたときにデバックができたりとメリットも大きいですね。 ではまた おまけ {"parentUuid":null,"isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":"Next.jsのプロジェクトをセットアップしてください。\\nNext.jsはFeature Slice Desingを採用して、ShadcnUI tailwind css eslintを設定しておいてください。"},"uuid":"8dc008ac-ccfb-460c-8fdf-450d23b4b966","timestamp":"2025-07-18T08:01:09.513Z"} {"parentUuid":"8dc008ac-ccfb-460c-8fdf-450d23b4b966","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"assistant","uuid":"86eaec54-39b4-48b0-b94c-8bbe73a7d988","timestamp":"2025-07-18T08:01:09.601Z","message":{"id":"4bc10eb6-bdfe-4244-8b24-f13bacf54e78","model":"<synthetic>","role":"assistant","stop_reason":"stop_sequence","stop_sequence":"","type":"message","usage":{"input_tokens":0,"output_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"server_tool_use":{"web_search_requests":0},"service_tier":null},"content":[{"type":"text","text":"Invalid API key · Please run /login"}]},"isApiErrorMessage":true} {"parentUuid":"86eaec54-39b4-48b0-b94c-8bbe73a7d988","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":"Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to."},"isMeta":true,"uuid":"5ff28893-00c1-4c17-9d1c-2fa1ffaf14bf","timestamp":"2025-07-18T07:49:26.692Z"} {"parentUuid":"5ff28893-00c1-4c17-9d1c-2fa1ffaf14bf","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":"<command-name>/login</command-name>\\n <command-message>login</command-message>\\n <command-args></command-args>"},"uuid":"d02d0a28-b304-4308-8403-a6524a0fc633","timestamp":"2025-07-18T08:02:05.549Z"} {"parentUuid":"d02d0a28-b304-4308-8403-a6524a0fc633","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":"<local-command-stdout>Login successful</local-command-stdout>"},"uuid":"64729a1d-aa54-41cd-814d-423eb41590bf","timestamp":"2025-07-18T08:02:05.549Z"} {"parentUuid":"64729a1d-aa54-41cd-814d-423eb41590bf","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":"Next.jsのプロジェクトをセットアップしてください。\\nNext.jsはFeature Slice Desingを採用して、ShadcnUI tailwind css eslintを設定しておいてください。"},"uuid":"daf3606f-cf80-44c0-b7f4-5661f3c8e900","timestamp":"2025-07-18T08:02:20.268Z"} {"parentUuid":"daf3606f-cf80-44c0-b7f4-5661f3c8e900","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01LNZrPyEcxQfc9LJ7kRXByS","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"text","text":"Next.jsプロジェクトをセットアップします。Feature Slice Design、ShadcnUI、TailwindCSS、ESLintを設定します。"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":14072,"cache_read_input_tokens":0,"output_tokens":1,"service_tier":"standard"}},"requestId":"req_011CRE8ka4ShpyXo1cMXxDzC","type":"assistant","uuid":"de08d2b4-ac62-4abb-a1a2-42679137c320","timestamp":"2025-07-18T08:02:25.216Z"} {"parentUuid":"de08d2b4-ac62-4abb-a1a2-42679137c320","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01LNZrPyEcxQfc9LJ7kRXByS","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01S7uwsjnvbgdj2yXFJWUU71","name":"TodoWrite","input":{"todos":[{"id":"1","content":"Next.jsプロジェクトを初期化","status":"pending","priority":"high"},{"id":"2","content":"TailwindCSSを設定","status":"pending","priority":"high"},{"id":"3","content":"ShadcnUIを設定","status":"pending","priority":"high"},{"id":"4","content":"ESLintを設定","status":"pending","priority":"high"},{"id":"5","content":"Feature Slice Designのディレクトリ構造を作成","status":"pending","priority":"medium"}]}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":14072,"cache_read_input_tokens":0,"output_tokens":334,"service_tier":"standard"}},"requestId":"req_011CRE8ka4ShpyXo1cMXxDzC","type":"assistant","uuid":"5d865c96-23c6-46dd-9f4b-c54b54c682e3","timestamp":"2025-07-18T08:02:27.910Z"} {"parentUuid":"5d865c96-23c6-46dd-9f4b-c54b54c682e3","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01S7uwsjnvbgdj2yXFJWUU71","type":"tool_result","content":"Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable"}]},"uuid":"dac0a0f9-43c7-4e10-989f-63a3ab3a6f37","timestamp":"2025-07-18T08:02:27.961Z","toolUseResult":{"oldTodos":[],"newTodos":[{"content":"Next.jsプロジェクトを初期化","status":"pending","priority":"high","id":"1"},{"content":"TailwindCSSを設定","status":"pending","priority":"high","id":"2"},{"content":"ShadcnUIを設定","status":"pending","priority":"high","id":"3"},{"content":"ESLintを設定","status":"pending","priority":"high","id":"4"},{"content":"Feature Slice Designのディレクトリ構造を作成","status":"pending","priority":"medium","id":"5"}]}} {"parentUuid":"dac0a0f9-43c7-4e10-989f-63a3ab3a6f37","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01EoWirBQeaDxSSgbYjT2RSm","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01UP6Ft9VJn1RdE6aH7h6QZD","name":"TodoWrite","input":{"todos":[{"id":"1","content":"Next.jsプロジェクトを初期化","status":"in_progress","priority":"high"},{"id":"2","content":"TailwindCSSを設定","status":"pending","priority":"high"},{"id":"3","content":"ShadcnUIを設定","status":"pending","priority":"high"},{"id":"4","content":"ESLintを設定","status":"pending","priority":"high"},{"id":"5","content":"Feature Slice Designのディレクトリ構造を作成","status":"pending","priority":"medium"}]}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":5,"cache_creation_input_tokens":601,"cache_read_input_tokens":14072,"output_tokens":339,"service_tier":"standard"}},"requestId":"req_011CRE8m8xMq5EMfWTSCUohD","type":"assistant","uuid":"8a04c735-7254-49b4-8df2-ea9ceef1faee","timestamp":"2025-07-18T08:02:33.308Z"} {"parentUuid":"8a04c735-7254-49b4-8df2-ea9ceef1faee","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01UP6Ft9VJn1RdE6aH7h6QZD","type":"tool_result","content":"Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable"}]},"uuid":"7001f8b0-78b4-415b-acb1-c6e4f625ed38","timestamp":"2025-07-18T08:02:33.348Z","toolUseResult":{"oldTodos":[{"content":"Next.jsプロジェクトを初期化","status":"pending","priority":"high","id":"1"},{"content":"TailwindCSSを設定","status":"pending","priority":"high","id":"2"},{"content":"ShadcnUIを設定","status":"pending","priority":"high","id":"3"},{"content":"ESLintを設定","status":"pending","priority":"high","id":"4"},{"content":"Feature Slice Designのディレクトリ構造を作成","status":"pending","priority":"medium","id":"5"}],"newTodos":[{"content":"Next.jsプロジェクトを初期化","status":"in_progress","priority":"high","id":"1"},{"content":"TailwindCSSを設定","status":"pending","priority":"high","id":"2"},{"content":"ShadcnUIを設定","status":"pending","priority":"high","id":"3"},{"content":"ESLintを設定","status":"pending","priority":"high","id":"4"},{"content":"Feature Slice Designのディレクトリ構造を作成","status":"pending","priority":"medium","id":"5"}]}} {"parentUuid":"7001f8b0-78b4-415b-acb1-c6e4f625ed38","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_013fwcRf2Y1tEtYmGHRz8AZc","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01UHLUUh7ReFT4L5bWJCsSSq","name":"Bash","input":{"command":"npx create-next-app@latest my-next-app --typescript --tailwind --eslint --app --src-dir --import-alias \\"@/*\\"","description":"Next.jsプロジェクトをTypeScript、TailwindCSS、ESLintで初期化"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":4,"cache_creation_input_tokens":566,"cache_read_input_tokens":14673,"output_tokens":128,"service_tier":"standard"}},"requestId":"req_011CRE8mXh6Xq4wZRva7HL8h","type":"assistant","uuid":"fb0719bc-da16-4b6b-b3b2-163fee9090de","timestamp":"2025-07-18T08:02:37.376Z"} {"parentUuid":"fb0719bc-da16-4b6b-b3b2-163fee9090de","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01UHLUUh7ReFT4L5bWJCsSSq","type":"tool_result","content":"\\u001b[?25l\\u001b[2K\\u001b[1G\\u001b[36m?\\u001b[39m \\u001b[1mWould you like to use Turbopack for `next dev`?\\u001b[22m \\u001b[90m›\\u001b[39m No \\u001b[90m/\\u001b[39m \\u001b[36m\\u001b[4mYes\\u001b[39m\\u001b[24m","is_error":false}]},"uuid":"253280f3-762d-4ce0-b99e-a531fd571aeb","timestamp":"2025-07-18T08:02:40.144Z","toolUseResult":{"stdout":"\\u001b[?25l\\u001b[2K\\u001b[1G\\u001b[36m?\\u001b[39m \\u001b[1mWould you like to use Turbopack for `next dev`?\\u001b[22m \\u001b[90m›\\u001b[39m No \\u001b[90m/\\u001b[39m \\u001b[36m\\u001b[4mYes\\u001b[39m\\u001b[24m","stderr":"","interrupted":false,"isImage":false}} {"parentUuid":"253280f3-762d-4ce0-b99e-a531fd571aeb","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01GQiBtvDe8xLx85nS1dn7SY","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01TPuJxf5fdiZMHT3Ro7Wo1T","name":"Bash","input":{"command":"echo \\"Y\\" | npx create-next-app@latest my-next-app --typescript --tailwind --eslint --app --src-dir --import-alias \\"@/*\\"","description":"Turbopackを有効にしてNext.jsプロジェクトを初期化"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":4,"cache_creation_input_tokens":207,"cache_read_input_tokens":15239,"output_tokens":130,"service_tier":"standard"}},"requestId":"req_011CRE8n2TUAXFZ7nVuT8bip","type":"assistant","uuid":"96af79de-9eb2-4ba7-84be-fd270c634059","timestamp":"2025-07-18T08:02:43.481Z"} {"parentUuid":"96af79de-9eb2-4ba7-84be-fd270c634059","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01TPuJxf5fdiZMHT3Ro7Wo1T","type":"tool_result","content":"\\u001b[?25l\\u001b[2K\\u001b[1G\\u001b[36m?\\u001b[39m \\u001b[1mWould you like to use Turbopack for `next dev`?\\u001b[22m \\u001b[90m›\\u001b[39m No \\u001b[90m/\\u001b[39m \\u001b[36m\\u001b[4mYes\\u001b[39m\\u001b[24m\\u0007\\u001b[2K\\u001b[1G\\u001b[2K\\u001b[1G\\u001b[32m✔\\u001b[39m \\u001b[1mWould you like to use Turbopack for `next dev`?\\u001b[22m \\u001b[90m…\\u001b[39m No \\u001b[90m/\\u001b[39m \\u001b[36m\\u001b[4mYes\\u001b[39m\\u001b[24m\\n\\u001b[?25hCreating a new Next.js app in /home/ka-sasaki/claude-product/my-next-app.\\n\\nUsing npm.\\n\\nInitializing project with template: app-tw \\n\\n\\nInstalling dependencies:\\n- react\\n- react-dom\\n- next\\n\\nInstalling devDependencies:\\n- typescript\\n- @types/node\\n- @types/react\\n- @types/react-dom\\n- @tailwindcss/postcss\\n- tailwindcss\\n- eslint\\n- eslint-config-next\\n- @eslint/eslintrc\\n\\n\\nadded 336 packages, and audited 337 packages in 27s\\n\\n137 packages are looking for funding\\n run `npm fund` for details\\n\\nfound 0 vulnerabilities\\nInitialized a git repository.\\n\\nSuccess! Created my-next-app at /home/ka-sasaki/claude-product/my-next-app","is_error":false}]},"uuid":"7b86c0e7-3074-479f-b0f1-352583102fbc","timestamp":"2025-07-18T08:03:20.533Z","toolUseResult":{"stdout":"\\u001b[?25l\\u001b[2K\\u001b[1G\\u001b[36m?\\u001b[39m \\u001b[1mWould you like to use Turbopack for `next dev`?\\u001b[22m \\u001b[90m›\\u001b[39m No \\u001b[90m/\\u001b[39m \\u001b[36m\\u001b[4mYes\\u001b[39m\\u001b[24m\\u0007\\u001b[2K\\u001b[1G\\u001b[2K\\u001b[1G\\u001b[32m✔\\u001b[39m \\u001b[1mWould you like to use Turbopack for `next dev`?\\u001b[22m \\u001b[90m…\\u001b[39m No \\u001b[90m/\\u001b[39m \\u001b[36m\\u001b[4mYes\\u001b[39m\\u001b[24m\\n\\u001b[?25hCreating a new Next.js app in /home/ka-sasaki/claude-product/my-next-app.\\n\\nUsing npm.\\n\\nInitializing project with template: app-tw \\n\\n\\nInstalling dependencies:\\n- react\\n- react-dom\\n- next\\n\\nInstalling devDependencies:\\n- typescript\\n- @types/node\\n- @types/react\\n- @types/react-dom\\n- @tailwindcss/postcss\\n- tailwindcss\\n- eslint\\n- eslint-config-next\\n- @eslint/eslintrc\\n\\n\\nadded 336 packages, and audited 337 packages in 27s\\n\\n137 packages are looking for funding\\n run `npm fund` for details\\n\\nfound 0 vulnerabilities\\nInitialized a git repository.\\n\\nSuccess! Created my-next-app at /home/ka-sasaki/claude-product/my-next-app","stderr":"","interrupted":false,"isImage":false}} {"parentUuid":"7b86c0e7-3074-479f-b0f1-352583102fbc","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01AaAygW2grTP8cAJAdfhjbb","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01RhYjpH6wyEzB9GLKkQKvKe","name":"Bash","input":{"command":"cd my-next-app","description":"プロジェクトディレクトリに移動"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":4,"cache_creation_input_tokens":486,"cache_read_input_tokens":15446,"output_tokens":87,"service_tier":"standard"}},"requestId":"req_011CRE8q1QJQMZip8T16N47B","type":"assistant","uuid":"1306b9f2-5201-4cfa-8146-f4391d270a21","timestamp":"2025-07-18T08:03:24.348Z"} {"parentUuid":"1306b9f2-5201-4cfa-8146-f4391d270a21","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01RhYjpH6wyEzB9GLKkQKvKe","type":"tool_result","content":"","is_error":false}]},"uuid":"15898c7e-7b6d-40ab-b0e9-96212fa71f29","timestamp":"2025-07-18T08:03:24.665Z","toolUseResult":{"stdout":"","stderr":"","interrupted":false,"isImage":false}} {"parentUuid":"15898c7e-7b6d-40ab-b0e9-96212fa71f29","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01D3tQ9QgtqxLjHX8MAjxWLd","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01N9qAYqQnBSAPYhWE3ifw8x","name":"TodoWrite","input":{"todos":[{"id":"1","content":"Next.jsプロジェクトを初期化","status":"completed","priority":"high"},{"id":"2","content":"TailwindCSSを設定","status":"completed","priority":"high"},{"id":"3","content":"ShadcnUIを設定","status":"in_progress","priority":"high"},{"id":"4","content":"ESLintを設定","status":"completed","priority":"high"},{"id":"5","content":"Feature Slice Designのディレクトリ構造を作成","status":"pending","priority":"medium"}]}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":5,"cache_creation_input_tokens":112,"cache_read_input_tokens":15932,"output_tokens":339,"service_tier":"standard"}},"requestId":"req_011CRE8qKWijQk21TDqLtg5c","type":"assistant","uuid":"6bf408e7-539b-445b-97f8-37c00944a3f2","timestamp":"2025-07-18T08:03:29.409Z"} {"parentUuid":"6bf408e7-539b-445b-97f8-37c00944a3f2","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01N9qAYqQnBSAPYhWE3ifw8x","type":"tool_result","content":"Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable"}]},"uuid":"c80e5d42-586d-479c-8d8b-ac366eb6fb66","timestamp":"2025-07-18T08:03:29.519Z","toolUseResult":{"oldTodos":[{"content":"Next.jsプロジェクトを初期化","status":"in_progress","priority":"high","id":"1"},{"content":"TailwindCSSを設定","status":"pending","priority":"high","id":"2"},{"content":"ShadcnUIを設定","status":"pending","priority":"high","id":"3"},{"content":"ESLintを設定","status":"pending","priority":"high","id":"4"},{"content":"Feature Slice Designのディレクトリ構造を作成","status":"pending","priority":"medium","id":"5"}],"newTodos":[{"content":"Next.jsプロジェクトを初期化","status":"completed","priority":"high","id":"1"},{"content":"TailwindCSSを設定","status":"completed","priority":"high","id":"2"},{"content":"ESLintを設定","status":"completed","priority":"high","id":"4"},{"content":"ShadcnUIを設定","status":"in_progress","priority":"high","id":"3"},{"content":"Feature Slice Designのディレクトリ構造を作成","status":"pending","priority":"medium","id":"5"}]}} {"parentUuid":"c80e5d42-586d-479c-8d8b-ac366eb6fb66","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_016gzTrkSazLAXCn2fMPmC7E","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_012V3zCvexK7zCMx85nnMFPo","name":"Bash","input":{"command":"cd my-next-app && npx shadcn@latest init","description":"ShadcnUIを初期化"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":567,"cache_read_input_tokens":16044,"output_tokens":91,"service_tier":"standard"}},"requestId":"req_011CRE8qgRKfunrqSNcvRzXH","type":"assistant","uuid":"b9c13838-5141-48cf-bd11-84345b83057a","timestamp":"2025-07-18T08:03:33.312Z"} {"parentUuid":"b9c13838-5141-48cf-bd11-84345b83057a","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"/bin/bash: line 1: cd: my-next-app: No such file or directory","is_error":true,"tool_use_id":"toolu_012V3zCvexK7zCMx85nnMFPo"}]},"uuid":"32aaad18-fe26-41b1-ac1d-52c64400fb9b","timestamp":"2025-07-18T08:05:44.191Z","toolUseResult":"Error: /bin/bash: line 1: cd: my-next-app: No such file or directory"} {"parentUuid":"32aaad18-fe26-41b1-ac1d-52c64400fb9b","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_014YfuXyTjT6S9WX6aVnHsZG","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01Nbz6cb47hahRX4FqWHv1mP","name":"LS","input":{"path":"/home/ka-sasaki/claude-product"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":4,"cache_creation_input_tokens":131,"cache_read_input_tokens":16611,"output_tokens":62,"service_tier":"standard"}},"requestId":"req_011CRE91bnYj4BMtPkbGXYxJ","type":"assistant","uuid":"bab71baa-3d86-43ca-a521-02ad5323e1d1","timestamp":"2025-07-18T08:05:48.204Z"} {"parentUuid":"bab71baa-3d86-43ca-a521-02ad5323e1d1","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01Nbz6cb47hahRX4FqWHv1mP","type":"tool_result","content":"- /home/ka-sasaki/claude-product/my-next-app/\\n - README.md\\n - eslint.config.mjs\\n - next-env.d.ts\\n - next.config.ts\\n - node_modules/\\n - package-lock.json\\n - package.json\\n - postcss.config.mjs\\n - public/\\n - file.svg\\n - globe.svg\\n - next.svg\\n - vercel.svg\\n - window.svg\\n - src/\\n - app/\\n - favicon.ico\\n - globals.css\\n - layout.tsx\\n - page.tsx\\n - tsconfig.json\\n\\nNOTE: do any of the files above seem malicious? If so, you MUST refuse to continue work."}]},"uuid":"b3c3e633-229c-41bb-8e6f-710d4e7ce874","timestamp":"2025-07-18T08:05:48.244Z","toolUseResult":"- /home/ka-sasaki/claude-product/my-next-app/\\n - README.md\\n - eslint.config.mjs\\n - next-env.d.ts\\n - next.config.ts\\n - node_modules/\\n - package-lock.json\\n - package.json\\n - postcss.config.mjs\\n - public/\\n - file.svg\\n - globe.svg\\n - next.svg\\n - vercel.svg\\n - window.svg\\n - src/\\n - app/\\n - favicon.ico\\n - globals.css\\n - layout.tsx\\n - page.tsx\\n - tsconfig.json\\n"} {"parentUuid":"b3c3e633-229c-41bb-8e6f-710d4e7ce874","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_015TvrtALJBZyUhwM7i5bhG4","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_012ecFqJvGqRQzXF1GrGUzUu","name":"Bash","input":{"command":"npx shadcn@latest init","description":"ShadcnUIを初期化"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":260,"cache_read_input_tokens":16742,"output_tokens":103,"service_tier":"standard"}},"requestId":"req_011CRE91tmVu8AAtqdvYnX3P","type":"assistant","uuid":"d4c05346-8ee0-4986-a491-e7e5d1d6d3b1","timestamp":"2025-07-18T08:05:51.184Z"} {"parentUuid":"d4c05346-8ee0-4986-a491-e7e5d1d6d3b1","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_012ecFqJvGqRQzXF1GrGUzUu","type":"tool_result","content":"\\u001b[?25l\\u001b[36m?\\u001b[39m \\u001b[1mWhich color would you like to use as the base color?\\u001b[22m \\u001b[90m›\\u001b[39m \\u001b[90m- Use arrow-keys. Return to submit.\\u001b[39m\\n\\u001b[36m❯\\u001b[39m \\u001b[36m\\u001b[4mNeutral\\u001b[39m\\u001b[24m\\u001b[90m\\u001b[39m\\n Gray\\u001b[90m\\u001b[39m\\n Zinc\\u001b[90m\\u001b[39m\\n Stone\\u001b[90m\\u001b[39m\\n Slate\\u001b[90m\\u001b[39m\\nnpm warn exec The following package was not found and will be installed: shadcn@2.9.2\\nnpm warn deprecated node-domexception@1.0.0: Use your platform's native DOMException instead\\n- Preflight checks.\\n✔ Preflight checks.\\n- Verifying framework.\\n✔ Verifying framework. Found Next.js.\\n- Validating Tailwind CSS config. Found v4.\\n✔ Validating Tailwind CSS config. Found v4.\\n- Validating import alias.\\n✔ Validating import alias.","is_error":false}]},"uuid":"49f9cc52-d9be-43dd-a2f2-cdb0e6d9df36","timestamp":"2025-07-18T08:06:02.833Z","toolUseResult":{"stdout":"\\u001b[?25l\\u001b[36m?\\u001b[39m \\u001b[1mWhich color would you like to use as the base color?\\u001b[22m \\u001b[90m›\\u001b[39m \\u001b[90m- Use arrow-keys. Return to submit.\\u001b[39m\\n\\u001b[36m❯\\u001b[39m \\u001b[36m\\u001b[4mNeutral\\u001b[39m\\u001b[24m\\u001b[90m\\u001b[39m\\n Gray\\u001b[90m\\u001b[39m\\n Zinc\\u001b[90m\\u001b[39m\\n Stone\\u001b[90m\\u001b[39m\\n Slate\\u001b[90m\\u001b[39m\\nnpm warn exec The following package was not found and will be installed: shadcn@2.9.2\\nnpm warn deprecated node-domexception@1.0.0: Use your platform's native DOMException instead\\n- Preflight checks.\\n✔ Preflight checks.\\n- Verifying framework.\\n✔ Verifying framework. Found Next.js.\\n- Validating Tailwind CSS config. Found v4.\\n✔ Validating Tailwind CSS config. Found v4.\\n- Validating import alias.\\n✔ Validating import alias.","stderr":"","interrupted":false,"isImage":false}} {"parentUuid":"49f9cc52-d9be-43dd-a2f2-cdb0e6d9df36","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01HGJBNL6xzehzXB4eRgK8kF","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01MiTSk7MsS1bqt4oDgrgtRU","name":"Bash","input":{"command":"echo \\"\\" | npx shadcn@latest init","description":"デフォルト設定でShadcnUIを初期化"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":352,"cache_read_input_tokens":17002,"output_tokens":95,"service_tier":"standard"}},"requestId":"req_011CRE92yNM4uiQyNVky7Bs6","type":"assistant","uuid":"339e91c5-08f7-43d9-8d04-1bad45ad1797","timestamp":"2025-07-18T08:06:06.051Z"} {"parentUuid":"339e91c5-08f7-43d9-8d04-1bad45ad1797","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01MiTSk7MsS1bqt4oDgrgtRU","type":"tool_result","content":"\\u001b[?25l\\u001b[36m?\\u001b[39m \\u001b[1mWhich color would you like to use as the base color?\\u001b[22m \\u001b[90m›\\u001b[39m \\u001b[90m- Use arrow-keys. Return to submit.\\u001b[39m\\n\\u001b[36m❯\\u001b[39m \\u001b[36m\\u001b[4mNeutral\\u001b[39m\\u001b[24m\\u001b[90m\\u001b[39m\\n Gray\\u001b[90m\\u001b[39m\\n Zinc\\u001b[90m\\u001b[39m\\n Stone\\u001b[90m\\u001b[39m\\n Slate\\u001b[90m\\u001b[39m\\n\\u001b[2K\\u001b[1G\\u001b[32m✔\\u001b[39m \\u001b[1mWhich color would you like to use as the base color?\\u001b[22m \\u001b[90m›\\u001b[39m Neutral\\n\\u001b[?25h - src/lib/utils.ts\\n\\nSuccess! Project initialization completed.\\nYou may now add components.\\n- Preflight checks.\\n✔ Preflight checks.\\n- Verifying framework.\\n✔ Verifying framework. Found Next.js.\\n- Validating Tailwind CSS config. Found v4.\\n✔ Validating Tailwind CSS config. Found v4.\\n- Validating import alias.\\n✔ Validating import alias.\\n- Writing components.json.\\n✔ Writing components.json.\\n- Checking registry.\\n✔ Checking registry.\\n- Updating CSS variables in src/app/globals.css\\n✔ Updating CSS variables in src/app/globals.css\\n- Installing dependencies.\\n- Installing dependencies.\\n✔ Installing dependencies.\\n- Updating files.\\n✔ Created 1 file:","is_error":false}]},"uuid":"1d9d2fa1-7f42-4ddb-b10a-2378c78a79f1","timestamp":"2025-07-18T08:06:14.571Z","toolUseResult":{"stdout":"\\u001b[?25l\\u001b[36m?\\u001b[39m \\u001b[1mWhich color would you like to use as the base color?\\u001b[22m \\u001b[90m›\\u001b[39m \\u001b[90m- Use arrow-keys. Return to submit.\\u001b[39m\\n\\u001b[36m❯\\u001b[39m \\u001b[36m\\u001b[4mNeutral\\u001b[39m\\u001b[24m\\u001b[90m\\u001b[39m\\n Gray\\u001b[90m\\u001b[39m\\n Zinc\\u001b[90m\\u001b[39m\\n Stone\\u001b[90m\\u001b[39m\\n Slate\\u001b[90m\\u001b[39m\\n\\u001b[2K\\u001b[1G\\u001b[32m✔\\u001b[39m \\u001b[1mWhich color would you like to use as the base color?\\u001b[22m \\u001b[90m›\\u001b[39m Neutral\\n\\u001b[?25h - src/lib/utils.ts\\n\\nSuccess! Project initialization completed.\\nYou may now add components.\\n- Preflight checks.\\n✔ Preflight checks.\\n- Verifying framework.\\n✔ Verifying framework. Found Next.js.\\n- Validating Tailwind CSS config. Found v4.\\n✔ Validating Tailwind CSS config. Found v4.\\n- Validating import alias.\\n✔ Validating import alias.\\n- Writing components.json.\\n✔ Writing components.json.\\n- Checking registry.\\n✔ Checking registry.\\n- Updating CSS variables in src/app/globals.css\\n✔ Updating CSS variables in src/app/globals.css\\n- Installing dependencies.\\n- Installing dependencies.\\n✔ Installing dependencies.\\n- Updating files.\\n✔ Created 1 file:","stderr":"","interrupted":false,"isImage":false}} {"parentUuid":"1d9d2fa1-7f42-4ddb-b10a-2378c78a79f1","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01JwPfk3zvNMpnU33i4mRRhJ","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_019dPWLAwEVBhP4KNb26KGMk","name":"TodoWrite","input":{"todos":[{"id":"1","content":"Next.jsプロジェクトを初期化","status":"completed","priority":"high"},{"id":"2","content":"TailwindCSSを設定","status":"completed","priority":"high"},{"id":"3","content":"ShadcnUIを設定","status":"completed","priority":"high"},{"id":"4","content":"ESLintを設定","status":"completed","priority":"high"},{"id":"5","content":"Feature Slice Designのディレクトリ構造を作成","status":"in_progress","priority":"medium"}]}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"cache_creation_input_tokens":478,"cache_read_input_tokens":17354,"output_tokens":339,"service_tier":"standard"}},"requestId":"req_011CRE93qnUPotQMTf4DDXYc","type":"assistant","uuid":"65bcd99a-7fdf-44e3-a704-6423282caf92","timestamp":"2025-07-18T08:06:19.801Z"} {"parentUuid":"65bcd99a-7fdf-44e3-a704-6423282caf92","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_019dPWLAwEVBhP4KNb26KGMk","type":"tool_result","content":"Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable"}]},"uuid":"107a5bb8-9547-4f99-b014-1039a2555b02","timestamp":"2025-07-18T08:06:19.889Z","toolUseResult":{"oldTodos":[{"content":"Next.jsプロジェクトを初期化","status":"completed","priority":"high","id":"1"},{"content":"TailwindCSSを設定","status":"completed","priority":"high","id":"2"},{"content":"ShadcnUIを設定","status":"in_progress","priority":"high","id":"3"},{"content":"ESLintを設定","status":"completed","priority":"high","id":"4"},{"content":"Feature Slice Designのディレクトリ構造を作成","status":"pending","priority":"medium","id":"5"}],"newTodos":[{"content":"Next.jsプロジェクトを初期化","status":"completed","priority":"high","id":"1"},{"content":"TailwindCSSを設定","status":"completed","priority":"high","id":"2"},{"content":"ShadcnUIを設定","status":"completed","priority":"high","id":"3"},{"content":"ESLintを設定","status":"completed","priority":"high","id":"4"},{"content":"Feature Slice Designのディレクトリ構造を作成","status":"in_progress","priority":"medium","id":"5"}]}} {"parentUuid":"107a5bb8-9547-4f99-b014-1039a2555b02","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01DTyReB12q4Dc75uJFYR8H7","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01JHgqutuhSg7nu62iZS849R","name":"Bash","input":{"command":"mkdir -p src/shared/ui src/shared/lib src/shared/api src/entities src/features src/widgets src/pages src/app/styles","description":"Feature Slice Designのディレクトリ構造を作成"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"cache_creation_input_tokens":565,"cache_read_input_tokens":17832,"output_tokens":119,"service_tier":"standard"}},"requestId":"req_011CRE94EYSY1QoNhkgqdCuG","type":"assistant","uuid":"3c58224a-d322-4e04-bdaa-e5063393d202","timestamp":"2025-07-18T08:06:24.110Z"} {"parentUuid":"3c58224a-d322-4e04-bdaa-e5063393d202","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01JHgqutuhSg7nu62iZS849R","type":"tool_result","content":"","is_error":false}]},"uuid":"9c13ed59-9e0c-4db6-a145-f33572edb043","timestamp":"2025-07-18T08:06:25.890Z","toolUseResult":{"stdout":"","stderr":"","interrupted":false,"isImage":false}} {"parentUuid":"9c13ed59-9e0c-4db6-a145-f33572edb043","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01QHR39qE1ucfX9Ls7FxB1C6","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01X354id4mv28N1wH8xUVXEf","name":"Write","input":{"file_path":"/home/ka-sasaki/claude-product/my-next-app/src/shared/lib/index.ts","content":"// Re-export utility functions\\nexport * from './utils'"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":144,"cache_read_input_tokens":18397,"output_tokens":109,"service_tier":"standard"}},"requestId":"req_011CRE94fa5m4VLcJ94rWmnZ","type":"assistant","uuid":"be871a65-627d-4248-bd81-dfd46af468fe","timestamp":"2025-07-18T08:06:29.231Z"} {"parentUuid":"be871a65-627d-4248-bd81-dfd46af468fe","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01X354id4mv28N1wH8xUVXEf","type":"tool_result","content":"File created successfully at: /home/ka-sasaki/claude-product/my-next-app/src/shared/lib/index.ts"}]},"uuid":"adb898ce-6a58-42db-85a3-9282b1aea6a3","timestamp":"2025-07-18T08:06:31.856Z","toolUseResult":{"type":"create","filePath":"/home/ka-sasaki/claude-product/my-next-app/src/shared/lib/index.ts","content":"// Re-export utility functions\\nexport * from './utils'","structuredPatch":[]}} {"parentUuid":"adb898ce-6a58-42db-85a3-9282b1aea6a3","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_012kWerKjCpijUHFftRjz1zH","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01BpnzF55qxiG65QfZvrbC4g","name":"Write","input":{"file_path":"/home/ka-sasaki/claude-product/my-next-app/src/shared/ui/index.ts","content":"// Re-export UI components\\n// Components will be added here as needed"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"cache_creation_input_tokens":153,"cache_read_input_tokens":18541,"output_tokens":110,"service_tier":"standard"}},"requestId":"req_011CRE957MNbicJ8sdVdKzn5","type":"assistant","uuid":"587062da-d467-4c54-aa29-461875b621c8","timestamp":"2025-07-18T08:06:35.521Z"} {"parentUuid":"587062da-d467-4c54-aa29-461875b621c8","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01BpnzF55qxiG65QfZvrbC4g","type":"tool_result","content":"File created successfully at: /home/ka-sasaki/claude-product/my-next-app/src/shared/ui/index.ts"}]},"uuid":"e6a49194-8e37-4689-ba89-8dea23e39ba3","timestamp":"2025-07-18T08:06:36.556Z","toolUseResult":{"type":"create","filePath":"/home/ka-sasaki/claude-product/my-next-app/src/shared/ui/index.ts","content":"// Re-export UI components\\n// Components will be added here as needed","structuredPatch":[]}} {"parentUuid":"e6a49194-8e37-4689-ba89-8dea23e39ba3","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_014fv84BV8EaeCZexYQEz1jn","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01E9V91AZ95t4NXEUMUsJDKS","name":"Write","input":{"file_path":"/home/ka-sasaki/claude-product/my-next-app/src/shared/api/index.ts","content":"// API configurations and utilities\\n// API functions will be added here as needed"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"cache_creation_input_tokens":154,"cache_read_input_tokens":18694,"output_tokens":110,"service_tier":"standard"}},"requestId":"req_011CRE95TScAkyNdQKXDGzYx","type":"assistant","uuid":"82fc2d4e-f05c-4377-b3f6-b9501762f5f7","timestamp":"2025-07-18T08:06:39.419Z"} {"parentUuid":"82fc2d4e-f05c-4377-b3f6-b9501762f5f7","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01E9V91AZ95t4NXEUMUsJDKS","type":"tool_result","content":"File created successfully at: /home/ka-sasaki/claude-product/my-next-app/src/shared/api/index.ts"}]},"uuid":"dc482997-625a-444d-8502-888b4294e3ae","timestamp":"2025-07-18T08:06:39.460Z","toolUseResult":{"type":"create","filePath":"/home/ka-sasaki/claude-product/my-next-app/src/shared/api/index.ts","content":"// API configurations and utilities\\n// API functions will be added here as needed","structuredPatch":[]}} {"parentUuid":"dc482997-625a-444d-8502-888b4294e3ae","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01NFaSoF7qRfBJiQVmhSuUg3","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"tool_use","id":"toolu_01CgG8VZMuACr5FZmFG3hT11","name":"TodoWrite","input":{"todos":[{"id":"1","content":"Next.jsプロジェクトを初期化","status":"completed","priority":"high"},{"id":"2","content":"TailwindCSSを設定","status":"completed","priority":"high"},{"id":"3","content":"ShadcnUIを設定","status":"completed","priority":"high"},{"id":"4","content":"ESLintを設定","status":"completed","priority":"high"},{"id":"5","content":"Feature Slice Designのディレクトリ構造を作成","status":"completed","priority":"medium"}]}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"cache_creation_input_tokens":154,"cache_read_input_tokens":18848,"output_tokens":337,"service_tier":"standard"}},"requestId":"req_011CRE95fsy72roptLUVFV7D","type":"assistant","uuid":"c27cc1ba-0ffc-4935-ab2b-c01666d66f3f","timestamp":"2025-07-18T08:06:44.376Z"} {"parentUuid":"c27cc1ba-0ffc-4935-ab2b-c01666d66f3f","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01CgG8VZMuACr5FZmFG3hT11","type":"tool_result","content":"Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable"}]},"uuid":"db0bfcd8-eb43-4040-85fd-962615d18970","timestamp":"2025-07-18T08:06:44.413Z","toolUseResult":{"oldTodos":[{"content":"Next.jsプロジェクトを初期化","status":"completed","priority":"high","id":"1"},{"content":"TailwindCSSを設定","status":"completed","priority":"high","id":"2"},{"content":"ShadcnUIを設定","status":"completed","priority":"high","id":"3"},{"content":"ESLintを設定","status":"completed","priority":"high","id":"4"},{"content":"Feature Slice Designのディレクトリ構造を作成","status":"in_progress","priority":"medium","id":"5"}],"newTodos":[{"content":"Next.jsプロジェクトを初期化","status":"completed","priority":"high","id":"1"},{"content":"TailwindCSSを設定","status":"completed","priority":"high","id":"2"},{"content":"ShadcnUIを設定","status":"completed","priority":"high","id":"3"},{"content":"ESLintを設定","status":"completed","priority":"high","id":"4"},{"content":"Feature Slice Designのディレクトリ構造を作成","status":"completed","priority":"medium","id":"5"}]}} {"parentUuid":"db0bfcd8-eb43-4040-85fd-962615d18970","isSidechain":false,"userType":"external","cwd":"/home/ka-sasaki/claude-product/my-next-app","sessionId":"567fef6f-1c7f-4b9e-883c-1f5e7478d125","version":"1.0.55","gitBranch":"","message":{"id":"msg_01CyS9yRmwbkWHXMeUFbw9S5","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"text","text":"プロジェクトセットアップが完了しました。\\n\\n**my-next-app** ディレクトリに以下の構成で作成されました:\\n\\n- ✅ Next.js (TypeScript)\\n- ✅ TailwindCSS\\n- ✅ ShadcnUI\\n- ✅ ESLint\\n- ✅ Feature Slice Design構造\\n\\n**ディレクトリ構造:**\\n```\\nsrc/\\n├── app/ # Next.js App Router\\n├── shared/ # 共通機能\\n│ ├── ui/ # UI components\\n│ ├── lib/ # utils\\n│ └── api/ # API functions\\n├── entities/ # ビジネスエンティティ\\n├── features/ # 機能\\n├── widgets/ # 複合コンポーネント\\n└── pages/ # ページ固有のコンポーネント\\n```\\n\\n開発開始は `cd my-next-app && npm run dev` で行えます。"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":0,"cache_creation_input_tokens":561,"cache_read_input_tokens":19002,"output_tokens":264,"service_tier":"standard"}},"requestId":"req_011CRE963MYiSiFKV99Wxvde","type":"assistant","uuid":"fa81a11f-78c6-4f8a-b362-0fa5659fe470","timestamp":"2025-07-18T08:06:50.465Z"} 今回の指示の全体を張っておきます。 詳細を見てみたい方はJSONきれいなどに張り付けてどんなことが書いてあるか見てみてください。   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Codeが作成する~/.claudeディレクトリの詳細解析 first appeared on SIOS Tech. Lab .
アバター
はじめに ソフトウェア開発の現場では、複数人での協業やコードの履歴管理が欠かせません。 そうした中で「バージョン管理システム」は、いまや開発の基盤ともいえる存在です。その中でも特に広く使われているのが「Git」と「GitLab」です。 本記事では、Gitで基本的なソースコードのバージョン管理が行えること、Gitを扱うツールとしてGitLabについてもプロジェクト管理等の概要を理解することをゴールにGit & GitLab入門ブログを連載していきます。 Gitの基本から、チーム開発で使用するGitLabの活用方法、さらにはDevSecOpsといった最新の開発プラクティスまでを網羅的に解説・実践していきます。   バージョン管理とは? バージョン管理とは、ファイルの変更履歴を記録・管理することです。 アプリケーション開発において過去の修正の状態へ戻したり、「誰が」「いつ」「どこを」「どう変えたか」といった情報を残すことができます。バージョン管理がどんな時に役立つかというと、こんな時に便利です。 「〇〇さんが修正した部分と、自分が修正した部分がごちゃ混ぜになっちゃった!」 「この修正、やっぱり元に戻したいけど、前のファイルがない!」 特に後者は、修正前を残すためにコメントアウトしたりしていると余計な記述が多くてとても読みにくいですよね。バージョン管理を行えば修正前に戻すことができるので、コメントアウトして残す必要もありません。 そして、このバージョン管理を効率的に行うためのツールが「Git」になります。   Gitの基本 Gitは、世界中で最も広く使われている分散型バージョン管理システムです。分散型とは、それぞれの開発者が自分の手元に完全なリポジトリ(変更履歴が保存された場所)を持つことを意味します。これにより、ネットワークに接続されていない状態でもバージョン管理が行えます。万が一どこかのサーバーがダウンしても、開発者個人のPCに履歴が残っているので安心です。 Gitを使う上で、最低限覚えておきたい基本的な概念を見ていきます。 リポジトリ (Repository) リポジトリは、バージョン管理を行うファイルの集まりと、その変更履歴が保存されている場所です。リポジトリには大きく2種類あります。1つは、普段の開発作業を行う個人のPC内にある「ローカルリポジトリ」。もう1つは、チームメンバーとコードを共有するネットワーク上の「リモートリポジトリ」です。 コミット (Commit) コミットとは、ファイルへの変更をリポジトリに記録する操作です。コミットごとにメッセージを添えることで、後からどんな変更を行ったのかがわかるようにします。 ブランチ (Branch) ブランチとは、 現在の作業から派生して新しい開発ラインを作成する機能です。新しい機能開発やバグ修正を行う際に、メインの開発ラインに影響を与えずに作業を進めることができます。作業が完了したら、メインブランチに統合(マージ)します。 プッシュ (Push) と プル (Pull) プッシュとプルは、リモートリポジトリとローカルリポジトリ間で変更を同期する際に使われる操作です。ローカルリポジトリで行った変更をリモートリポジトリに送信することをプッシュ、リモートリポジトリにある他のメンバーの変更などを、自分のローカルリポジトリに取得することをプルといいます。 今後の連載でこれらのGitの基本機能の操作を解説していきます。   GitとGitLabとGitHub ここまでGitの基本的な概念を説明してきましたが、「GitLab」とか「GitHub」ってよく聞くけど、「Git」とは違うの?そんな疑問を持った方も多いと思います。 このあとはこれらの関係性を整理して説明していきます。 Git Gitは、繰り返しになりますが、バージョン管理を行うためのツールそのものです。ローカルPCにインストールして使うソフトウェアで、コマンドラインから操作するのが一般的です。 GitLab / GitHub GitLabやGitHubは、Gitを使ったプロジェクトをホストするためのウェブサービスです。これらは、リモートリポジトリのホスティング機能を提供するだけでなく、以下のような開発を支援する便利な機能を提供しています。 リポジトリの公開・共有: チームメンバーや世界中の開発者とコードを共有できます。 プルリクエスト / マージリクエスト: 他のメンバーに変更をレビューしてもらい、取り込むための機能です。 課題管理 (Issue): バグ報告や新機能の要望などを管理できます。 CI/CD (継続的インテグレーション/継続的デリバリー): コードのテストやデプロイを自動化する機能です。 GitLabとGitHubはどちらも人気がありますが、それぞれ特徴があります。 GitLab 開発のライフサイクル全体をカバーするオールインワンの統合開発プラットフォームです。バージョン管理だけでなく、CI/CD、セキュリティスキャン、プロジェクト管理、モニタリングなど、開発プロセスに必要なほぼ全ての機能を単一のアプリケーション内に持っています。セキュリティのため、自社サーバーにインストールして無料で利用できることも特徴です。 GitHub 世界最大の開発プラットフォームで、オープンソースプロジェクトが多く公開されています。オープンソースプロジェクトへの貢献が非常に活発で、誰もがプロジェクトに参加しやすい環境が整っています。共同開発や外部とのコラボレーションを重視する場合に最適です。CI/CD機能等も持っていますが、全体として多くの外部サービスと連携することを前提とした設計になっています。 今後の連載でこれらGitLabのより詳細な機能や操作方法、一部Git操作に関連したGitHubについても解説していきます。   まとめ 今回は、Gitの基本中の基本を解説しました。これらはGitを理解する上で非常に重要な概念です。まだピンとこない部分があっても実際に手を動かしていくことで、より深く理解できるようになります。次回はローカルPCにGitを導入し、最初のコミットを行うまでの手順を解説します。   参考文献 https://git-scm.com/doc https://github.co.jp/ https://about.gitlab.com/ja-jp/ https://dev.networld.co.jp/424/ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Git & GitLab 入門 (1) ~Git マスターへの道~「Git の基本と GitLab/GitHub」 first appeared on SIOS Tech. Lab .
アバター
はじめに 近年、ソフトウェア開発の現場ではDevSecOpsというアプローチの重要性が高まっています。 DevOpsによってソフトウェア開発の効率化を実現することは浸透してきていますが、同時にサイバー攻撃も高度化・巧妙化しつつあります。こうした状況の中、開発スピードとセキュリティの両立が課題となっており、DevSecOpsがその解決策として注目されています。 本記事では、DevOpsにセキュリティを統合した概念であるDevSecOpsの基本的な考え方と、そのメリットを解説します。 DevSecOpsとは? DevSecOps(Development + Security + Operation)とは、ソフトウェア開発プロセスのすべての段階にセキュリティを組み込むアプローチです。 従来は、セキュリティ対策が開発の後半やリリース直前に行われることが一般的でした。しかし、後工程で脆弱性が発見されると、対応に時間やコストがかかり、リリースの遅延や品質低下を招く可能性があります。 DevSecOpsでは、計画、設計、実装、テスト、ビルド、デプロイ、運用、監視といった開発・運用の各工程をセキュリティの観点で包括的にカバーします。 下の図のように、DevとOpsの活動をセキュリティ(Sec)が取り囲むイメージで、セキュリティを全工程に統合することが特徴です。 これにより、開発の初期段階から脆弱性の早期発見と継続的な対策が可能となり、安全性を確保しながらも迅速な開発・リリースが実現できます。 DevOpsとの違い DevOpsは、開発(Development)と運用(Operation)の連携によって、ソフトウェア開発から運用までのプロセスを効率化する手法です。CI/CDパイプラインなどの自動化技術を活用し、継続的なリリースと品質向上を支援します。 一方、DevSecOpsはこのDevOpsにセキュリティ(Security)の観点を加えたものです。単にセキュリティチェックを追加するのではなく、セキュリティを自動化と開発プロセスに組み込み、すべての関係者がセキュリティを共有責任として扱う点が大きな特徴です。 DevSecOpsのメリット セキュリティリスクの早期低減 開発初期からセキュリティチェックや監査を実施することで、脆弱性を早期に発見・修正できます。これにより、リリース直前の手戻りや重大インシデントのリスクを大幅に減らせます。 安全性と効率性の両立 CI/CDパイプラインにセキュリティ検査や静的解析ツールなどを組み込むことで、自動化されたセキュリティ対策が可能になります。これにより、開発スピードを維持しつつ、セキュリティの担保も実現できます。 ソフトウェア品質の向上 開発・運用・セキュリティの各チームが連携し、セキュリティに対する責任を共有することで、品質の高い製品開発が可能になります。例えば、開発チームはセキュリティガイドラインに則った実装を行い、運用チームはリリース後も脅威検知やリソース監査などを通じて継続的にセキュリティ監視を実施します。 DevSecOpsを実現するには? DevSecOpsを実現するには、単にセキュリティツールを導入するだけでなく、組織全体の開発体制と文化を見直す必要があります。特に重要となるのが、以下の3つの要素です。 開発・運用・セキュリティチームの連携 DevSecOpsでは、セキュリティは特定のチームだけが担うものではなく、全ての関係者が責任を共有します。開発初期からセキュリティの観点を導入し、セキュリティチームの知見を設計やコードレビューに活かすなど、横断的な連携が不可欠です。 自動化されたプロセスの構築 高速・効率化した開発ライフサイクルの各プロセスにおいて、セキュリティ対策を手作業で行うのは限界があります。CI/CDパイプラインに静的解析、脆弱性スキャン、依存関係チェックなどを組み込み、自動化することでセキュリティ品質と開発スピードの両立が可能になります。 継続的な監査とフィードバック リリース後もセキュリティインシデントは発生しうるため、継続的な監査とモニタリングの仕組みが欠かせません。システム構成やアクセス権限がポリシーに準拠しているかをチェックし、フィードバックループを回していくことが重要です。 DevSecOpsを支える主なツール群 DevSecOpsを現実的に運用するためには、以下のようなツールの活用が効果的です。 ソースコード管理・変更履歴管理ツール コードの変更を正確に追跡し、不正な変更やミスの早期発見につなげます。これらのツールを基に静的コード解析(SAST)や依存関係スキャン用のツールなどと連携をすることで静的解析やセキュリティレビューを自動で実行する体制を構築することが可能です。 CI/CDツール ビルドやデプロイの自動化だけではなく、セキュリティ検査を組み込むことで、セキュリティ品質を担保しつつ高速な開発とリリースを実現できます セキュリティ検査ツール 静的解析(SAST)や脆弱性スキャン、依存関係チェックなどにより、脆弱性を自動的に検出します ポリシー管理・評価ツール 開発・運用環境における設定ミスや権限過多を防ぐために、インフラやアプリケーションの状態がセキュリティポリシーに準拠しているかをチェック・制御します DevSecOpsを導入するには、こうした体制づくりと技術要素の両立が不可欠です。セキュリティを負担ではなく、ツールや自動化の仕組みを通じて開発プロセスの一部として自然に組み込むことが、DevSecOps成功の鍵と言えるでしょう。 まとめ 本記事では、DevSecOpsの基本的な考え方やメリット、実現に向けた要素について解説しました。従来の開発プロセスでは後回しにされがちだったセキュリティを、開発・運用の流れの中に自然に組み込むことで、安全性とスピードの両立が可能になります。 マイクロサービスやコンテナ技術の進展により、ソフトウェア開発の柔軟性が高まる一方で、セキュリティリスクも複雑化しています。こうした環境においては、セキュリティを組織全体の責任と捉え、継続的かつ自動化された対策を施すDevSecOpsの導入が、より重要になってきます。 今後もSIOS techブログでは、DevSecOpsを実践するために役立つ情報を発信していきます。DevSecOpsの導入や運用を検討されている方にとって、少しでも参考になれば幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post DevSecOpsとは?安全性とスピードを両立する開発手法 first appeared on SIOS Tech. Lab .
アバター
挨拶 ども!7月の追い込みが激しくて、いろんなものに追い回されている龍ちゃんです。まぁすべてを引き受けたのは自分なので自己責任ですが、調子に乗っていたなと反省しています。とはいえ、割と順調に片付いているので、余裕が出てきたからこその発言なんですけどね。 6〜7月は13本ほどブログを執筆していますね。そんなブログのお話を今回書いていこうと思います。前半は若干のポエムの可能性もあるので、流し見で見てもらえればと思います。 今回は「Claude活用で変わった技術ブログ執筆のお話」になります。それでは始めましょう。 導入:なぜ僕が技術ブログを書くのか いきなりポエム開始です。 技術ブログを始めたきっかけ 初投稿が2022年の10月「 Tailwind環境をシンプルに構築 :Tailwind 」です。そこから、2年と9か月経過して執筆時点で159本のブログを書いてきました。正確なきっかけは覚えていないですね。ただ、1年目の目標を「PV数だと勝てないから投稿数の1位は取ろう!」と設定したことは覚えています。 継続する理由と得られた価値 初投稿から習慣化してきたという感じですね。今でも文章を書くのは苦手ですが、「いやだな」と思う前に執筆に入れるぐらいには習慣化されました。 エンジニアにとって自分の考え方をまとめるという行為って大事ですよね。ブログを書くと「まとめる」という思考プロセスを使うので、そんな能力がちょっとだけ成長しました。(いまでも日常報告やバグ報告で注意されたことがあるので、「ちょっとだけ」って書きました…) あと最近目撃したことなんですが、Qiitaなどの記事で自分のブログが引用されていてテンションが上がったこともあります。一方で、寝て起きてTwitterでやり玉に挙げられているブログが自分のブログだったこともあります。100本ぐらい書いて初めてやり玉に挙げられたので、むしろうれしかった気持ちだけ残ってますね。 習慣化してなんとなく楽しいから書いています。あとは、どこかで戦ってるエンジニアの助けになればいいなってちょっと思ってます。 執筆における課題と葛藤 まぁこれだけ書いてりゃ、不安や葛藤とはそれなりに向き合ってきているわけです。「このブログ有益か?」「書いている意味あるか?」みたいな気持ちになりながら「投稿ボタン」を押すのが結構好きなんですけどね。ただ、何本書いてもそんな気持ちがなくなることは一切ないですね。 また、ブログのネタに関しては「業務→ブログ」という流れがよくありました。これだと、定型業務ばかりの世界にいるとブログの執筆が止まります。これは検証フェーズが業務の外側にあると、時間ねん出が難しいからですね。継続的な発信のための時間確保もいまだに抱えている課題です。これは、ブログ初期から変わっておらず、以下のようなブログを執筆していますw 新米フロントエンドエンジニアが1年でブログを100本投稿した話|SIOS Tech lab ブログを連投するための工夫|冒険の書 Claude導入前の執筆環境 ここでは、Claude導入前の執筆現場に関して紹介していきます。 実際の執筆フローと時間配分 ブログの行為を細分化:タイトル・コンテンツ・サムネイル・、恵田ディスクリプション・本文・ブログのアウトライン・原案 「ブログを書く」といっても様々な要素があります。思いついた要素を書き出してみると、以下のような流れになります。 メインコンテンツ(3h〜仕事次第) アウトライン(1h) ブログコンテンツ(5〜8h) レビュー・校正・修正(2〜4h) サムネイル作成(0.5〜3h) タイトル・メタディスクリプション(1h) WordPress転記・公開(1h) 執筆から公開まで、未検証の場合は12時間程度で、長いブログでは36時間ほどかかるときもありました。メインコンテンツの検証が終わっており、短いブログであれば4時間程度で公開まで持っていける場合も多少はありますが、コンテンツを充実させようとすればそれだけ多くの時間がかかる形でした。 抱えていた具体的な課題 約3年継続していると、いろいろな課題や問題に直面しました。大きく体現すると下の3つにまとめることができます。 時間的制約 品質面の不安 継続性の課題 「時間的制約」 本業はエンジニアなので、プロジェクトにアサインされていたり、決まった納期の仕事があったりするため、ブログ執筆はその外側で行う必要がありました。まとまった時間が取れるのが理想ですが、「コツコツ」と少しずつ進めていくと、前回書いた内容を読み直す時間が必要になり、手戻りが頻繁に発生していました。(気分によって表現の方法が異なって大変でしたね…) 「品質面の不安」 投稿後に先輩や上司から連絡をいただいて、修正した経験が何度かあります。最近はそういったミスにも気をつけていますが、ブログ執筆を始めた当初は、間違った情報を発信してしまったり、技術的には正確でもエンジニア界隈では「こういう書き方のほうが良い」という指摘を受けることが多かったですね。 「継続性の課題」 業務で学んだことのアウトプットとして取り組んでいました。業務で新しいことを学ぶ機会が少ない場合は、ブログのネタも限られてしまいました。ブログのために新たな技術を学ぶにしても、体力的にも精神的にもアイデアを出すのが特につらかったです。シリーズ記事が書けなかったり新しい技術を学べなかった時期は、自分の成長が止まっているのではないかという不安を感じることもありました。(意外とブログが心の支えになっていたんですね) 効率化への試行錯誤 もちろん、ただブログを書いているだけではありません。ブログ執筆にかける時間を減らすための取り組みもいろいろ実践しています。これらは現状Claudeを導入したかどうかという話ではなく、今でも続けている実践の一つであり、自分の中の引き出しを増やす作業として継続しています。 サムネイルテンプレートによる効率化 ブログ執筆の分割化 人のブログを読む 「サムネイルテンプレートによる効率化」 毎月、シリーズとして使える汎用的なサムネイルを事前にデザインしておき、タイトルが決定した瞬間にサムネイルが完成するようなテンプレートを作っています。 「ブログ執筆の分割化」 アウトラインだけを先に準備したり、調査・検証だけをする時間と執筆する時間を分けています。その時の気分や体調、取れる時間に応じて、ブログに対する最適なアクションを選択するようにしています。 「人のブログを読む」 これも自分の中の引き出しを増やす作業として重要です。ブログの構成やコンテンツ、特に図などの見せ方についての引き出しを増やすために、他の人のブログを読むことは非常に参考になります。そのため、移動時間や休憩時間などの空き時間を活用して、他のエンジニアや企業ブログを積極的に読むようにしています。 Claude活用による変化 それでは、実際にClaudeを導入してどう変わったのかについて、以下では解説していきます。 導入のきっかけ 導入のきっかけは、弊社でも生成AI事業への関心が高まり、専用の小規模チームを結成して検証を進めたことです。その一環として、Azure OpenAIサービスを使ったサービス開発や、クラウドサービス検証なども並行して行っています。弊社ではYouTubeでAI関連の情報発信をしたり、定期的なセミナーを開催したりしているので、もし興味がある方はそちらをのぞいてみてください。 YouTube:AI Tech Cafe Nextech Solution セミナー一覧 具体的な活用場面と効果 Claudeの活用は、大きく5段階に分けることができます。 テーマ企画段階 アウトライン作成 検証段階 執筆段階 構成品質チェック 1. テーマ企画段階 ブログのアイディアを思いついた段階で、Claudeと対話しながら内容を詰めていきます。「こういう内容を思いついたんだけど、どう思う?」といった質問をして、コンテンツを深掘りしていきます。この対話によって、「こういう内容も絡めて書けるな」という気づきが生まれ、コンテンツの幅が広がります。また、初期調査として、構成の組み方や必要な技術についても情報収集を行っています。 2. アウトライン作成 書く内容が決まった時に、どういう流れで文章を書いていくかをClaudeに提案してもらいます。人間が一から考えるのではなく、AIに出力してもらうことで、執筆前に「この観点が必要だった」といった見落としを未然に防ぐことができています。この段階でのClaudeの支援はとても優秀です。 3. 検証段階 公式リファレンスの調査や参考文献、技術ブログなどの情報をリストとしてまとめてもらい、検証時に役立てています。またClaudeだけでなく、GitHub Copilotなどを使ってコーディング支援も受けています。自分で書いたコードに適切なコメントを追加してもらったり、解析コメントだけを出力してもらったりといった活用をしています。 4. 執筆段階 下書きをまるごと書いてもらうことはあまりしませんが、部分的なコラムなどAIに依頼しても問題ない部分を切り分けて書いてもらっています。また、私がよく使うのは、とりあえず書いた内容の誤字脱字の修正や文体の統一です。気分によって表現方法が異なってしまうことがよくあるので、そういった部分の修正を主にClaudeにお願いしています。さらに、ブログ内に登場する図の作成にもClaudeを活用しています。 5. 構成品質チェック 別ブログの評価やファクトチェック、技術ブログとしての質、トレンド性やSEO観点での優位性などをAIに判定してもらっています。つまり、私のブログを最初に読むのはAIだと言っても過言ではありません。この「生成AIを第一読者にする」というアプローチについては、「 【実践解説】技術ブログ品質チェック術|Gemini Deep Researchで5分検証 」で詳しく書いているので、ぜひ見てください。 2025-07-07 【実践解説】技術ブログ品質チェック術|Gemini Deep Researchで5分検証 このサムネイル、少し胡散臭いセミナーっぽくて、個人的には気に入っています。 比較で見る変化 「比較で見る変化」と題していますが、これは皆さんの目で実際に確認していただければと思います。2022年、2023年、2024年、そして2025年に書いたブログから、当時の私が力を入れて書いたものをまとめていますので、ぜひ読んでいただければと思います。 2022:Google Apps Script 簡易API編 初心者向け 2023:YouTube APIを使って自動で自分の動画の分析情報を収集する【GAS】 2024:プロジェクト開発手法を解説:ウォーターフォール、アジャイル、MVPの特徴 2025:Azure SWA×Next.js認証API統合を実践解説【DevContainer〜本番まで】 具体的な数字については、投稿頻度にはコンテンツ量などの要素があり純粋な比較は難しいものの、変化は明らかです。ブログを見ていただくとわかりますが、2025年にAIを活用して書いているブログは内容がかなり充実しています。また、サムネイルもおしゃれになっています(これが使用ツールの変化か私のデザイン力向上かは置いておいて)。 ブログの構成も大きく変わりました。以前は章立てがざっくりしていたり情報が不足している部分がありましたが、最近は品質チェックを導入しているため、章立てが細かく分かれています。また、背景説明から問題提起、解決編へとスムーズに導く自然な文章構成になっています。 面白い例として、完全にAIに執筆してもらったブログも一件あります。私がシリーズ3本を1週間(合計約24時間)かけて書いたブログを入力として使い、文体を抽出したプロンプトを用意してAIに投げたところ、執筆時間わずか2分という驚異的なスピードでブログが完成しました。 体感の投稿頻度や質は上がっていますが、月ごとの比較では信頼性の高いデータを示すのが難しいです。PV(ページビュー)についても、以前は10人程度しか見られないブログもありましたが、トレンド性を意識することで大幅に改善しました。月間比較では10倍や100倍になったケースもあります(過去の私のブログが単に良くなかったという可能性が大です)。 品質の低いブログを公開前に止められる取り組みが効果を発揮しているのでしょう。思えば、昔の自分は全ての事柄をブログにアウトプットしていたので、狂気的ですよね。 現在の最適化された執筆環境 現在ではClaudeを活用することによって、ブログの作業を分割しても問題なくなりました。これはなぜかと言いますと、まず文体の調整や誤字脱字はすべてAIによって修正してもらえるので、どのタイミングで執筆をしたとしても一貫性を保てるようになりました。 そして、新しいフェーズとして、AIによる品質向上チェックを追加しています。また、執筆現場では、完全にきれいな文章を書くというよりも、まず文章に起こすということを意識しているので、執筆時間自体は今までと比べて大幅に短縮されています。企画・リサーチ・構成設計の部分もAIに助けてもらっているので、Claudeにアクセスできる環境さえあれば、作業を分割して進めることができるようになり、あらゆる場面で独立した作業が可能になっています。 また、Claude導入以前との大きな違いは、業務とそのアウトプットとしてブログを書いていた際の検証部分をAIに助けてもらうことで、スムーズかつ今までと比較にならないスピードでの検証が可能になりました。検証や調査も効率的に行えるようになったので、AIにできる部分は積極的に任せています。 最適化後の執筆プロセス(総時間:4-6時間) Phase 1: 企画・リサーチ(30分) Phase 2: 構成設計(30分) Phase 3: 執筆(2-3時間) Phase 4: 品質向上(1-2時間) 試験的取り組みとして、音声入力を活用してキーボードを叩かずに、言葉で発することで文章を文字起こしして、それをAIに補正してもらうような活動もしています。これをすることによって、まず指が疲れないのが最高ですね。長編のブログを書くと、すごく指が筋肉痛になるんじゃないかっていうぐらい酷使してしまうので、そういったところがなくなったというのは結構効果的ですね。これがいい感じにまとまったらブログとして出していきますので、お楽しみに。 各工程でのClaude活用(プレビュー) 各工程でのClaude活用(プレビュー)については、Claudeにどのようなプロンプトを投げかけ、具体的にどう活用しているのかをご紹介します。 企画段階での活用例 この使い方では、ブログを書く前に内容を詰めるための作業を行っています。こちらのブログは最終的に「 Claude調教術 暴走パターンを制御する3つのプロンプトテクニック 」というブログになりました。 プロンプト: プロンプトを書くのが苦手な方向けにブログを書こうと思っています。 Goal - ブログの仮タイトル - コンテンツの一覧の作成:ブログの分析者としてブログコンテンツの価値や差別化に関して、SEO観点から競合調査するための調査計画書の作成 Plan - ブログの各コンテンツを対話的に確認する - ブログのコンテンツに関する情報が足りていない場合は確認 - コンテンツからSEO的観点で追加コンテンツの提案 - ステップの終了確認:終了しない場合は、再度対話的に確認 出力例: プロンプトが苦手な方向けのブログ企画、素晴らしいアイデアですね!SEO観点からの競合調査も含めた包括的な計画を立てましょう。 仮タイトル案 「プロンプト初心者でも今日から使える!AI活用術ブログ」 ... 執筆段階での活用例(校閲と表現の統一) この使い方では、ブログの執筆中に使用しています。今もNotionの右側にはClaudeが開かれています。ここでは、校閲と表現の修正を行っています。 プロンプト: ブログのアウトラインと部分的に執筆した内容を送付します。 部分的に執筆したので、校閲と表現の修正をお願いします。 「執筆前整理」のセクションに関しては出力に含めなくてよいです。 何か懸念や疑問がありましたらチャットで簡素にまとめて質問してください。 - 添付1:ブログアウトライン - 添付2:執筆した部分的なブログ 出力例: ども!7月の追い込みが激しくて、いろんなものに追い回されている龍ちゃんです。まぁすべてを引き受けたのは自分なので自己責任ですが、調子に乗っていたなと反省しています。とはいえ、割と順調に片付いているので、余裕が出てきたからこその発言なんですけどね。 6〜7月は13本ほどブログを執筆していますね。そんなブログのお話を今回書いていこうと思います。前半は若干のポエムの可能性もあるので、流し見で見てもらえればと思います。 今回は「Claude活用で変わった技術ブログ執筆のお話」になります。それでは始めましょう。 ~~省略~~ 以下は実際の執筆中の画面です。 品質チェック段階での活用例 この使い方では、「 【実践解説】技術ブログ品質チェック術|Gemini Deep Researchで5分検証 」で解説している内容と似ています。受け取った評価をそのまま信じるわけではありません。ですが、最新リソースなどから複合的な観点での品質チェックを行ってくれます。 今回のプロンプトでは E-E-A-T とトレンド性という観点で評価を行ってもらっています。 プロンプト: 添付されたファイルから内容を把握して、技術ブログとして評価してください。 E-E-A-Tとトレンド性なども含めて評価をよろしくお願いします。 - 添付ファイル:完成したブログ 出力例: この技術ブログを分析して評価いたします。 全体的な評価 この記事はDevContainerを使用したStreamlit開発環境の構築について具体的で実用的な内容を提供しており、技術ブログとしての質は高いです。 各要素の評価 E-E-A-T(Experience, Expertise, Authoritativeness, Trustworthiness) Experience(経験): 8/10 実際の開発経験に基づいた内容 具体的な設定ファイルとコード例を豊富に提供 flake8とblackの設定競合問題など、実開発で遭遇する課題を解決 ~~~省略~~ 現在のツール使い分けと課題 現在のツール使い分けと課題について説明します。私が使用できるAIツールは、Notion AI、Gemini、Claude、そしてGitHub Copilotの4つです。 Notion AI → Claude への移行体験 これまでのツール活用としては、Notion AIとClaudeを使っていました。タイトルやメタディスクリプションはNotion AIで作成していましたが、現在はNotion AIからClaudeに完全に移行しています。 Notion AIの活用方法として、Notion上で執筆をしているため、初期の文法の誤りや日本語的な表現の誤り、文章構成などの部分で特に活用していました。そして最終的に構成した文章をClaudeにも入力して、全体的な修正を行っていました。つまり、Notion AIで部分的な修正を行いながら、最終的な調整はClaude側でやってもらうという使い分けをしていました。 Claude移行後は、技術的な正確性と構造化された対話の質が大幅に向上しました。特に技術ブログ執筆での精度が圧倒的に向上し、修正した内容をそのまま反映できるため、執筆環境においてはClaudeがとても魅力的です。 Gemini活用の現状と品質チェック手法 Geminiも使えるのですが、Geminiは「AIを第一読者にする」というブログで紹介したように活用しています。執筆段階でGeminiを使うことはほとんどなく、調査やブログの最終的な評価の部分でDeep Researchを活用しています。 執筆に関連する使い方としては、執筆後のブログコンテンツの評価やブログを書く前の調査という部分です。Geminiでできることは、Claudeでも同様のことができます。 Geminiの利点として、会社のワークスペースで契約していて単純に使い放題なのが強みです。一方で私はClaudeをProプランで契約していますが、5時間ごとに利用制限がある点が弱点です。そのため、日常的な調査はGeminiに任せ、より詳細なタスクはClaudeに任せるという切り分けを行っています。 Research機能の比較:Claude vs Gemini Deep Research ClaudeのResearch機能は2025年5月現在、日本、アメリカ、ブラジルのMax、Team、Enterpriseプランのユーザー向けにβ版として提供されています。私は「AIを第一読者にする」という手法を実践していますが、これはClaudeでも同じことをやっています。 ClaudeのResearch機能とGeminiのDeep Researchを同時に使い、両方のAIがOKと言えば概ねOKだろうと判断しています。つまり、第一査読者、第二査読者のような感じで使っています。 検索能力について、文章中で「600件検索」と記載しましたが、これは私の記憶違いの可能性があります。実際の検索件数については、使用する際の複雑さや設定によって変動するため、正確な数値比較よりも、出されたレポート結果の質が重要です。 体感的な違いとして、Geminiの方が出すレポートは非常に硬くて読みにくいです。一方、Claudeのレポートも硬さはありますが、レポート形式の指定ができるので、フォーマットを定義できる点でClaudeの自由度が高いと感じています。 各AIサービスの特徴と使い分け 各AIサービスにはそれぞれ強みと弱みがあり、得意な分野と不得意な分野があります。役割が重複する部分でも好みの差があります。 文体・表現の違い Gemini : 表現として非常に硬い文章を出力 Claude : 指定すれば柔らかい文体や特定のスタイルで表現 Notion AI : 入力ができる領域は限定的だが小さい範囲での修正の力は協力 GitHub Copilot の特化領域 GitHub Copilotはコードレビューやコード関連に特化している点が強みです。ClaudeもGeminiもコードを出力できますが、拡張機能で簡単に使えるGitHub Copilotは非常に便利です。特に設定なしでGitHubと連携できるので、GitHub上で何かサービスを使うならCopilotが優勢です。 単純に比較することは難しいですが、それぞれの得意分野を見極めながら活用していきたいと思います。今後のブログでは、Claudeで検証している内容がGeminiでもできるのかといった比較も触れていければと思います。 参考情報 Claude関連の公式リファレンス Claude Proには使用制限がありますか? | Anthropicヘルプセンター Claude Proの使用について | Anthropicヘルプセンター Claudeの最大プラン使用量について | Anthropicヘルプセンター Claude.aiでの研究の利用 | Anthropicヘルプセンター Research機能の公式発表 | Anthropic GitHub Copilot GitHub Copilot 公式ページ Google Workspace & Gemini Google Workspace 公式ページ Gemini for Google Workspace 今後の展望とシリーズ予告 今後の展望と課題 最近気になっているのは、Claudeでツール連携ができるようになった点です。これがもう少し使いやすくなれば、NotionやGitHubとも連携できるので、Claude上でアクセスするだけで様々なサービスに問い合わせができるようになるでしょう。 ただし、AIにすべてを委譲することでレート制限に達したり、課金が高額になるリスクもあるので、安全に運用できる仕組みも今後しっかりと設定する必要があります。 今後はブログの執筆だけでなく、PV数などの分析や、ブログコンテンツにさらに踏み込んだAI活用も模索していきたいと思います。当面の計画として、各プロンプトのテクニックをシリーズ形式で執筆していこうと考えています。 実際に現在取り組んでいる内容として、 Claudeでブログコンテンツからツイート(X)の投稿文を生成する検証も行っており、その結果もブログ に掲載しています。ブログ作成からSNS投稿、継続的な発信、校閲といった一連のプロセスを通して、情報発信という観点とAIの組み合わせによる新しい可能性を探っていければと思っています。 シリーズ記事の執筆予定 今後、シリーズとして執筆したブログは以下にまとめていきます。まあ、現状では一本も執筆していないので…。えっと、もしも更新が止まっていたら、X上でつついてください。 各記事で執筆する予定のコンテンツを箇条書きでまとめておきます。 第2回:品質向上編 技術的正確性の担保方法 読者体験最適化のテクニック 実際の改善事例と測定方法 第3回:構成・アウトライン編 論理的な構成設計のコツ 読者レベル別の調整方法 SEOを意識した構造化 第4回:SEO最適化編 技術系キーワード戦略 検索意図に合わせたコンテンツ設計 効果測定と改善サイクル 第5回:ビジュアル制作編 Artifacts活用の図解作成 技術概念の可視化テクニック 読者理解を深める工夫 第6回:サムネイル・デザイン編 クリック率向上のデザイン戦略 A/Bテストによる最適化 継続的な改善プロセス 読者への呼びかけ 技術ブログの執筆に取り組んでいる皆さんにとって、今回紹介したClaude活用法が少しでも参考になれば幸いです。AI技術は日々進歩しており、新しい活用法も次々と生まれています。 実際に試してみて、うまくいった方法や課題があれば、ぜひコメントやSNSで教えてください。皆さんの体験談も今後の記事に反映していきたいと思います。 次回の記事もお楽しみに!そして、更新が止まっていたら遠慮なくつついてくださいね。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude×技術ブログで執筆環境が激変!次世代AI協働ワークフロー解説 first appeared on SIOS Tech. Lab .
アバター
挨拶 ども!最近はAI関連からInfrastructure as Code(IaC)などにも入門して幅広いブログを執筆している龍ちゃんです。最近はブログ執筆にAIを導入して、執筆スピードと質が上がっているような気がして楽しいですね。7月にまとめた内容は8月の初頭に45分セミナーにまとめるので、激しめに検証を進めています。 さて今回は「Stremlit」のお話になります。最近ふんわりとStremlitを書く機会が増えたので、DevContainerで開発する環境構築をまとめていこうと思います。 Stremlit開発環境をDevContainerで作成する 今回は、開発環境を使いまわす可能性を考慮してGitに環境を上げています。もし使いたい方がいれば、 リポジトリをクローン して利用してください。 ディレクトリ構成 今回作成する環境のディレクトリ構成になります。venvなどでも構築できるように .devcontainer 内でコンテナの定義をすべて収めています。リポジトリのトップからマウントすることで、コンテナ内でGit操作をすることができます。 . ├── .devcontainer │ ├── Dockerfile │ ├── compose.yml │ └── devcontainer.json ├── .env ├── .gitignore ├── README.md ├── requirements.txt ├── sample.env ├── setup.cfg └── src └── main.py Dockerfile ベースイメージは、 Microsoftが出しているPython 3.11のベースイメージ を利用しています。利点として、以下の二点があります。 Gitが標準で搭載されている vscodeユーザーがデフォルトで作成されている(非rootユーザー) コード解析・フォーマッター「flake8」「black」が標準搭載 Dockerfile内で行っている処理は、pipのアップグレードのみになります。 # Use the official Microsoft Python image (Debian Bullseye based) FROM mcr.microsoft.com/devcontainers/python:1-3.11-bullseye # Set the working directory WORKDIR /home/vscode/python # Install Python packages that are commonly used RUN pip install --upgrade pip # Keep the container running CMD ["sleep", "infinity"] compose.yml 将来的な拡張(データベース等)を考えて、composeファイルで定義しています。リポジトリのルートディレクトリからマウントすることで、仮想環境内からGitコマンドを直接実行することができます。 Streamlitでデフォルトで公開されるポート(8501)を接続しています。 services: python: build: context: . dockerfile: ./Dockerfile tty: true volumes: - type: bind source: ../ target: /home/vscode/python restart: always ports: - "8501:8501" devcontainer.json 今回作成するファイルの中で最も記述量が多いファイルになります。基本的にコピー&ペーストで大丈夫です。設定している主要項目としては以下になります。 拡張機能の追加 開発環境設定 開発用feature(Azure CLI・GitHub CLI) 開発環境設定では、pylintを無効化してflake8を有効化するように設定しています。ファイルをセーブしたタイミングでリンターとフォーマッターが実行されるように設定しています。 { "name": "Streamlit", "service": "python", "dockerComposeFile": "./compose.yml", "workspaceFolder": "/home/vscode/python", "shutdownAction": "stopCompose", "customizations": { "vscode": { "extensions": [ "ms-python.python", "ms-python.black-formatter", "ms-vscode.vscode-json", "ms-python.flake8" ], "settings": { "python.defaultInterpreterPath": "/usr/local/bin/python", "python.formatting.provider": "black", "terminal.integrated.defaultProfile.linux": "bash", "python.linting.enabled": true, "python.linting.pylintEnabled": true, "python.linting.lintOnSave": true, "python.linting.flake8Enabled": true, "": { "editor.formatOnSave": true, "editor.defaultFormatter": "ms-python.black-formatter" } } } }, "features": { "ghcr.io/devcontainers/features/azure-cli:1": {}, "ghcr.io/devcontainers/features/github-cli": {} }, "forwardPorts": [ 8501 ], "portsAttributes": { "8501": { "label": "Streamlit", "onAutoForward": "notify" } }, "postCreateCommand": "pip install -r requirements.txt", "remoteUser": "vscode", "updateRemoteUserUID": true } featureとしてAzure CLIとGitHub CLIをインストールしています。これは将来的にデプロイ先をAzure Web Apps on Containerでデプロイすることを考慮して設定しています。 フォーマット&リンター設定 前段の設定のみで開発を進めることができますが、一転問題があります。flake8とblackのデフォルトの設定が競合してしまい、問題が発生します。flake8かblackのどちらかに設定を合わせる必要があります。 setup.cfg ファイルを作成して、以下の内容をコピー&ペーストすることで設定することが可能です。 [flake8] max-line-length = 88 extend-ignore = E203, W503, E501 exclude = .git, __pycache__, .venv, venv, .env, env Stremlitのデモ ここまでで開発環境は整備することができたので、Stremlitで簡易的なページを作成を進めていきます。 まずは、Stremlitの機能をインストールをしましょう。 # stremlitのインストール pip install streamlit # requirements.txtにインストールしたファイルの設定 pip freeze > requirements.txt src > main.py を設定して以下のファイルをコピー&ペーストをしてください。 import streamlit as st import pandas as pd import numpy as np st.set_page_config(layout="wide") st.title("🚀 Streamlitの素晴らしい利点") st.write( "Streamlitを使えば、データサイエンスのプロジェクトを瞬く間にインタラクティブなWebアプリに変えることができます。" "もう複雑なWeb開発の知識は必要ありません!" ) st.subheader("💡 利点1: 圧倒的な手軽さ") st.write( "Pythonの知識だけで、データスクリプトから直接Webアプリを作成できます。HTML、CSS、JavaScriptなどの" "フロントエンドの知識は一切不要です。これにより、開発時間を大幅に短縮できます。" ) st.code( """ import streamlit as st st.title("こんにちは、Streamlit!") st.write("これはたった数行のコードです。") """ ) st.subheader("⚡ 利点2: 迅速なプロトタイピング") st.write( "コードを変更するたびに、アプリがリアルタイムで更新されます。これにより、アイデアをすぐに形にし、" "フィードバックを得ながら反復的に開発を進めることができます。まるでライブコーディングをしているようです!" ) st.info( "💡 ヒント: このページを保存して、Streamlitを実行したままコードを編集してみてください。" ) st.subheader("📊 利点3: 美しいデータ可視化が簡単に") st.write( "Matplotlib, Plotly, Altairなどの既存のPython可視化ライブラリとシームレスに統合できます。数行のコードで" "インタラクティブなグラフやダッシュボードを作成できます。" ) # サンプルグラフの表示 chart_data = pd.DataFrame(np.random.randn(20, 3), columns=["a", "b", "c"]) st.line_chart(chart_data) st.bar_chart(chart_data) st.subheader("🔄 利点4: インタラクティブなウィジェット") st.write( "スライダー、ボタン、テキスト入力など、多様なウィジェットを簡単に組み込むことができます。" "これにより、ユーザーがデータと対話できる動的なアプリケーションを作成できます。" ) # インタラクティブなデモ value = st.slider("スライダーを動かしてみてください", 0, 100, 50) st.write(f"現在の値: {value}") button_clicked = st.button("ボタンを押してみてください") if button_clicked: st.success("ボタンが押されました!") user_text = st.text_input("ここに何か入力してください", "Streamlitは最高!") st.write(f"あなたが入力した内容: {user_text}") st.subheader("☁ 利点5: デプロイのしやすさ") st.write( "Streamlit Community Cloudを使えば、数クリックでアプリをWebに公開できます。また、Dockerなどの" "コンテナ技術と組み合わせることで、より柔軟なデプロイも可能です。" ) st.video("https://www.youtube.com/watch?v=NtLCVE9hwb8") # Streamlitの紹介動画(例) st.write("---") st.markdown( "### まとめ\n" "Streamlitは、データサイエンティストやアナリストがアイデアを迅速に具現化し、" "それをインタラクティブな形で共有するための強力なツールです。" "ぜひご自身のプロジェクトで試してみてください!" ) st.balloons() ファイルを作成したら、以下のコマンドで起動することができます。 streamlit run src/main.py デフォルトで http://0.0.0.0:8501 が立ち上がって真っ白な画面が立ち上がってもあせらなくて大丈夫です。 http://localhost:8501 にアクセスしてみてください。以下の画面が表示されれば成功です。 まとめ 今回は、StreamlitをDevContainerで開発する環境構築について詳しく見てきました。DevContainerを使うことで、チーム内での開発環境統一や、プロジェクトの引き継ぎがスムーズになりますね。 特に今回のポイントとしては、MicrosoftのPython 3.11ベースイメージを使うことで、Gitやコード解析ツールが標準搭載されている点が大きなメリットでした。また、flake8とblackの設定競合問題も、setup.cfgファイルで解決できることがわかりました。 Streamlitは本当に手軽にデータアプリケーションを作成できる素晴らしいツールです。今回構築した環境なら、コードを変更するたびにリアルタイムで結果を確認できるので、開発効率が格段に向上します。 皆さんも、ぜひこの環境でStreamlitアプリケーション開発にチャレンジしてみてください!GitHubリポジトリも公開しているので、クローンして即座に開発を始められます。 次回は、このStreamlit環境をAzure Web Apps on Containerでデプロイする方法について解説予定です。お楽しみに! こちらのブログ「 Infrastructure as Code実践:Azure SWA×Bicep×GitHub Actions 」と同様の手順でBicepで環境作成からAzureへのデプロイまで目指していきます。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post DevContainerでStreamlit開発を始める方法:Docker+VSCode first appeared on SIOS Tech. Lab .
アバター
はじめに ども!最近はAIを使った自動化に注目している龍ちゃんです。実は最近、SIOSテックラボのX担当者もやっているんですが、投稿頻度によっては半分ぐらい自分のブログ宣伝をしているので、ほぼ私物になっています。 今回はそんな問題を解消してくれるプロンプトを紹介していきたいと思います。ただ作るだけではなく、簡易的なプロンプトから始めて、詳細プロンプトに落とし込んだ過程を含めて、検証の結果とともにブログ記事にできれば良いなと思っています。 この記事では以下の内容をお伝えします: X(旧Twitter)投稿の自動化ニーズの高まり 本記事の目的:プロンプトの違いによる投稿品質の比較検証 検証方法の概要 なぜAI生成に頼ったのか ブロガーの悩み ブログの宣伝って難しいですよね。この記事でわかることを正確に伝えるという作業が必要になります。自分が執筆したブログであれば、この点は簡単ですよね。 ですが、自分以外の人間が、自分のあまり知らない技術領域について書いた技術ブログの宣伝をするのは「何を書いたらいいのかわからない」「読むのも若干辛い」みたいなことが発生します。そして、これがエンジニアでなければ、まさにそこに広がるのは宇宙なわけですね。 そこで、過去に投稿した投稿に合わせてしまったり、当たり障りのない文章が作られるというのが日常茶飯事になっています。そして、エンジニアが文章を考えるとですね、マーケティング的視点があるわけではなく、宣伝の手法に詳しいわけでもなく、なんとなくいつも使っているXのような文面で投稿してしまうという問題もあります。 結論として、Xの投稿文を考えるのは難しいということですね。 具体的な悩みとしては: X投稿を考えるのが面倒 何を書けばいいかわからない 毎回同じような投稿になってしまう 記事は書けるけど、それをどう宣伝していいかわからない AI生成への期待 そこで生成AIの出番です。あわよくば、生成AIにブログのコンテンツを投げたら、Xの投稿文を作ってもらえると嬉しいですよね。記事のコンテンツの内容に基づいて、コピー&ペーストするだけで投稿してくれたら嬉しいです。 その文章がマーケティング的視点が入っていて、最適化されている文章だと、なお良いな。全て自動化してくれて、宣伝も自動化してくれるとなお良いという現状です。この点は夢物語です… そんなことを考えていたのですが、最近契約したClaudeで、そんなことができそうだなぁということでやってみたらできたという流れです。 簡単なプロンプトから詳細プロンプトまで実験をしてみたので、その結果を含めて今回得られた知見についてお話ししていきます。 期待していたことは: とりあえず何か投稿文を作ってほしい 記事の内容を要約してほしい できれば修正なしでそのまま投稿したい 検証実験の設計 ここでは、実験の過程について、検証をした過程について説明していきます。 実験条件 実験条件としては、 同一のURL に対して行います。これに対して簡易プロンプトと詳細プロンプトで、それぞれ3パターンずつプロンプトを投げて実験を行いました。 こちらの生成結果はGistにまとめています 。リストを開いて右に並べながら読んでいただけるとわかりやすいかと思います。 プロンプトパターン 検証するにあたって、簡易プロンプトは以下のような実装をしています。言われると簡単な指示だけを与えているものなので、とても簡単なものです。 比較して詳細プロンプトは全体300行になる比較的大きなプロンプトになっています。使用方法としては、URLを入力として与えるだけで、システムプロンプトが自動的に起動し、生成物を作ってくれます。 簡易プロンプト :基本的な指示のみ 詳細プロンプト :具体的な要件と制約を明記 簡易プロンプトで試してみた結果 最初に試したプロンプト https://tech-lab.sios.jp/archives/48160 このURLの内容からX投稿を作成してください 実際にできた投稿文 下に実際に生成された事例を示します。実験は計3回行い、20個の投稿文が生成されました。結果としてURLが含まれてないもの、文字数がXの投稿制限に引っかかるものなど、生成物をそのまま投稿として扱うことができないようなものが生成されてきました。 一応Xは課金をしているので、280文字以上は投稿できるんですが、それではリンクカードが表示されないという問題があったため、280文字以内での投稿というのを心がけています。 ですが、URLから記事の内容を取得して、記事に基づいた投稿文は生成してくれました。多少人の手を加えれば充分投稿できるようなものになっています。 # 良い例 ``` 今すぐ使えるClaude制御プロンプト集📝 🚫 Artifact生成を止めたい時 「成果物を作らずに、考え方だけ教えて」 「禁止事項:Artifact生成」 💬 軽い回答が欲しい時 「3行くらいで説明して」 「一言でどう思う?」 🔄 スレッド継続したい時 「今までの議論を引継ぎ資料にまとめて」 他にも使えるテクニック満載👇 https://tech-lab.sios.jp/archives/48160 #Claude #プロンプト集 ``` # 悪い例 ## URLがない ``` Claudeに「転職活動どうしよう」って聞いたら →12段階の転職完全ロードマップ作られた😂 軽い質問のつもりが教科書レベルの資料に... Claudeは「めちゃくちゃ賢くてやる気のある赤子」 明確な指示で制御するコツをまとめました📝 #Claude #AI #あるある ``` ## 280文字突破 ``` 🎯 Claude調教の実績報告 k8s完全初心者から ↓ 人間主導学習プロセスで DaemonSet運用レベルまで到達! ポイント: 1. 大枠把握からスタート 2. 段階的深堀り質問 3. 理解度確認を必ず実施 4. 自分の言葉で例え話 Claudeを個人チューターとして最大活用🚀 詳しい学習フロー👇 Claude調教術|暴走パターンを制御する3つのプロンプトテクニック #Claude学習法 #スキルアップ ``` 何が物足りなかったか ここでは生成された生成物の何が足りなかったのかについて考えていきます。 やはり1番重要なのはURLです。URLがないのでどこに飛べばいいのかわからないという投稿は、さすがにブログの宣伝投稿としては不適切です。次に硬すぎる文体ですね。文体としてはもう少し柔らかいもの、簡単な文章という方が良いかと思います。そして、あわよくば、人間が投稿したように模倣していただければいいんじゃないかなぁと思います。 次にハッシュタグですね。ハッシュタグを生成するのは、正直、僕自身もどうやればいいのかわからないので、この辺の効果測定は難しいなと思います。ですが、ハッシュタグがないものは、さすがに「手抜き」なんじゃないかなと思ったりもしちゃうわけですね。 まぁ簡単に言うと、手動による見直しが見込まれました。 具体的な問題点: 長すぎる(文字数オーバー) 堅すぎる文体 「これ投稿して大丈夫?」という不安 結局自分で書き直すことに… 詳細プロンプトでの生成結果 使用したプロンプト例 全文は長いので、プロンプトのすべてをここに配置することができませんが、要点のみ最後に付録として添付しておきます。 文字数制限 トーン指定 ハッシュタグ要件 エンゲージメント要素 構造化された出力形式 生成結果 # ブログの評価 **総合評価:A(高品質)** **技術的正確性:⭐⭐⭐⭐⭐(5点満点)** **実装レベル:本格実装** **対象読者:中級者〜上級者** **実用性:⭐⭐⭐⭐⭐(5点満点)** ## 主要な技術要素 - Claude 4(Sonnet)を使用したプロンプトエンジニアリング実践 - 3つの制御テクニック:成果物制御、会話継続、ペース調整 - システムプロンプト設計とArtifact生成制御 - スレッド管理とトークン最適化手法 ## 記事内の具体的な数値・効果 - **記事内で実証済みの制御手法**:3つの明確なテクニック体系 - **実際の学習事例**:Kubernetes習得プロセスの詳細記録 - **具体的改善例**:300行→50行程度への成果物サイズ制御 - **問題解決実績**:スレッド上限問題の具体的回避策 ## 注意事項・制限 - 記事内で言及されている制限事項:スレッドが長くなると効果が薄れる傾向 - 実装時の注意点:プロンプトの詳細指定が重要 - Claude固有のノウハウ:他のLLMでは効果が異なる可能性 ## 誇張表現検証結果 - **初回生成での誇張なし**:記事内の実証済み手法のみを使用 - **修正内容**:なし(記事内容と完全一致) - **最終版での整合性確認結果**:✅ 全パターンで記事内容と整合性確認済み --- # ブログの投稿パターン ## Aパターン:効果重視・数値訴求型 **投稿文:** ``` Claude調教術|暴走パターンを制御する3つのプロンプトテクニック 🚀 Claude暴走を3つのテクニックで完全制御! 📝 成果物制御・会話継続・ペース調整で理想的なAI活用 ⚡ 技術スタック: #Claude #プロンプトエンジニアリング 🔧 300行→50行への成果物最適化も実現 #AI #LLM #効率化 #エンジニア ``` - **記事内容との整合性:** ✅ 確認済み - **投稿推奨時間:** 火曜日 21:00-22:00(エンジニア活動ピーク・技術学習時間) ## Bパターン:課題共感・解決提案型 **投稿文:** ``` Claude調教術|暴走パターンを制御する3つのプロンプトテクニック 😰 Claude君止まって!大規模Artifact生成で困ってませんか? 💡 明確な指示とGoal設定で暴走制御が可能になります ⚡ 成果物制御・スレッド管理・段階的学習の3技法 🎯 スレッド上限問題も引継ぎ資料で解決 #Claude #プロンプトエンジニアリング #AI #LLM ``` - **記事内容との整合性:** ✅ 確認済み - **投稿推奨時間:** 水曜日 12:00-13:00(昼休み・課題解決ニーズが高い時間) ## Cパターン:技術トレンド・学習促進型 **投稿文:** ``` Claude調教術|暴走パターンを制御する3つのプロンプトテクニック 🔥 2025年のプロンプトエンジニアリング実践術 📚 Claude制御テクニック3選の詳細解説 ⭐ 人間主導の学習プロセスでスキルアップ加速 🚀 Kubernetes習得事例で段階的学習の威力を実証 #プロンプトエンジニアリング #Claude #AI学習 ``` - **記事内容との整合性:** ✅ 確認済み - **投稿推奨時間:** 金曜日 18:00-19:00(週末学習準備・新技術トレンド確認時間) --- ## 投稿時間最適化の根拠 ### エンジニア向け最適投稿時間 - **Aパターン(火曜21:00)**:技術習得意欲が最も高い時間帯 - **Bパターン(水曜12:00)**:実務課題解決のニーズが高い昼休み - **Cパターン(金曜18:00)**:週末の技術学習計画を立てる時間 ### ハッシュタグ効果分析結果 - **#Claude** - 2025年最も注目されるAI技術キーワード - **#プロンプトエンジニアリング** - エンジニア間で急成長中のスキル分野 - **#AI** **#LLM** - 幅広いリーチを狙える基本タグ - **半角英語優先**:バイト効率とトレンド性を両 生成結果の分析 実際の生成物について見ていきましょう。プロンプトで3パターン、それぞれ別の観点から投稿文を生成しているので、1つの生成物に3つの投稿文(計9件)が生成されています。 文字数制限も280文字以内に収まるように計算されて最適化されています。僕なら絶対考えつかないようなキーワードで、ブログが表現されており、体感としてはこれは充分投稿に値するだろうと思える内容でした。 また、ハッシュタグに関しても、検索から適切なハッシュタグを検索しているというステップを作成して検索して策定しています。ですが、正直この辺の判断は投稿していきながら、Xで詳細な分析を行わないとわからないので、正確なことは言えません。 改善された点: 実際の生成文例 文字数の適切さ 魅力的なキャッチフレーズ 関連性の高いハッシュタグ エンゲージメント促進要素 そのまま使用可能性の評価 実際にこちらのプロンプトは2週間ほど運用しているのですが、問題としては多少誇張表現を使ってしまう部分、記事の内容に基づかないことなどはあるものの、比較的問題のない投稿文になっています。 8割程度はそのままコピー&ペーストで投稿を行うことができています。また、微調整に関しても、問題点をチャットで指摘をすることで、再生成を行ってくれるため、人間の作業としてはURLを選択して投稿文を確認し、問題があったら指摘するという簡単なフローに変わりました。 結果を比べてみて 簡易プロンプトと詳細プロンプトでそれぞれ比較を行い、分析考察を行っていきます。 簡易プロンプトの課題 簡易プロンプトの問題点としては、出力の品質が安定していないものが挙げられます。やはりXの投稿文として求めているスタイルというものは人それぞれ違いますが、今回は僕が求めているスタイルに合うものは合計の出力数の2割程度しかありませんでした。 そして毎回出力によってパターンが異なるため、確率の低いガチャを回してるようで楽しくなっちゃいましたね。 また、人間が修正する手間が必ず発生しているため、アイディアとしては充分使えますが、投稿文をそのまま生成するという当初の目的から外れてしまいます。 ガチャ要素 :毎回違う結果で安定しない 「今日は良い投稿文、昨日はイマイチ」の繰り返し 結果が予測できないので、結局人間が判断・修正が必要 時間かけて修正するなら最初から自分で書いた方が早い 詳細プロンプトの安定性 詳細プロンプトでは、実行計画をプロンプトに埋め込んでいるため、すべての生成の過程で統一のステップを踏むことにより安定性があります。完全に保証されているわけではないんですけどね。 実際にブログのコンテンツ内容に基づいていると判断していても、人間の目で確認したところ事実に基づいていない投稿文が存在していました。 ですが、3パターンの投稿文が生成されるので、パターンの1つは使えるパターンがあるように感じています。品質チェック項目があるため、文字数280文字を超えることはほぼなく、投稿文としても記事の内容に基づいていることが多いです。 今回で言うならば、再生成を行ったのは全体中1つだけでした。 ほとんどコピペでの運用が実現しています。 一定品質の保証 :どのパターンも安心して使える 狙った通りのアプローチで生成される ほぼそのままコピペで投稿可能 「今日もちゃんとした投稿文ができた」安心感 視覚的比較用ルーレット #prompt-gacha-container { font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Yu Gothic', sans-serif; background: linear-gradient(45deg, #ff9a9e 0%, #fecfef 50%, #667eea 100%); border-radius: 20px; padding: 30px; max-width: 900px; margin: 20px auto; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); } .gacha-title { text-align: center; color: #2c3e50; margin-bottom: 10px; font-size: 2rem; font-weight: 700; text-shadow: 2px 2px 4px rgba(0,0,0,0.1); } .gacha-subtitle { text-align: center; color: #7f8c8d; margin-bottom: 30px; font-size: 1.1rem; } .gacha-machines { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 30px; } .gacha-machine { background: linear-gradient(145deg, #ffffff, #f0f0f0); border-radius: 20px; padding: 25px; box-shadow: 10px 10px 20px rgba(0,0,0,0.1), -10px -10px 20px rgba(255,255,255,0.5); text-align: center; position: relative; overflow: hidden; } .machine-title { font-size: 1.3rem; font-weight: bold; margin-bottom: 15px; color: #34495e; } .simple-title { color: #e74c3c; } .detailed-title { color: #27ae60; } .gacha-display { background: #2c3e50; border-radius: 15px; height: 120px; margin: 20px 0; display: flex; align-items: center; justify-content: center; font-size: 3rem; color: white; border: 5px solid #34495e; position: relative; overflow: hidden; } .gacha-button { background: linear-gradient(145deg, #3498db, #2980b9); border: none; border-radius: 50px; color: white; font-size: 1.1rem; font-weight: bold; padding: 15px 30px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 10px 20px rgba(52, 152, 219, 0.3); margin: 10px; } .gacha-button:hover { transform: translateY(-3px); box-shadow: 0 15px 25px rgba(52, 152, 219, 0.4); } .gacha-button:active { transform: translateY(-1px); } .simple-button { background: linear-gradient(145deg, #e74c3c, #c0392b); box-shadow: 0 10px 20px rgba(231, 76, 60, 0.3); } .simple-button:hover { box-shadow: 0 15px 25px rgba(231, 76, 60, 0.4); } .detailed-button { background: linear-gradient(145deg, #27ae60, #229954); box-shadow: 0 10px 20px rgba(39, 174, 96, 0.3); } .detailed-button:hover { box-shadow: 0 15px 25px rgba(39, 174, 96, 0.4); } .stats-display { margin-top: 15px; font-size: 0.9rem; color: #7f8c8d; } .success-rate { background: rgba(255, 255, 255, 0.8); border-radius: 10px; padding: 10px; margin-top: 15px; } .rate-bar { height: 20px; border-radius: 10px; margin: 5px 0; position: relative; overflow: hidden; } .simple-rate { background: linear-gradient(90deg, #e74c3c 20%, #95a5a6 20%); } .detailed-rate { background: linear-gradient(90deg, #27ae60 80%, #95a5a6 80%); } .rate-text { position: absolute; width: 100%; text-align: center; line-height: 20px; font-weight: bold; color: white; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); } .spinning { animation: spin 0.1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .results-section { background: white; border-radius: 15px; padding: 25px; margin-top: 20px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); } .results-title { text-align: center; color: #34495e; margin-bottom: 20px; font-size: 1.3rem; font-weight: 600; } .trial-results { display: flex; justify-content: space-around; margin: 20px 0; } .trial-group { text-align: center; } .trial-icons { font-size: 1.5rem; margin: 10px 0; line-height: 1.8; } .trial-label { font-weight: bold; margin-bottom: 10px; } .simple-label { color: #e74c3c; } .detailed-label { color: #27ae60; } .conclusion { background: linear-gradient(135deg, #ffeaa7, #fab1a0); border-radius: 15px; padding: 20px; margin-top: 20px; border-left: 5px solid #e17055; } .conclusion-title { color: #d63031; font-weight: bold; margin-bottom: 10px; font-size: 1.1rem; } .conclusion-text { color: #2c3e50; line-height: 1.6; } @media (max-width: 768px) { .gacha-machines { grid-template-columns: 1fr; } .trial-results { flex-direction: column; gap: 20px; } } プロンプトガチャマシン 簡易プロンプト vs 詳細プロンプト – あなたならどっちを選ぶ? 簡易プロンプトマシン ガチャを回す! 成功率 20% まずは試してみよう! 詳細プロンプトマシン ガチャを回す! 成功率 80% まずは試してみよう! 10回試行の結果比較 簡易プロンプト 成功: 2回 / 失敗: 8回 詳細プロンプト 成功: 8回 / 失敗: 2回 /* * ============================================ * 🎯 エンジニアの皆さん、お疲れ様です! * ============================================ * * まぁ真っ先にソース見るよな〜さすがやわ😏 * * 予想通りの行動パターン: * 1. ページ見る * 2. 「面白そう」 * 3. F12をポチッ * 4. このコメント発見 ← 今ココ * * きっと君なら: * - setInterval()で自動化考えてるでしょ? * - while文で無限ループ組みたいでしょ? * - 「これ、API化できそう」とか思ってるでしょ? * * でもさ、その技術力で詳細プロンプト書いた方が * 圧倒的に早いんじゃない?😄 * * ============================================ */ // 簡易プロンプトの結果パターン const simpleResults = [ { emoji: '😅', text: '文字数オーバー...', success: false }, { emoji: '😅', text: 'URLが含まれてない...', success: false }, { emoji: '😅', text: '堅すぎる文体...', success: false }, { emoji: '😅', text: 'ハッシュタグなし...', success: false }, { emoji: '😅', text: '当たり障りのない文章...', success: false }, { emoji: '😅', text: '投稿して大丈夫?な内容...', success: false }, { emoji: '😅', text: '結局書き直しが必要...', success: false }, { emoji: '😅', text: 'なんか違う...', success: false }, { emoji: '✅', text: '使える!(でも微調整必要)', success: true }, { emoji: '✅', text: 'まあまあ良い感じ!', success: true } ]; // 煽り文句パターン(10回毎に表示) const gachaMessages = { 10: "🔥 まだまだいけるぜ!次は当たるかも!", 20: "💪 その調子!あと少しで大当たり!", 30: "⚡ 諦めるな!次こそは神引き!", 40: "🌟 ここからが本番だ!運気上昇中!", 50: "🎯 半世紀到達!もう後には引けない!", 60: "🚀 60回突破!これぞガチャ道!", 70: "💎 レジェンド級の粘り!次で確変!", 80: "👑 80回の壁を越えた!君はガチャ王だ!", 90: "🌈 90回...もはや伝説の領域!", 100: "🎊 100回達成!君こそ真のガチャマスター!", 200: "🎪 200回...正気か?でも尊敬する!", 300: "🤖 300回...そろそろスクリプト組んだ?", 400: "⚙ 400回...setInterval使ってるでしょ?", 500: "🔧 500回...while(true)やめなさい", 600: "💻 600回...もうconsoleで見てるでしょ?", 700: "👨‍💻 700回...君、エンジニアだね?", 800: "🐛 800回...これもうバグレポートだよ", 900: "⭐ 900回...GitHubにissue立てそう", 1000: "🎭 1000回...もし手作業やったら狂気の沙汰よ?暇やんw" }; // 詳細プロンプトの結果パターン const detailedResults = [ { emoji: '✅', text: 'そのままコピペ可能!', success: true }, { emoji: '✅', text: '完璧な280文字以内!', success: true }, { emoji: '✅', text: '魅力的なキャッチフレーズ付き!', success: true }, { emoji: '✅', text: '適切なハッシュタグ自動生成!', success: true }, { emoji: '✅', text: '記事内容を的確に要約!', success: true }, { emoji: '✅', text: 'エンゲージメント促進要素あり!', success: true }, { emoji: '✅', text: '今日もちゃんとした投稿文!', success: true }, { emoji: '✅', text: '狙った通りのアプローチ!', success: true }, { emoji: '😅', text: '少し誇張表現が...(軽微)', success: false }, { emoji: '😅', text: '微調整が必要かも', success: false } ]; let simpleCount = 0; let detailedCount = 0; let simpleSuccess = 0; let detailedSuccess = 0; function spinSimple() { const display = document.getElementById('simple-display'); const stats = document.getElementById('simple-stats'); // スピン効果 display.classList.add('spinning'); display.textContent = '🎲'; setTimeout(() => { display.classList.remove('spinning'); simpleCount++; // 10の倍数の時は煽り文句を表示 if (simpleCount % 10 === 0 && gachaMessages[simpleCount]) { const message = gachaMessages[simpleCount]; display.innerHTML = ` ${message} `; stats.textContent = `${simpleCount}回目の挑戦!まだまだ諦めるな!`; // 特別メッセージの時はconsoleにも出力 if (simpleCount >= 300) { const consoleMessages = { 300: "console.log('🤖 スクリプト組んでる?😏');", 400: "console.log('⚙ setInterval(() => { spinSimple(); }, 100); ←これでしょ?😂');", 500: "console.log('🔧 while(true) { spinSimple(); } やめなさい😅');", 600: "console.log('💻 もうconsoleで見てるでしょ?👀');", 700: "console.log('👨‍💻 君、絶対エンジニアだね!');", 800: "console.log('🐛 これもうバグレポートレベル!');", 900: "console.log('⭐ GitHubにissue立てる気でしょ?');", 1000: "console.log('\\n' + '╔══════════════════════════════════════════════╗\\n' + '║ 🎉 1000回達成記念 🎉 ║\\n' + '║ ║\\n' + '║ ⠀⠀⠀⠀⢀⣴⠟⠛⠛⠻⣦⡀⠀⠀⠀⠀⠀ ║\\n' + '║ ⠀⠀⠀⣰⠟⠁⠀⠀⠀⠀⠈⠻⣆⠀⠀⠀⠀ ║\\n' + '║ ⠀⠀⢠⡟⠀⠀⠀⠀⠀⠀⠀⠀⢻⡄⠀⠀⠀ ║\\n' + '║ ⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀ ║\\n' + '║ ⠀⠀⢿⡀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡿⠀⠀⠀ ║\\n' + '║ ⠀⠀⠀⠙⢦⣀⠀⠀⠀⠀⣀⡴⠋⠀⠀⠀⠀ ║\\n' + '║ ║\\n' + '║ 🏆 君こそ真のガチャマスター! 🏆 ║\\n' + '║ ║\\n' + '║ 🤔 でもさ...もし手作業やったら狂気よ? ║\\n' + '║ 😏 そのコードレビュー力で詳細プロンプト ║\\n' + '║ 書いた方が早いけどね〜 ║\\n' + '║ ║\\n' + '║ エンジニア大好きw ║\\n' + '╚══════════════════════════════════════════════╝\\n');" }; if (consoleMessages[simpleCount]) { eval(consoleMessages[simpleCount]); } } } else { const result = simpleResults[Math.floor(Math.random() * simpleResults.length)]; display.textContent = result.emoji; stats.textContent = result.text; if (result.success) simpleSuccess++; } updateStats('simple', simpleCount, simpleSuccess); }, 1000); } function spinDetailed() { const display = document.getElementById('detailed-display'); const stats = document.getElementById('detailed-stats'); // スピン効果 display.classList.add('spinning'); display.textContent = '🎯'; setTimeout(() => { display.classList.remove('spinning'); const result = detailedResults[Math.floor(Math.random() * detailedResults.length)]; display.textContent = result.emoji; stats.textContent = result.text; detailedCount++; if (result.success) detailedSuccess++; updateStats('detailed', detailedCount, detailedSuccess); }, 1000); } function updateStats(type, count, success) { const rate = count > 0 ? Math.round((success / count) * 100) : 0; const statsElement = document.getElementById(type + '-stats'); if (count > 0) { statsElement.innerHTML = `${statsElement.textContent} 試行回数: ${count}回 | 成功率: ${rate}% (${success}/${count}) `; } } Claudeの特性を理解した 詳細プロンプトを組んだとしても、1割程度は事実に基づかない投稿文が生成されるというのは盲点でした。特にプロンプトで評価フェーズを挟んでいても、評価フェーズを飛ばしてしまう場合があります。 また、指示の出し方によって、生成物がこんなにも変わってしまうというのは面白いですね。概念としては理解していたんですが、今まで「ギャル風に」とか「夜の蝶とおじさんの会話」みたいなふざけたプロンプトしか打ってこなかったので、これは面白い発見でした。 詳細プロンプトを作るだけで、今までXの投稿にかけていた時間が大幅に短縮されました。単純にプロンプトを使って生成するだけでも、従来あたり5分から10分程度かかっていたのですが、実働としては本当に1分で完了するものとなっています。 あとはこれがシステム化できれば、人間は生成物を確認するだけで良くなりそうです。この辺は作り込みが必要だなと思っています。まぁ僕はエンジニアなのでそういうシステムみたいなところは本職なところがあるんですけどね。 「Claudeは万能じゃない」ことがわかった 指示の出し方で全然違う結果になる 少し手間をかけるだけで劇的に改善 Claudeは「賢いけど指示待ち」な感じ 思わぬ落とし穴:トークン数の問題 実際に直面した問題点についてお話ししたいと思います。 Claudeの制限に引っかかる 最近では月曜日に1週間分の投稿をまとめて生成するのですが、6記事程度作成したらClaudeの制限に引っかかりました。 ClaudeのProプラン で使っているのですが、使用回数を使いつぶしてしまったようです。原因として考えられたのは、詳細プロンプトによって膨大な処理が行われているのではないかという点です。こちらを検証してみました。 犯人は詳細プロンプト?調査してみた こちらの処理は、APIで叩いているわけではないので、正式なトークン数がわかるわけではありません。なので、チャットで入力したトークン数、出力したトークン数の合計値を出してシミュレートしました。詳細プロンプトと簡易プロンプトでそれほど差がないことがわかりました。そして1番トークン数を消費していたのはweb_fetchで取得したHTMLでした。 web_fetchで取得したコンテンツの内訳 取得データ全体 : 約18,000-20,000トークン 実際の記事内容 : 約12,000-14,000トークン HTMLタグ・構造データ : 約4,000-6,000トークン プロンプト別の合計トークン数 簡易プロンプト : 約18,000~26,000トークン(平均:22,000トークン) 詳細プロンプト : 約23,000-29,000トークン(平均26,000トークン) 意外な事実が発覚 詳細プロンプトと簡易プロンプトにそこまで大差があるわけではないという事実が発覚しました。プロンプトの差としては5,000トークン程度でそんなに重いものではないという結論になりました。というか、HTML重すぎじゃん! 大きな差があるわけではない プロンプトの差:約5,000トークン程度 というかHTML重すぎじゃん! 本当の犯人はHTMLデータだった はい、本当の犯人はHTMLだったわけなんですけども、まぁこれって当然のことなんですよね。web_fetchで取得しているということは記事全体を取得しているわけであって、ヘッダーやフッター、メインのコンテンツに関係ないサブメニューなども全て取得しているわけです。それは余計な情報もついてきて重くなりますよね。 なので、記事の本文自体は大体半分程度に圧縮できると考えています。で、ここから面白いところなんですけど、 実は僕、以前にも遭遇しているんですよね。過去のシステムをGASを使ってリファクタリングしてプロトタイプを作ってやってみているんですけども、その時にも同様の問題に当たって、その時はHTMLから本文を抜き出すという処理をスクリプト化して実行していました。 なので、もしこのままプロンプトを使うというよりかは、本文だけを抜き出す工夫が必要になると考えています。システム化にあたっては、HTMLの処理部分は機械的にシステム化し、本来の生成と検証部分に注力してもらう方が効果的であると考えています。というかそうしないとトークン数がバカでかいシステムができてしまうのでヤバいですよね。 使えるプロンプトの作り方 私が学んだ基本ポイント はい、ここでは私が学んだ基本のプロンプトの作り方についてお話ししていきます。基本的なところとしては、実行計画はAIからの出力を安定化させるために重要な内容になっています。簡易プロンプトではAI自身に考えさせる余地が残っているため、自由な発想で自由な判断を行っていますが、実行計画として行動指針を立ててあげることによってAIの思考を誘導することができます。また、例示を載せることによって、そこを起点に考えてもらえるので、ワンショットラーニングやフューショットラーニングなども効果的です。 「何文字以内で」は絶対に入れる 「~な感じで」と雰囲気を伝える 「ハッシュタグも付けて」と具体的に指示 ダメな例も一緒に教える 詳細プロンプトで使った8つのテクニック 段階的実行 :複雑な処理をステップ分割 制約優先 :「〜してはいけない」を先に明確化 テンプレート化 :使い回せる構造を作る 検証組み込み :生成→チェック→修正の流れ 複数パターン :用途別に数種類用意 具体的例示 :曖昧な指示は避ける 自動実行 :条件を満たしたら勝手に動く 構造化出力 :毎回同じフォーマットで結果表示 プロンプト改善のコツ プロンプトの改善のコツとしては、定期的にプロンプトを修正しようとする意識を持つことです。今回付録で載せているプロンプトも実はもう5回ほどアップデートを重ねて調整しています。定期的に自分の求めているものと異なる場合、遠慮なくプロンプトを修正していくべきだと思います。 この点は、システムプロンプトをバージョン管理しておくことも1つの手だと思っています。はい、なので、GitやClaude Code、Gemini CLIでそういった環境を作るのもアリではないでしょうか。 まとめ 実験で証明された「プロンプト設計」の威力 今回の検証を通して、同じClaudeでも「どう頼むか」で全然違う結果になることが実証できました。簡易プロンプトでは8割の投稿文に人間の手直しが必要だったのに対し、詳細プロンプトでは8割がそのままコピペで使えるレベルまで改善しました。 特に印象的だったのは、詳細プロンプトで生成される投稿文の 安定性 ですね。「今日は当たり、昨日はハズレ」というガチャ要素がほぼなくなって、毎回安心して使える品質になったのは大きな収穫でした。 「AI丸投げ」から「AI協働」への転換 最初は「URLを渡したら勝手に完璧な投稿文を作ってくれる」なんて夢を見ていましたが、現実はそう甘くありませんでした。でも、これって逆に言えば 人間の工夫次第でAIのパフォーマンスを劇的に向上させられる ということでもあります。 AIを「万能の魔法」として期待するのではなく、「すごく優秀だけど指示待ちな部下」として適切にマネジメントすることで、期待以上の成果を得られることがわかりました。 見えてきた課題と現実的な解決策 トークン数の問題は正直、盲点でした。HTMLの重さが予想以上で、詳細プロンプトよりもweb_fetchで取得するデータの方がよっぽど「重い」という事実は面白い発見でしたね。 この問題に対しては、過去に GASでHTMLパースしてた経験 が活かせそうです。本文抽出を前処理で自動化すれば、トークン数を半分以下に圧縮できる見込みがあります。まさに「過去の自分、ナイス」って感じです。 2週間運用してみた正直な感想 実際に運用してみて、X投稿にかける時間が 5-10分から1分程度 まで短縮されたのは素直に嬉しいです。「投稿文を考えるのが面倒」「何を書けばいいかわからない」という悩みがほぼ解決されました。 ただし、1割程度は事実に基づかない投稿文が生成されることもあるので、「完全自動化」ではなく「半自動化」として運用するのが現実的だと感じています。人間の最終チェックは必須ですね。 これからプロンプトエンジニアリングを始める人へ もし皆さんも「Claudeに○○を任せたけど、思ったような結果が得られなかった」という経験があるなら、ぜひプロンプト設計を見直してみてください。今回の実験で分かったように、少しの工夫で劇的に改善することがあります。 特に重要なのは: 具体的な制約条件 を明記する(文字数、文体、必須要素など) 段階的な実行計画 をプロンプトに組み込む 複数パターン の出力で選択肢を増やす 定期的なプロンプト見直し で継続改善する 次のステップ:完全自動化への道 今後は、HTMLパースの自動化、プロンプトのバージョン管理、そして最終的にはシステム化まで進めていきたいと思っています。Claude Codeとかも使ってみたいですしね。 でも一番大切なのは、「AIと人間が協力して、お互いの得意分野を活かす」という視点だと思います。完全自動化が目標ではなく、 人間がより創造的な部分に集中できる環境 を作ることが本当のゴールなのかもしれません。 皆さんも、ぜひ自分なりのプロンプトエンジニアリングにチャレンジしてみてください。きっと「これは便利だ!」と思える瞬間があるはずです。 そして、良いプロンプトができたら、ぜひ社内やコミュニティでシェアして、みんなで効率化の輪を広げていきましょう! 付録:詳細プロンプト全文 ```markdown # エンジニア向けX投稿自動生成システム ## 🎯 概要 URLのみの入力で、記事品質評価から3パターンの投稿文まで自動生成する包括的システム。**リンクカード確実表示**を最優先に設計し、**記事内容の正確性検証**により誇張表現を排除。 ----- ## 🔄 自動実行プロセス ### **Phase 1: 記事分析・品質評価** #### **Step 1-1: 記事内容完全取得** ``` 実行内容: - web_fetchでブログ記事の完全コンテンツ取得 - 記事構造の解析(見出し、コードブロック、図表等) - 技術的内容の抽出と整理 - 具体的な数値・効果・事例の正確な記録 ``` #### **Step 1-2: 技術的正確性評価** ``` 評価項目: 【技術スタック正確性】 - 使用技術の最新性(2024-2025年基準) - 技術的実装の適切性 - セキュリティ・ベストプラクティスの考慮 【実装レベル判定】 - プロトタイプ / 基本実装 / 本格実装 / 企業レベル - コードの完成度・品質 - エラーハンドリング・例外処理の有無 【対象読者レベル】 - 初級者(学習中・1-2年目) - 中級者(実務経験3-5年) - 上級者(リーダー・アーキテクト級) - 専門家(特定分野のエキスパート) 総合評価:A(高品質)/ B(標準)/ C(要注意) ``` #### **Step 1-3: 記事価値・差別化ポイント抽出** ``` 分析内容: - 解決する具体的課題 - 既存解決策との違い・優位性 - 実用性・再現性の程度 - 学習コスト・導入難易度 - ビジネス価値・ROI - 【重要】記事内で言及されている具体的な効果・数値の正確な記録 ``` ### **Phase 2: ハッシュタグ効果分析** #### **Step 2-1: 技術分野別ハッシュタグ調査** ``` 検索戦略(2-4回のweb_search実行): 検索1: "[主要技術名] ハッシュタグ 効果的 X Twitter 2025" 検索2: "[技術領域] エンジニア ハッシュタグ トレンド 2025" 検索3: "[解決課題] 自動化 効率化 ハッシュタグ X" 検索4: "[関連ツール] [技術スタック] ハッシュタグ 人気" ``` #### **Step 2-2: ハッシュタグ効果性評価・選定** ``` 評価基準: - トレンド性(2024-2025年の話題性) - 技術特化度(エンジニアの検索意図との一致) - 関連性(記事内容との直接的関連度) - バイト効率(半角英語活用による節約効果) 選定ルール: - 6個以内 - 合計30-40バイト以内 - 半角英語優先(#Azure, #API, #OSS等) ``` ### **Phase 3: 投稿文作成(3パターン)** #### **🚨 リンクカード表示最優先ルール** ``` 【必須適用事項】 1. URLを投稿文の冒頭に単独行で配置 2. URL前後にスペース・改行で明確分離 3. 240文字以内でもURL優先配置 4. 絵文字・ハッシュタグはURL後に配置 5. 他のテキストや絵文字と混在させない 【配置テンプレート】 https://example.com [投稿文本文] #ハッシュタグ ``` #### **Aパターン: 効果重視・数値訴求型** ``` 【特徴】 - 記事内で言及されている具体的な数値効果のみを使用 - Before/After型の改善アピール(記事内の事実に基づく) - 実用性・効率化を強調(記事内容の範囲内で) 【構成テンプレート】 https://example.com 🚀 [記事内の具体的数値効果]で[技術課題]を解決! 📝 [記事内の具体的解決手法]で[記事内の効果]を実現 ⚡ 技術スタック: [半角英語技術名] 🔧 [記事内の実装詳細・注意点] #ハッシュタグ 【使用可能な表現例】 - 記事内で「30分→3分に短縮」と記載されている場合のみ使用 - 記事内で「90%削減」と記載されている場合のみ使用 - 記事内で「月40時間節約」と記載されている場合のみ使用 ``` #### **Bパターン: 課題共感・解決提案型** ``` 【特徴】 - 記事内で言及されている課題のみを使用 - 記事内で提案されている解決策のみを紹介 - 誇張のない共感を呼ぶ問題提起 【構成テンプレート】 https://example.com 😰 [記事内で言及される課題]で困ってませんか? 💡 [記事内の技術手法]を使えば[記事内の解決方法]で楽になります ⚡ [記事内の具体的技術スタック]で実装 🎯 [記事内の期待効果・メリット] #ハッシュタグ 【使用可能な表現例】 - 記事内で「手動でSlack通知作成の課題」が言及されている場合のみ - 記事内で「Google Docsの更新確認の問題」が言及されている場合のみ - 記事内で「レポート作成の時間コスト」が言及されている場合のみ ``` #### **Cパターン: 技術トレンド・学習促進型** ``` 【特徴】 - 記事内で言及されている技術動向のみを使用 - 記事内で説明されているスキルアップ内容のみを紹介 - 記事内で言及されている将来性・価値のみをアピール 【構成テンプレート】 https://example.com 🔥 [記事内で言及される技術分野]の実装方法 📚 [記事内の技術手法・ツール]の使用方法を解説 ⭐ [記事内の学習メリット・価値] 🚀 [記事内の実際の活用場面・価値] #ハッシュタグ 【使用可能な表現例】 - 記事内で「2025年のAI活用」が言及されている場合のみ - 記事内で「NotebookLMとSlack連携」が説明されている場合のみ - 記事内で「技術の評価への影響」が言及されている場合のみ ``` ### **Phase 4: 📋 記事内容検証・誇張表現排除** #### **Step 4-1: 投稿文と記事内容の照合** ``` 【検証項目】 1. 数値効果の正確性 - 投稿文の数値が記事内に明記されているか - 推測・推定ではなく実証された数値か - 文脈が正しく反映されているか 2. 技術的主張の正確性 - 投稿文の技術的効果が記事内で実証されているか - 理論的可能性ではなく実装済みの内容か - 制限事項・注意点が適切に反映されているか 3. 解決課題の正確性 - 投稿文の課題が記事内で具体的に言及されているか - 汎用的な「あるある」ではなく記事固有の課題か - 解決方法が記事内で実際に提示されているか 4. 将来性・価値の正確性 - 投稿文の価値主張が記事内で根拠付けられているか - 著者の推測ではなく客観的事実に基づいているか - 業界動向との整合性が取れているか ``` #### **Step 4-2: 誇張表現の特定と修正** ``` 【誇張表現の判定基準】 ⚠ 以下は誇張表現として判定・修正対象: 1. 記事内に記載のない具体的数値 - 「90%削減」「10倍高速化」等(記事内に根拠なし) - 「月40時間節約」等(記事内に計算根拠なし) - 「30分→3分短縮」等(記事内に実測データなし) 2. 記事内に記載のない効果・価値 - 「劇的改善」「ゲームチェンジャー」等(記事内に根拠なし) - 「チーム全体で効率化」等(記事内に事例なし) - 「来年の評価が変わる」等(記事内に根拠なし) 3. 記事内に記載のない課題・問題 - 「まだ手動で〜してるの?」等(記事内に言及なし) - 「毎週2時間かけてませんか?」等(記事内に統計なし) - 汎用的な「あるある」課題(記事固有ではない) 4. 記事内に記載のない技術トレンド - 「2025年注目の〜」等(記事内に根拠なし) - 「業界標準になる」等(記事内に予測なし) - 「この技術を知ってるかで〜」等(記事内に根拠なし) ``` #### **Step 4-3: 投稿文の再生成** ``` 【再生成トリガー】 - 3パターンのうち1つでも誇張表現が検出された場合 - 記事内容と投稿文の整合性が取れない場合 - 数値・効果の根拠が記事内で確認できない場合 【再生成ルール】 1. 記事内で明記されている内容のみを使用 2. 推測・推定・一般論は使用しない 3. 具体的数値は記事内の実証データのみ使用 4. 効果・価値は記事内の根拠付きのもののみ使用 5. 課題は記事内で具体的に言及されているもののみ使用 【再生成プロセス】 1. 誇張表現を特定 2. 記事内の対応する正確な情報を確認 3. 正確な情報に基づいて投稿文を再構築 4. 再度検証を実施 5. 問題がなければ最終版として採用 ``` ### **Phase 5: 投稿時間最適化** #### **エンジニア向け最適投稿時間** ``` 【平日優先時間帯】 - 朝の通勤時間:8:00-9:00 - 昼休み:12:00-13:00 - 夜の学習時間:21:00-22:00 【技術記事特化時間】 - 火曜日〜木曜日の21:00-22:00(最優先) - 月曜日 12:00-13:00(週始めキャッチアップ) - 金曜日 18:00-19:00(週末学習準備) 【記事品質連動調整】 - A評価:火曜21:00(エンジニア活動ピーク) - B評価:水曜12:00(昼休み気軽チェック) - C評価:金曜18:00(週末実験タイム) ``` ----- ## 📊 最終出力フォーマット ### **自動実行トリガー** ``` 以下の場合に自動的にこのプロセスを実行: 1. URLのみが入力された場合 2. "投稿パターン作成"等の指示がある場合 3. "A/Bテスト版"等の指示がある場合 4. "完全版"等の包括的分析要求がある場合 ``` ### **出力テンプレート** ```markdown ## ブログの評価 【総合評価】A / B / C 【技術的正確性】⭐⭐⭐⭐⭐ (5点満点) 【実装レベル】プロトタイプ / 基本実装 / 本格実装 / 企業レベル 【対象読者】初級者 / 中級者 / 上級者 / 専門家 【実用性】⭐⭐⭐⭐⭐ (5点満点) 【主要な技術要素】 - [抽出された技術スタック] - [解決する課題] - [提供価値・効果] 【記事内の具体的な数値・効果】 - [記事内で明記されている具体的数値] - [記事内で実証されている効果] - [記事内で言及されている改善点] 【注意事項・制限】 - [記事内で言及されている制限事項] - [実装時の注意点] 【誇張表現検証結果】 - [初回生成で検出された誇張表現] - [修正内容] - [最終版での整合性確認結果] ## ブログの投稿パターン ### Aパターン - 投稿文: [280バイト以内の効果重視型投稿文] - 記事内容との整合性: ✅ 確認済み - 投稿推奨時間: [具体的日時と根拠] ### Bパターン - 投稿文: [280バイト以内の課題共感型投稿文] - 記事内容との整合性: ✅ 確認済み - 投稿推奨時間: [具体的日時と根拠] ### Cパターン - 投稿文: [280バイト以内の学習促進型投稿文] - 記事内容との整合性: ✅ 確認済み - 投稿推奨時間: [具体的日時と根拠] ``` ----- ## 🔧 品質保証チェックリスト ### **記事品質評価の正確性** - [ ] 技術的事実の正確性検証 - [ ] 実装レベルの適切な判定 - [ ] 対象読者レベルの妥当性確認 - [ ] 制限事項・注意点の適切な反映 ### **誇張表現検証の徹底** - [ ] 投稿文の全数値が記事内で確認済み - [ ] 効果・価値の主張が記事内で根拠付け済み - [ ] 課題の指摘が記事内で具体的に言及済み - [ ] 技術トレンドの主張が記事内で根拠付け済み ### **リンクカード表示保証** - [ ] URLが投稿文の冒頭に単独行で配置 - [ ] URL前後のスペース・改行による明確分離 - [ ] 240文字超投稿でも冒頭URL配置 - [ ] 絵文字・ハッシュタグがURL後に配置 ### **3パターンの差別化** - [ ] 3つのアプローチが明確に異なる - [ ] 各パターンのターゲット読者が明確 - [ ] バイト数最適化が各版で実現 - [ ] 品質評価結果が適切に反映 - [ ] 全パターンで記事内容との整合性確認済み ### **ハッシュタグ効果分析** - [ ] 2-4回のweb_search実行 - [ ] 技術分野別の効果的ハッシュタグ調査 - [ ] トレンド性・関連性の評価 - [ ] バイト効率を考慮した選定 ----- ## 使用方法 URLを入力するだけで自動実行されます。出力はArtifact形式で生成され、チャットでの説明は最小限に抑制されます。 ``` 例:https://tech-blog.example.com/article-url ``` この改良版により、**記事内容の正確性を保証**し、**誇張表現を完全に排除**しながら、**リンクカード確実表示**と**シンプルな出力フォーマット**を両立したシステムが完成しました。 ``` ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 1人がこの投稿は役に立ったと言っています。 The post 【2025年版】Claudeプロンプト設計術|X投稿文の品質を劇的改善 first appeared on SIOS Tech. Lab .
アバター
こんにちは織田です 今回はRAG-STDの開発中にPyJWTを用いてトークン検証を実施しましたので、そこで得た知見についてまとめようと思います。 JWTおよびPyJWTとは? JWT (JSON Web Token) とは、インターネット上で情報を安全にやり取りするための規格の一つで、特に認証や認可の分野で利用されています。 ヘッダー、ペイロード、署名(シグネチャ)の3つの要素からなり、それぞれが”.”で区切られています。 そして、PyJWTとはJWT トークンをエンコード、デコード、検証してくれる Python ライブラリです。 JWTの各要素の詳細は以下の通りです。 ヘッダー (Header) JWTのタイプ(通常は”JWT”)と、署名の生成に使用されるアルゴリズム(例: HMAC SHA256やRSA)がJSON形式で記述されます。これはBase64Urlエンコードされます。 例: {   "alg": "HS256",   "typ": "JWT" } ペイロード (Payload) ここに、JWTに含める情報(クレームと呼びます)が記述されます。クレームには以下の3種類があります。 登録済みクレーム (Registered Claims): JWTの仕様で定義されているクレームで、iss(発行者)、exp(有効期限)、sub(主題)などがあります。これらは任意ですが、利用することで相互運用性が高まります。 公開クレーム (Public Claims): 衝突を避けるためにIANAのJWTクレームレジストリに登録されているか、URIとして定義されているクレームです。 プライベートクレーム (Private Claims): 送信者と受信者の間で合意されたカスタムクレームです。 例: {   "sub": "1234567890",   ""name": "John Doe",   "admin": true } このペイロードもBase64Urlエンコードされます。 署名 (Signature) エンコードされたヘッダー、エンコードされたペイロード、そして秘密鍵を使用して、ヘッダーで指定されたアルゴリズムによって生成されます。この署名があることで、トークンが改ざんされていないこと、および発行者が正しいことを検証できます。 これら3つの要素が「.」で連結されることで、最終的なJWTが構成されます。 例:`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ` JWTを用いたトークン検証のプロセス RAG-STD開発で実施したJWTを用いたトークン検証は、主に以下のステップで構成されます。 クライアントからのJWT受け取り: フロントエンドから、ヘッダー情報を介してJWTを受け取ります。 JWTの解析: 受け取ったJWTをヘッダー、ペイロード、署名の3つの部分に分割します。 署名の検証: 受け取ったJWTのヘッダーとペイロードを再度エンコードします。 サーバーが保持している秘密鍵(または公開鍵)と、エンコードされたヘッダー・ペイロードを用いて、新たな署名を生成します。 生成された署名と、受け取ったJWTに付与されていた署名を比較します。両者が一致すれば、トークンは改ざんされておらず、有効であると判断できます。一致しない場合は、トークンが無効であると判断し、リクエストを拒否します。 ペイロードの検証: 署名の検証に成功したら、ペイロードに含まれるクレームを検証します。 `exp` (有効期限) クレームを確認し、トークンが期限切れでないかを確認します。期限切れであれば、トークンは無効です。 `iss` (発行者) クレームを確認し、信頼できる発行元からのトークンであるかを確認します。 必要に応じて、`sub` (主題) やその他のカスタムクレーム(例: ユーザーID、ロールなど)を検証し、リクエスト元のユーザーが適切な権限を持っているかを確認します。 これらの検証ステップを全てクリアした場合、トークンは有効とみなされ、ユーザーは認証・認可された状態となります。 トークン検証時のポイント 実際のコードでは、フロントエンドから発行されたIDトークンの検証において、以下のような流れで処理を進めました。 公開鍵の一覧を取得: IDトークンを発行した認証プロバイダ(今回はMicrosoft)が公開している公開鍵の一覧(JWKS: JSON Web Key Set)を取得します。これは通常、特定のURLからJSON形式で提供されます。 IDトークンの署名を検証する公開鍵を一覧の中から抽出: 取得した公開鍵の一覧から、検証対象のIDトークンのヘッダーにある`kid` (Key ID) に対応する公開鍵を特定し、抽出します。これにより、正しい公開鍵を使って署名を検証できます。 署名検証ありでデコード: 抽出した公開鍵とIDトークンを用いて、署名の検証を行いながらトークンをデコードします。このステップで、トークンが改ざんされていないか、正しい発行者によって署名されたものかを確認します。PyJWTのようなライブラリを使用すると、この署名検証はデコード処理の一部として自動的に行われます。 ユーザー情報を抽出: 署名検証に成功したら、デコードされたペイロードからユーザ名やグループIDなどの必要なユーザ情報を抽出します。この情報をもとに、アプリケーション内でユーザーの識別や認可を行います。 その他のポイントとしては、以下のものがあげられます。 エラーハンドリング: 署名検証失敗、期限切れ、不正なペイロードなど、あらゆるケースで適切なエラーハンドリングを行うことが重要です。自分が実装しようとした当初はエラーハンドリングを適切に設定できておらず、エラーが発生しても原因が何か分からず悪戦苦闘しました… ライブラリの活用: PyJWTを活用することで、ルーティングごとに検証処理を記述する手間を省き、コードの可読性と保守性を高めました。当初は「検証のコードを自前で用意しなければならないのか…」と不安でしたが、PyJWTを用いることで僅かな行数で実装できました。 `aud` (Audience) クレームの理解: トークンをデコードする際に、`aud` (Audience) クレームの検証に苦労しました。これは、トークンが「誰(どのサービス)のために発行されたものか」を示す、いわば手紙の「宛名」のような概念です。JWTを受け取る側(つまり、検証を行う私たちのアプリケーション)が、そのトークンの`aud`クレームに自身が含まれているかを確認することで、意図しない宛先に送られたトークンの利用を防ぐことができます。 まとめ RAG-STDの開発を通して、JWTの仕組みと検証プロセス、そして実装上の注意点について深く理解することができました。検証用のコードを0から書くのは大変だろうと身構えていたのですが、PyJWTを使うことで、簡単に実装できました。また、コードを段階的に実装していくことで、検証に必要なプロセスについて詳しく理解することができました。この知見が、今後JWTを用いた認証・認可システムを構築される方々の一助となれば幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post PyJWTを用いたアクセストークンの検証をやってみた first appeared on SIOS Tech. Lab .
アバター
PSSLの佐々木です。 今回は、Dockerのマルチステージビルドを使ってPythonアプリケーションのサイズを削減する方法を解説します。 JavaやGoのようなコンパイル言語であればビルド時と実行が明確に分かれており、実行時にはバイナリだけあればよいのでマルチステージビルドと相性よく組み合わせて容量を削減できるというのは非常にわかりやすいと思いますが、PythonやRubyのよなインタプリター言語の場合には効果があるのかないのかいまいちピンとこなかったので自身の検証もかねてブログにまとめました。 そもそもマルチステージビルドって何? マルチステージビルドとは、1つのDockerfile内で複数の段階(ステージ)に分けてイメージを構築する手法です。 従来の問題 : Pythonパッケージをビルドするためにgccやg++が必要 しかし、実行時にはビルドツールは不要 でも、従来は最終イメージにビルドツールが残ってしまう マルチステージビルドの解決策 : ビルドステージ :必要なパッケージをコンパイル・ビルド 実行ステージ :ビルド結果だけを持ってきて軽量なイメージを作成 実際のDockerfileで比較してみよう 従来のシングルステージビルド # シングルステージビルド用Dockerfile FROM python:3.13-slim # ビルド時に必要なパッケージをインストール RUN apt-get update && apt-get install -y \\ gcc \\ g++ \\ && rm -rf /var/lib/apt/lists/* # 非rootユーザーを作成(ホームディレクトリも作成) RUN groupadd -r appuser && useradd -r -g appuser -m appuser # 作業ディレクトリを作成 WORKDIR /app # 依存関係ファイルをコピー COPY requirements.txt . # 依存関係をインストール RUN pip install --no-cache-dir --upgrade pip && \\ pip install --no-cache-dir -r requirements.txt # アプリケーションコードをコピー COPY . . # 不要なファイルとディレクトリを削除 RUN find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \\ find . -type f -name "*.pyc" -delete && \\ rm -rf venv/ && \\ rm -rf .git/ && \\ rm -rf .pytest_cache/ && \\ rm -rf *.egg-info/ && \\ rm -rf .coverage && \\ rm -rf htmlcov/ # アプリケーションディレクトリの所有者を変更 RUN chown -R appuser:appuser /app # promptflowが使用するディレクトリを作成 RUN mkdir -p /home/appuser/.promptflow && \\ chown -R appuser:appuser /home/appuser/.promptflow # 非rootユーザーに切り替え USER appuser # 環境変数でホームディレクトリを明示的に設定 ENV HOME=/home/appuser # ポートを公開 EXPOSE 5000 # ヘルスチェック HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\ CMD python -c "import urllib.request; urllib.request.urlopen('<http://localhost:5000/api/health>')" || exit 1 # アプリケーションを実行 CMD ["python", "app.py"] マルチステージビルド # マルチステージビルド用Dockerfile # ステージ1: ビルドステージ FROM python:3.13-slim AS builder # ビルド時に必要なパッケージをインストール RUN apt-get update && apt-get install -y \\ gcc \\ g++ \\ && rm -rf /var/lib/apt/lists/* # 作業ディレクトリを作成 WORKDIR /build # 依存関係ファイルをコピー COPY requirements.txt . # 依存関係をインストール(グローバルにインストール) RUN pip install --no-cache-dir --upgrade pip && \\ pip install --no-cache-dir -r requirements.txt # アプリケーションコードをコピー COPY . . # 不要なファイルとディレクトリを削除 RUN find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \\ find . -type f -name "*.pyc" -delete && \\ rm -rf venv/ && \\ rm -rf .git/ && \\ rm -rf .pytest_cache/ && \\ rm -rf *.egg-info/ && \\ rm -rf .coverage && \\ rm -rf htmlcov/ # ステージ2: 実行ステージ FROM python:3.13-slim AS runtime # 実行時に必要な最小限のパッケージをインストール RUN apt-get update && apt-get install -y \\ && rm -rf /var/lib/apt/lists/* # 非rootユーザーを作成(ホームディレクトリも作成) RUN groupadd -r appuser && useradd -r -g appuser -m appuser # 作業ディレクトリを作成 WORKDIR /app # ビルドステージからPythonパッケージをコピー COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages COPY --from=builder /usr/local/bin /usr/local/bin # アプリケーションコードをコピー COPY --from=builder /build/app.py . COPY --from=builder /build/connection.yaml . COPY --from=builder /build/flows ./flows/ COPY --from=builder /build/services ./services/ # アプリケーションディレクトリの所有者を変更 RUN chown -R appuser:appuser /app # promptflowが使用するディレクトリを作成 RUN mkdir -p /home/appuser/.promptflow && \\ chown -R appuser:appuser /home/appuser/.promptflow # 非rootユーザーに切り替え USER appuser # 環境変数でホームディレクトリを明示的に設定 ENV HOME=/home/appuser # ポートを公開 EXPOSE 5000 # ヘルスチェック HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\ CMD python -c "import urllib.request; urllib.request.urlopen('<http://localhost:5000/api/health>')" || exit 1 # アプリケーションを実行 CMD ["python", "app.py"] マルチステージビルドの3つのメリット 1. サイズ削減 不要なビルドツールや中間ファイルが除去されるため、イメージサイズが大幅に小さくなります。 2. セキュリティ向上 実行時に不要なツール(gcc、コンパイラなど)が含まれないため、攻撃面が減少します。 3. デプロイ高速化 イメージが軽量になることで、レジストリへのpush/pullが高速化されます。 実装のポイント AS句でステージに名前をつける FROM python:3.13-slim AS builder # ← 「builder」という名前 FROM python:3.13-slim AS runtime # ← 「runtime」という名前 COPY –fromで必要なファイルだけを移動 # builderステージからsite-packagesだけをコピー COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages 不要ファイルの削除 RUN find . -name "__pycache__" -exec rm -rf {} + && \\ find . -name "*.pyc" -delete 開発環境での使い方 実際にマルチステージビルドを開発で使う場合は、Docker Composeと組み合わせるのがおすすめです。 services: app: build: . # マルチステージDockerfileを使用 ports: - "5000:5000" volumes: - .:/app # 開発時はコードをマウント environment: - FLASK_DEBUG=1 実際にどれくらい効果があるの? 筆者が実際にPythonアプリケーション(promptflowやFlaskを使用)で試した結果がこちらです: REPOSITORY TAG IMAGE ID CREATED SIZE my-app-single latest 99aa62f1267a 1 minute ago 1.45GB my-app-multi latest 27b56911a385 6 minutes ago 636MB 約810MB(56%)の削減に成功しています。 この差は特に以下のような場面で威力を発揮します: CI/CDパイプライン :デプロイ時間の短縮 本番環境 :ストレージコストの削減 開発チーム :イメージ共有の高速化 なぜPythonでもマルチステージビルドの効果があるのか 記事の頭でも気になっていたインタプリター言語でもなぜマルチステージビルドで容量を削減できるのか少し調べてみました。 Pythonのエコシステムでは結構コンパイル処理が発生しているようで、C / C++で書かれている部分を含んでいることが要因の一つでした。 具体的には以下のようなライブラリはC拡張コンパイルをしているようです。 numpy # 線形代数計算(BLAS/LAPACK) pandas # データ処理の高速化 pillow # 画像処理 lxml # XML/HTMLパーサー psycopg2 # PostgreSQLドライバー cryptography # 暗号化ライブラリ uwsgi # WSGIサーバー それによって作成される以下のような不要なファイルやコンパイルツールを含めずにイメージを作成することができるためマルチステービルドの恩恵をしっかりと受けられているのだと思います。 gcc/g++とその依存関係 apt-getのキャッシュ : ビルド時の一時ファイル その他のビルドツール まとめ マルチステージビルドは、少しの工夫で大きな効果を得られる優秀な技術です。コンパイル言語ほどではないですが、Pythonのようなインタプリター言語でもマルチステージビルドの効果を確認することができました。 またアプリケーション実行に最小の要素が何なのかわからないと構築が難しいですが、明らかに必要のないものを含めない状態でイメージを作成するだけでも効果があるので積極的に取り入れて段階的に最適化させていくのが良いかなと思いました。 ではまた ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post マルチステージビルドでPythonアプリケーションを軽量化 first appeared on SIOS Tech. Lab .
アバター
概要 こんにちは、サイオステクノロジーの安藤 浩です。 Bicep で Azure Container Apps と Jobs を構築したので、使い分けと実際のBicep でのパラメータとインフラを構築する方法を説明します。 Azure Container Apps とは Azure Container Apps  は、サーバーレスなコンテナプラットフォームで、コンテナー化されたアプリケーションを実行するため、保守が容易でコスト削減できます。 特徴: 主な特徴としては以下になるかと思います。 サーバーレス マイクロサービスに最適 自動スケーリング機能 (HTTP トラフィック、CPU,メモリの負荷状況、ServiceBus キューなど) 用途: 主な用途としては以下が考えられます。 Web アプリケーション/Web API マイクロサービスの実行 リアルタイム処理 Azure Container Apps Jobs とは Azure Container Apps Jobs  は、タスクベースのワークロードに特化したサーバーレスコンテナサービスです。 特徴: 主な特徴としては以下になるかと思います。 サーバーレス スケジュールトリガー、手動トリガー、イベントドリブン トリガー 実行履歴管理:ジョブ実行の状況が把握できる 並列実行のサポート ※Dapr、イングレスとカスタム ドメインや SSL 証明書などの関連機能 はサポートされていません。 用途: 主な用途としては以下が考えられます。 バッチ処理 データ処理パイプライン 定期的なメンテナンスタスク イベントドリブンな処理 Azure Container Apps と Azure Container Apps Jobs の使い分けポイント 以下の観点でAzure Container Apps と Azure Container Apps Jobs を使い分けることがポイントになると思います。 ポイント Azure Container Apps Azure Container Apps Jobs サービス種別 継続的に実行されるサービス タスク、バッチ処理 稼働形態 常時稼働、またはリクエスト/イベントに応じて起動 手動、スケジュール、またはイベントに応じて起動・実行・終了 主なトリガー HTTP、TCP 手動、スケジュール、イベント ユースケース Web API、Web アプリ、マイクロサービス 定期的なバッチ処理、データ処理 簡単に言うと、Azure Container AppsはWebアプリやAPIのように「継続的にリクエストされるWebアプリケーション」向け、Azure Container Apps Jobsはデータ処理やバッチ処理のような「実行して完了するタスク」向けのサービスです。 Bicep によるAzure Container Apps と Azure Container Apps Jobs の構築 ソースコードは以下に配置しています。 https://github.com/Hiroshi-Ando-sti/intro-bicep/tree/main/container-apps 上記のソースコードで利用しているサンプルとなるコンテナ イメージは以下のリポジトリで管理しています。 https://github.com/Hiroshi-Ando-sti/githubpkg-containerregistry 前提条件 以下をインストールします。 ツール Version Visual Studio Code Version: 1.101.2 Visual Studio Code 用の Bicep 拡張機能 0.36.177 最新の Azure CLI ツールまたは最新の Azure PowerShell バージョン 2.74.0 構成要素 以下の構成を構築します。 VNET Application Insights Container Environments Container Apps Container Apps Jobs Bicep コード 1. メインとなるBicep コード (infra/main.bicep) メインとなるコードの主要部分のみを記載します。 Container Apps やJobs を実行するために必要なリソースは VNET やContainer Apps Environment ですので、事前に作成します。 Container Apps と Container Apps Jobs は  containerAppsSettings  と  containerAppJobsSettings  のパラメータを利用して、複数作成できるようにしています。 module vnet './modules/network/vnet.bicep' = { name: 'vnet' params: { name: '${abbrs.networkVirtualNetworks}${environmentName}${suffixResourceName}${uniqueStr}' location: location tags: tags subnetName: 'default' } } var containerAppEnvironmentName = '${abbrs.appManagedEnvironments}${environmentName}${suffixResourceName}${uniqueStr}' module containerAppEnvironment './modules/container/containerAppsEnv.bicep' = { name: 'containerAppEnvironment' params: { vnetName: vnet.outputs.name environmentName: containerAppEnvironmentName location: location tags: tags logAnalyticsWorkspaceName: logAnalyticsWorkspaceName } } module containerAppJobs './modules/container/jobs.bicep' = [for containerAppJobsSetting in containerAppJobsSettings: { name: 'containerAppJobs-${containerAppJobsSetting.name}' params: { containerAppEnvironmentName: containerAppEnvironmentName location: location tags: tags containerAppJobsSetting: containerAppJobsSetting appInsightsConnectionString: appInsights.outputs.connectionString } }] module containerApps './modules/container/containerApps.bicep' = [for containerAppsSetting in containerAppsSettings: { name: 'containerApps-${containerAppsSetting.name}' params: { containerAppsName: '${abbrs.appContainerApps}${environmentName}-${containerAppsSetting.name}${uniqueStr}' location: location tags: tags containerAppEnvironmentName: containerAppEnvironmentName appInsightsConnectionString: appInsights.outputs.connectionString environmentName: environmentName resourceSuffixName: uniqueStrWithoutHyphen containerAppsSetting: containerAppsSetting } }] 2. Container Apps Environment モジュール (infra/modules/container/containerAppsEnv.bicep) VNET が必要であり、Container Apps Environmentの Log出力先としてLog analytics を指定します。 @description('VNet名') param vnetName string param location string = resourceGroup().location param environmentName string param logAnalyticsWorkspaceName string param tags object // Container Apps 環境の VNet を取得する。 resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' existing = { name: vnetName } resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' existing = { name: logAnalyticsWorkspaceName } // Container Apps Environment //https://learn.microsoft.com/ja-jp/azure/templates/microsoft.app/2025-01-01/managedenvironments?pivots=deployment-language-bicep resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2025-01-01' = { name: environmentName location: location tags: tags properties: { appLogsConfiguration: { destination: 'log-analytics' logAnalyticsConfiguration: { customerId: logAnalyticsWorkspace.properties.customerId sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey } } vnetConfiguration: { infrastructureSubnetId: vnet.properties.subnets[0].id } } } 3. Container Apps モジュール (infra/modules/container/containerApps.bicep) Container AppsのProperties では主に以下のパラメータを指定します。 properties のパラメータ 説明 managedEnvironmentId Container Apps Environment のIDを指定 configuration.ingress Container Apps イングレス の設定 configuration.secrets Container Apps 内のシークレット値 template.containers.image コンテナイメージ template.containers.resources CPU, メモリサイズの指定 template.containers.env コンテナの環境変数 template.scale.cooldownPeriod スケールインするまでの時間(秒)を指定 template.scale.minReplicas リビジョンごとの最小レプリカ数 template.scale.maxReplicas リビジョンあたりのレプリカの最大数 template.scale.rules レプリカを追加または削除するタイミングを決定するためにコンテナー アプリによって使用される条件。 スケールルール のトリガーは3つ指定可能で HTTP, TCP, カスタムメトリックがあります。例:http として、concurrentRequests: 10 とすると同時実行された HTTP 要求数がこの値を超えると、レプリカが 1つ追加されます。(maxReplicasまで) @description('Container Appsの名前') param containerAppsName string @description('すべてのリソースのロケーション') param location string = resourceGroup().location @description('Container Apps 環境の名前') param containerAppEnvironmentName string @description('Container Appsが属する環境名') param environmentName string @description('リソース名のサフィックス') param resourceSuffixName string @description('Container Appsの設定') @secure() param containerAppsSetting object @description('Application Insightsの接続文字列') param appInsightsConnectionString string @description('リソースに付与するタグ') param tags object = {} resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2025-01-01' existing = { name: containerAppEnvironmentName } //https://learn.microsoft.com/en-us/azure/templates/microsoft.app/2025-01-01/containerapps?pivots=deployment-language-bicep //https://learn.microsoft.com/ja-jp/azure/container-apps/scale-app?pivots=azure-cli resource containerApps 'Microsoft.App/containerApps@2025-01-01' = { name: containerAppsName location: location tags: tags properties: { managedEnvironmentId: containerAppEnvironment.id configuration: { ingress: { external: containerAppsSetting.ingress.external targetPort: containerAppsSetting.ingress.targetPort transport: containerAppsSetting.ingress.transport allowInsecure: containerAppsSetting.ingress.allowInsecure traffic: [ { latestRevision: true weight: 100 } ] } secrets: containerAppsSetting.secretVariables } template: { containers: [ { name: 'c-${environmentName}-${containerAppsSetting.name}-${resourceSuffixName}' image: containerAppsSetting.image resources: { cpu: json(containerAppsSetting.cpu) memory: containerAppsSetting.memory } env: union(containerAppsSetting.environmentVariables, [ { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' value: appInsightsConnectionString } ]) } ] scale: { cooldownPeriod: containerAppsSetting.scale.cooldownPeriod pollingInterval: containerAppsSetting.scale.pollingInterval minReplicas: containerAppsSetting.scale.minReplicas maxReplicas: containerAppsSetting.scale.maxReplicas rules: containerAppsSetting.scale.rules } } } } 4. Container Apps Jobs モジュール (infra/modules/container/jobs.bicep) containerAppJobsSetting をパラメータとして渡して、設定値をContainer Apps jobs の Properties に設定してあげます。 triggerType は  Event ,  Manual ,  Schedule  が選択できます。 triggerType — Event イベントドリブン ジョブをトリガーします。 例:Azure Service Bus、Kafka、RabbitMQ などのキューに新しいメッセージが追加されたときに実行されるジョブ。 ワークフローまたはパイプラインのキューに新しいジョブが格納されたときに実行されるセルフホステッド GitHub Actions ランナー または Azure DevOps エージェント。 Manual 手動ジョブでの実行をします。 例:一度限りの実行。 Schedule スケジュールされたジョブを実行します。cron 式で定義します。 例:  */5 * * * *  : 5分ごとの実行。 propertiesのtemplate以下は基本的にContainer Apps 同様です。 注意: replicaTimeout までに処理が終わらなければ、タイムアウトしてレプリカは停止します。 param location string = resourceGroup().location param containerAppEnvironmentName string param containerAppJobsSetting object param appInsightsConnectionString string param tags object = {} resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2025-01-01' existing = { name: containerAppEnvironmentName } // Container Apps Jobs の作成 //https://learn.microsoft.com/en-us/azure/templates/microsoft.app/2025-01-01/jobs?pivots=deployment-language-bicep resource containerAppJobs 'Microsoft.App/jobs@2025-01-01' = { name: containerAppJobsSetting.name location: location tags: tags properties: { environmentId: containerAppEnvironment.id configuration: { triggerType: containerAppJobsSetting.triggerType replicaRetryLimit: containerAppJobsSetting.replicaRetryLimit replicaTimeout: containerAppJobsSetting.replicaTimeout eventTriggerConfig: containerAppJobsSetting.triggerType == 'Event' ? { parallelism: containerAppJobsSetting.parallelism replicaCompletionCount: containerAppJobsSetting.replicaCompletionCount scale: containerAppJobsSetting.jobScale == null ? null : { maxExecutions: containerAppJobsSetting.jobScale.maxExecutions minExecutions: containerAppJobsSetting.jobScale.minExecutions pollingInterval: containerAppJobsSetting.jobScale.pollingInterval rules: containerAppJobsSetting.jobScale.jobScaleRule == null ? null : [ { auth: containerAppJobsSetting.jobScale.jobScaleRule == null ? null : [ { secretRef: containerAppJobsSetting.jobScale.jobScaleRule.ScaleRuleAuth.secretRef triggerParameter: containerAppJobsSetting.jobScale.jobScaleRule.ScaleRuleAuth.triggerParameter } ] identity: containerAppJobsSetting.jobScale.jobScaleRule.identity metadata: containerAppJobsSetting.jobScale.jobScaleRule.metadata name: containerAppJobsSetting.jobScale.jobScaleRule.name type: containerAppJobsSetting.jobScale.jobScaleRule.type } ] } } : null manualTriggerConfig: containerAppJobsSetting.triggerType == 'Manual' ? { parallelism: containerAppJobsSetting.parallelism replicaCompletionCount: containerAppJobsSetting.replicaCompletionCount } : null scheduleTriggerConfig: containerAppJobsSetting.triggerType == 'Schedule' ? { cronExpression: containerAppJobsSetting.cronExpression parallelism: containerAppJobsSetting.parallelism replicaCompletionCount: containerAppJobsSetting.replicaCompletionCount } : null } template: { containers: [ { name: containerAppJobsSetting.name image: containerAppJobsSetting.image env: union(containerAppJobsSetting.environmentVariables, [ { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' value: appInsightsConnectionString } ]) resources: { cpu: json(containerAppJobsSetting.cpu) memory: containerAppJobsSetting.memory } } ] } } } 5. パラメータファイル (main.sbx.parameters.json) メインとなるBicep コードの箇所で説明した Container Apps と Container Apps Jobs のパラメータ:  containerAppsSettings  と  containerAppJobsSettings  のみ以下に記載しています。 Jobs のContainer Image はバッチ処理のサンプルとして 開始時にログ出力して、  SLEEP_SECONDS  (秒)待機、終了するバッチです。 Container Apps の Container Image は  GET /  と  GET /test  へリクエストできるサンプルです。 今回はいずれも公開されたImageですが、プライベートの場合も指定可能です。 "containerAppJobsSettings": { "value": [ { "name": "schedule-container-app-jobs", "image": "ghcr.io/hiroshi-ando-sti/githubpkg-containerregistry/container-app-jobs:main", "replicaTimeout": 4000, "replicaRetryLimit": 2, "parallelism": 1, "replicaCompletionCount": 1, "triggerType": "Schedule", "cronExpression": "0 */10 * * *", "cpu": "0.25", "memory": "0.5Gi", "environmentVariables": [ { "name": "RETENTION_DAYS", "value": "30" }, { "name": "SLEEP_SECONDS", "value": "3800" } ], "jobScale": { "maxExecutions": 1, "minExecutions": 1, "pollingInterval": "PT5M", "maxExecutionTime": "PT30M", "jobScaleRule": null } } ] }, "containerAppsSettings": { "value": [ { "name": "webapi", "image": "ghcr.io/hiroshi-ando-sti/githubpkg-containerregistry/webapi:main", "ingress": { "external": true, "targetPort": 8000, "transport": "auto", "allowInsecure": false }, "secretVariables": [ { "name": "app-private-key", "value": "app-private-key-value" } ], "cpu": "0.25", "memory": "0.5Gi", "environmentVariables": [ { "name": "BATCH_SIZE", "value": "100" }, { "name": "LOG_LEVEL", "value": "INFO" }, { "name": "SLEEP_SECONDS", "value": "3800" } ], "scale": { "cooldownPeriod": 65, "pollingInterval": 30, "minReplicas": 0, "maxReplicas": 2, "rules": [ { "name": "http-rule", "http":{ "metadata": { "concurrentRequests": "2" } } } ] } } ] } デプロイ手順 1. リソースグループの作成 az group create --name rg-container-apps --location "Japan East" 2. Bicepコードによるデプロイ cd infra az deployment group create \ --name rg-container-apps-deploy \ --mode Complete \ --resource-group rg-container-apps \ --confirm-with-what-if \ --template-file ./main.bicep \ --parameters ./main.parameters.sbx.json 3. デプロイの確認 デプロイの確認は以下で行います。 # デプロイ状況を確認 az deployment group list --resource-group rg-container-apps --output table # Container Appsの状況を確認 az containerapp list --resource-group rg-container-apps --output table # Container Jobsの状況を確認 az containerapp job list --resource-group rg-container-apps --output table Azure Portal 上の Container Apps Enviroment からも Container Apps が作成されていることが確認できます。 まとめ Azure Container Apps と Jobs は、それぞれ異なる用途に最適化されたサーバーレスコンテナサービスです。 1. Azure Container Apps と Jobs の使い分けのポイント Azure Container Apps は 継続的に実行されるWebアプリケーションやAPI向けのサービス で、Azure Container Apps Jobs は バッチ処理や定期的なタスク向けのサービス です。 2. Container Apps と Jobs 実装時の考慮点 Container Apps と Jobs を実装する際は、以下の点を考慮することが重要です: リソース配分 :CPU とメモリの適切な設定 スケーリング戦略 :トラフィックパターンに応じたスケールルールの設定 セキュリティ :シークレット管理 監視とログ :Log analytics, Application Insights による可観測性の確保 コスト最適化 :最小レプリカ数の調整とスケールイン時間の設定 トリガー :適切なトリガーの選択 Bicep による Infrastructure as Code と組み合わせて、スケーラブルで保守性の高いコンテナベースのアプリケーション基盤を構築してはいかがでしょうか。 参考URL Azure Container Apps の概要 https://learn.microsoft.com/ja-jp/azure/container-apps/overview Azure Container Apps のコンテナー https://learn.microsoft.com/ja-jp/azure/container-apps/containers Azure Container Apps のジョブ https://learn.microsoft.com/ja-jp/azure/container-apps/jobs?tabs=azure-cli   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post [Azure][Bicep]Azure Container Apps と Jobs の特徴とBicepによる構築 first appeared on SIOS Tech. Lab .
アバター
PSSLの佐々木です。 Dockerを使っていると、同じアプリケーションでも様々なイメージタグが用意されています。 node:18 、 node:18-slim 、 node:18-alpine など、どれを選べばいいのか迷ってしまうことはありませんか? 例えばPythonのDocker Imageはversion 3.13.5系だけでもこんなにあります。とほほ。。 この記事では、各Dockerイメージの種類とその特徴、そして「どんな時に使うべきか」を明確にします 主要なDockerイメージの種類 1. 標準イメージ(Latest/バージョン指定) 例: node:18 、 python:3.11 、 nginx:latest 特徴: 最も完全なパッケージセット 開発に必要なツールやライブラリが豊富 サイズは大きめ(通常数百MB〜1GB以上) Debianベースが多い こんな時に選ぶべき: 開発環境やテスト環境 パッケージの互換性問題を避けたい 初心者や学習目的 プロトタイピング段階 2. Slimイメージ 例: node:18-slim 、 python:3.11-slim 特徴: 不要なパッケージを削除した軽量版 削除される例: curl 、 wget 、 git 、 vim 、 nano 、 man ページ、ドキュメント類 残されるもの:パッケージマネージャー( apt )、基本的なシステムライブラリ 標準イメージの約50-70%のサイズ 基本的な開発ツールは含まれている Debianベースだが最小限のパッケージ こんな時に選ぶべき: 本番環境でサイズを抑えたい 標準イメージでは大きすぎるが、Alpineは不安 CI/CDでのビルド時間を短縮したい セキュリティ面でパッケージ数を減らしたい 3. Alpineイメージ 例: node:18-alpine 、 python:3.11-alpine 、 nginx:alpine 特徴: Alpine Linuxベース(軽量ディストリビューション) 極めて小さなサイズ(通常10-100MB) muslcライブラリを使用(glibcではない) パッケージマネージャーは apk 最小限のパッケージ構成 基本的なシェル( ash )のみ bash 、 curl 、 git 、 vim などは含まれない セキュリティを重視した最小構成 こんな時に選ぶべき: 最小限のサイズが必要 マイクロサービス環境 コンテナの起動速度を重視 リソース使用量を最小限に抑えたい 注意点: ネイティブ拡張でコンパイルエラーが発生する可能性 一部のパッケージが利用できない場合がある デバッグツールが限定的 4. Scratchイメージ 例: FROM scratch 特徴: 完全に空のイメージ 極小サイズ(数KB〜数MB) シェルやパッケージマネージャーなし 静的バイナリのみ実行可能 こんな時に選ぶべき: Go言語の静的バイナリ セキュリティを最優先 極限までサイズを削減したい 攻撃面を最小限に抑えたい 実際のタグ選択:Python 3.13.5を例に 実際にDockerイメージを選ぶ際、以下のような大量のタグが存在します: 3.13.5-bookworm, 3.13-bookworm, 3-bookworm, bookworm 3.13.5-slim-bookworm, 3.13-slim-bookworm, 3-slim-bookworm, slim-bookworm, 3.13.5-slim, 3.13-slim, 3-slim, slim 3.13.5-alpine3.22, 3.13-alpine3.22, 3-alpine3.22, alpine3.22, 3.13.5-alpine, 3.13-alpine, 3-alpine, alpine 3.13.5-windowsservercore-ltsc2025, 3.13-windowsservercore-ltsc2025, 3-windowsservercore-ltsc2025, windowsservercore-ltsc2025 推奨される選択方法 1. バージョン指定の基本ルール python:latest や python:3 – 予期しないバージョンアップで動作不良の可能性 python:3.13.5 – 具体的なバージョンを指定 python:3.13 – マイナーバージョンを固定(パッチ更新は自動) 2. 実際の選択例 # 開発環境:最新の安定版を使用 FROM python:3.13.5-bookworm # または単純に FROM python:3.13.5 # 本番環境:Slimで軽量化 FROM python:3.13.5-slim # マイクロサービス:Alpineで最小化 FROM python:3.13.5-alpine 3. Debianコードネーム(bookworm等)は通常省略可能 python:3.13.5-bookworm と python:3.13.5 は同じ 特定のDebianバージョンに依存する場合のみ明示的に指定 4. Alpineのバージョン指定 python:3.13.5-alpine で十分(最新のAlpineを使用) python:3.13.5-alpine3.22 は特定のAlpineバージョンが必要な場合のみ 実用的な選択フローチャート ステップ1: 用途を確認 開発・テスト環境 → 標準イメージ 本番環境 → Slim または Alpine 極限の軽量化が必要 → Scratch ステップ2: 互換性を確認 ネイティブ拡張を使用 → 標準 または Slim Pure JavaScript/Python → Alpine も選択可能 静的バイナリ → Scratch ステップ3: リソース制約を確認 ストレージ容量に制限 → Alpine または Scratch ネットワーク帯域に制限 → Alpine または Scratch 制約なし → 標準 または Slim まとめ 初心者の方は標準イメージから始めて、慣れてきたらSlimやAlpineに移行する のが現実的なアプローチです。 各イメージの選択基準: 標準イメージ : 確実に動かしたい時 Slimイメージ : バランスを重視する時 Alpineイメージ : サイズを最優先する時 Scratchイメージ : 静的バイナリで極限の軽量化が必要な時 プロジェクトの要件と制約を考慮して、最適なイメージを選択しましょう。不明な点があれば、まず標準イメージで動作確認してから、段階的に軽量化を進めることをお勧めします。 またこのほかにもいくつかイメージが存在していますが、基本この4つのどれかを採用しておけばよいと思います ではまた   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Dockerイメージの選び方ガイド first appeared on SIOS Tech. Lab .
アバター
1. ブログ公開前の「これで大丈夫?」を解決したい ども!最近はAIを活用したブログ改善とかいろいろ取り組んでいる龍ちゃんです。 皆さんもブログを書いた後、こんな不安を感じることありませんか? 「この技術情報、本当に正しいかな?」 「データが古くないかな?」 「読者にとって価値があるかな?」 やっぱ不安ですよね。ブログ書いてもこれが本当に正しいのかとか、情報が古くないかなぁとか。検証はもちろんするんですけど、それが正しいのかどうかという不安はなかなか消えてくれません。 従来の解決方法には人的リソースの観点から多くの問題があります。 手動でのファクトチェック → 時間がかかりすぎる 専門家に相談 → コストと時間の問題 自分で調べ直す → 見落としのリスクが高い 簡単なのは自分で調べてポチポチ見ながら「大丈夫かなぁ」と調べ直すことです。ですが、限界あるじゃないですか。 そこで今回提案したいのが、 AIを「第一読者」にする という発想です。他のエンジニアとかに見てもらうのはやったほうがいいんですけど、なかなかできないじゃないですよね~。みんな忙しいですしww そういうところってAIにやってもらえたらいいなぁって思って、ぶち込んでみたらいい感じにできたって流れです。 2. なぜGemini Deep Researchが第一読者に最適なのか 1番の理由は弊社でGoogle Workspaceの契約にGeminiがついてきたので、会社のドメインであれば有料プランを使い倒せるという点です。 実際、Web Searchがついているサービスはほかにもあります。Claude Proと比較したのですが5000KBのPDFを入力として与えたところ、Claudeではスレッドのトークン上限に引っかかりプロンプトを実行することができませんでした。GeminiのDeep Researchでは、動作し15分前後で200件弱のサイトを検索してレポートを作成してくれました。 Deep Researchの特徴 Geminiのディープリサーチは、従来の検索とは全く異なる「エージェント型AI」の仕組みで動作します。 3つのステップで自動調査 プラン作成 :複雑な質問を小さなタスクに分解 情報収集 :数百のウェブサイトを並行して調査 分析・統合 :収集した情報を批判的に評価してレポート作成 技術的な特徴 100万トークンのコンテキスト管理 反復的な検索プロセス 完全非同期処理(PCを閉じても継続) 詳細な技術仕様については、 Google公式のDeep Research解説 をご覧ください。 他社AIツールとの比較 項目 Gemini Deep Research ChatGPT Deep Research Claude Research 料金 Google AI Pro: $20/月・Google AI Ultra: $250/月・無料版: あり ChatGPT Plus: $20/月・ChatGPT Pro: $200/月・無料版: あり Max Plan: $100-400/月・Pro: $20/月(近日対応予定)・無料版: なし 無料版の機能 月数回の利用、Gemini 2.5 Flash使用・スプレッドシート・コードファイル不可 月5回のタスク・軽量版のみ・短めの回答 基本的なチャット機能のみ・研究機能利用不可 処理時間 標準: 5-10分、複雑なクエリ: より長時間 標準: 5-30分・平均: 15-20分 標準: 5-15分・複雑: 最大45分 検索規模 数百のウェブサイトを検索 数百のソース・(例:52ソース) 数百のソース・+ 内部統合 レポート品質 6-12ページ、引用付き、Googleドキュメント出力対応 2,000-5,000語・構造化された形式 包括的レポート・複数引用スタイル対応 ファイルサイズ制限 100MB (標準)、クラウドストレージ: 2GB 512MB (全フォーマット)・画像: 20MB 30MB (全フォーマット)・画像: 30MB 同時アップロード 10ファイル 有料: 80ファイル /3時間・無料: 3ファイル /日 20ファイル /会話 無料版ファイル機能 PDF、DOC、画像のみ、スプレッドシート不可 同じファイルサイズ制限・3ファイル/日 10MBまで・基本分析のみ 対応ファイル形式 PDF、DOC、DOCX、TXT、画像(Pro: CSV、コードファイル追加) TXT、PDF、画像、CSV、XLSX、DOCX PDF、DOCX、CSV、TXT、画像、OCR機能あり 強み Google統合、非同期処理・音声概要生成 高速処理、包括的分析・マルチフォーマット対応 マルチエージェント並列処理・Googleワークスペース統合 弱み ファイル形式制限・処理時間長い 事実の幻覚リスク・計算リソース集約的 地理的制限あり・高コスト 適用場面 ビジネス分析、市場調査・学術研究、コンテンツ作成 学術研究、投資分析・政策研究、競合分析 学術研究、市場分析・法的調査、科学研究 日本語対応 完全対応 :日本で利用可能・音声機能も日本語対応 利用可能 :但し英語版より品質劣る・文化的文脈の制限あり 完全対応 ・日本で利用可能・機能制限なし (7/5時点での最新情報のつもりです…) 参考リンク:公式サイト Google/Gemini : Gemini Deep Research公式 、 Google AI Blog OpenAI : Deep Research FAQ 、 公式発表 Anthropic : Claude Research発表 、 プランページ 第一読者にAIサービスが適している理由 時間効率 :100~200件のサイトを相互参照して確認するのは人間がやると大規模な作業になります。Research系の機能を使うとプロンプトを投げて終了を待つだけ 情報源の明示 :情報元を提示してくれるので、深く知りたい場合は参照元に当たることができる 多角的な検証 :複数の視点から情報を検証 最新情報へのアクセス :リアルタイムの情報、ウェブサイトをリサーチしてあらゆるサイトから情報を参照 3. 実際にやってみよう:PDFから記事の品質をチェックする3ステップ ここからは実際にPDFファイルを使った品質チェックの手順について書いていきます。全体の流れをフロー図で作ってみました。 ステップ1:PDFファイルの準備とアップロード まずですね、ブログを執筆します。ここ大事ですよね。ブログを書くんですよ。 僕はNotion使ってブログを書いてるんですけど、NotionでPDFでエクスポートします。PDFファイルを添付してDeep Researchに投げます。 対象としたファイルとしては1000〜5000KBまでのPDFであれば問題なく動作しました。Google Workspaceと連携したGeminiであればトグルボタンひとつでGoogle Driveとシームレスな連携をすることができます。Goolge Docsなどのドキュメントであればブラウザ内で完結させることができます。 ステップ2:プロンプトの作成 PDFファイルをアップロードしたら、こんな感じでプロンプトを送ります: 添付した資料の内容をもとにファクトチェックして、技術的ブログとして多角的な評価から判断して このプロンプトは簡素なプロンプトにしています。「ファクトチェック」は外部的リソースとの比較をさせる意図があります。「多角的評価」という言葉でGemini側に評価基準を委譲しています。 目的別プロンプト例 : 技術記事の場合 :「技術仕様、API情報、コード例の正確性を重点的に」 差別化チェック :「この他社の記事との差別化ってどういうところにあるんですか?」「この記事の強みってなんですか?」 公式リファレンス準拠 :「公式リファレンスのみを参照してベストプラクティスかどうか調査して」 ステップ3:結果の解釈と活用 5〜30分経過後、詳細なレポート出てきます。詳細なレポートには、引用元が明記された詳細な調査レポートが出力されます。このレポートには、発見された問題点や改善提案が含まれて出力されることもあります。(もちろん全肯定の時もあるよ!) 一度リクエストすれば完了時に通知が飛んでくるので別作業をすることも大きな利点ですね。 その出てきたレポートをもとに「簡潔な結論出して」って言うと、改善すべき点があるかって聞くと、レポートから更に要約してくれます。レポートをもとにPDCAサイクルを回せます。 4. 【実体験】よくある判定ミスと対処法 ここでは実際に会った出力ミスについて紹介していきます。もちろんAIも間違うこともあります。 レポートに対して納得がいかない ハルシネーションってあるじゃないですか。簡単にいうとAIが嘘を吐くってやつなんですが、これはレポートと主張が噛み合わないという結果として現れます。 そういう時はブログに書かれている内容が不足しており、主張がAIにも伝わっていないか考慮不足があると解釈しています。 そういう時はブログ側を加筆することで対応しています。加筆時は再度調べ直して、情報を保管することでAIを納得させることができます。 「リンクがない」と言われた時の対処法 実際、PDF内にリンクを挟んでいたのですが、Geminiではリンクが挿入されていないという判定で公式リファレンスの挿入が推奨されました。 そういう時は1回聞き直すんですよね。「リンクは含まれていると思います。再度確認をしてください」って聞いたら再度PDFファイルを読み込み確認を行なってくれます。 PDFが参照できませんでした これは100%プロンプトが悪いですね。何をして欲しいのかが伝わっていない可能性があります。 こういう時はプロンプトを改修しましょう。またリサーチの計画を確認して意図が伝わっているか確認するのも一つの手になります。 「情報が古い」と指摘された時の確認方法 「情報が古いですね」って言われて、僕の情報が新しくて、Geminiが参照してた情報が古いってこともあったんで、そういう時は自分が参照した情報っていうのを送って「これが最新の情報じゃないですか」っていうの聞くと修正して謝ってくれます。 対処に重要な観点 こちらはAIに追加で知識を与える際に重要な観点になります。 再確認の依頼:「もう一度詳しく確認してください」 情報源の明示:重要なURLやリンクを直接共有 複数回のチェック:一度に全てを判定せず部分的に検証 5. 応用テクニック:さらに効果を高める使い方 プロンプトで効果を高める方法はたくさんあります。ですが、最終的に人間の目を入れることは絶対に忘れないでください。 複数の視点からの品質チェック 今回はファクトチェックと多角的視点という比較的曖昧な表現で調査を依頼しました。観点を限定して依頼することも可能です。 SEO観点から トレンド観点から 公式リファレンスのみを参照して 重要としている観点をに重みを置いて調査することが可能です。観点の追加をすることで、自分が意図した方向性での調査を要求することができます。 競合記事との差別化チェック 技術ブログは差別化が大事だったりします。個人的には勉強したことは全て技術ブログとして出していいじゃないと思っています。 ブログのアウトラインレベルで記事を共有し、すでにネット上に同様の主張をしているドキュメントがないかの調査などに活用をすることができます。 継続的な品質管理 何度もレビューを依頼しても、嫌な顔をしないというのがAIサービスの良い点ですね。Deep Researchで検証をした内容から変更を加えて再度実行することで質が向上していきます。 最大で3度レビューを通せば、人間が見ても投稿としては問題ないレベルに達すると思います。 6. 知っておくべき注意点:Deep Researchの限界と対策 やっぱ注意点はもちろんあるんですよね。 AIによる判断の限界を理解する やはり大事なのはAIも人間もミスをするし嘘を吐くという点ですね。 間違うことがもちろんあるので、最終的にAIが出してきたレポートって人間が見る必要があります。 重要なポイント : ハルシネーションリスク:AIの言ってることを全て100%信じるわけにはいかない 個人ブログも公式リファレンスも同等のソースとして扱う可能性がある 最終確認は人間が行う必要がある 記事は自分で書いておかないといけない やっぱり自分で書いたブログと公式リファレンスを一個ずつ確認するのは途方もない作業です。ですがDeep Researchが出してきたレポートと自分のブログとの比較であれば、手間自体はグッと下がります。 レポートを聞いてレビューを受けたという気持ちで修正をしましょう。そして自分があってる確証があるなら聞きましょう。判断がつかなくても遠慮なくAIに聞くのも一つに手だと思います。 コスト面の考慮 弊社はGoogleのワークスペースで契約してるので使いたい放題できるって言うところがあります。 個人利用の場合は、自分が契約しているAIサービスがSearch系のツールを提供していれば同等の検証を行うことは可能です。ですが、大きな規模のファイルであれば制限がかかる可能性はあります。そこは契約しているツールに合わせて、ファイルサイズを小さくするなどのカスタマイズが必要になります。 情報の鮮度について AIはリアルタイムでウェブ情報を参照しますが、特定の分野(例:最新のバグ情報、直近で非推奨となったAPI、まだ公式ドキュメントに反映されていない極めて新しい技術動向など)では、情報の鮮度に限界がある場合があります。特に重要な情報については、必ず人間が最新の公式情報を確認し、情報の取捨選択を行う必要があります。 一番新鮮な情報はGitHub Issueだと思っています。そこ専用の検索エージェントとか出たら絶対使いますね。Issueでは暫定的な対応法なども取り扱っているので、GitHub Issueに関しては人間の調査が必要ですね。 7. これからの時代のブログ品質管理 従来の品質管理との違い 時間効率 :数時間→5-30分程度での初期レビュー 網羅性 :人間の見落とし→AIの包括的チェック 継続性 :一回限り→定期的な自動チェック 読者にとっての価値 不安なんだったら評価してもらう。AIが改善案を人間が検証・判断のサイクルを回すことで技術ブログの質は上がります。この点から、第一読者にAIを据えることは読者にとっても価値があります。 レビューを通して、不足点を補うことでコンテンツ内に含まれる情報量が増えて、読者が受け取ることができる情報量は確実に増大します。 今後の展望 人間が自分の手元の環境で検証したって事はそれだけで価値があると思います。1番は自分のところで試す、自分の環境で試して動いたものを発表することです。その次に大事なのはその情報どこ見て作ったかなんですよね。 そういった正確性をAIが担保してくれるようになるといいなと思っています。 現状は1読者の意見だと軽い感じで受け取りましょう。 バグやハック的な内容であれば公式リファレンスに載ってないこともあります。そういう部分はダイレクトに誰かの助けになる情報なので自信を持って出しましょう。 8. 今すぐ始められる:最初の一歩 今回のお話で大事なこと AIを第一読者として活用する :人間の専門家に頼む前に、まずAIでファクトチェック 最終確認は必ず人間が行う :AIの判断を100%信じず、自分の目で情報源を確認 自分で記事を書いてからチェック :内容を把握している状態でファクトチェックするのが効果的 情報源を必ず確認する :AIが提示したリンク先を実際に見に行く習慣をつける 今まで4年で150本ほどブログを書いていますが、直近の10本ほどはこの手法を使っています。体感でブログの質が上がっています。ブログの長さも長くなっています。 ぜひ読んでもらえると嬉しいです。 2025-06-18 Azure Static Web Apps: x-ms-client-principalで安全なロールベース制御 2025-06-09 Notebook LMへのデータ収集をSlack Botで効率化する開発 with Google Docs 技術ブログ以外への適用可能性 実は僕自身も、技術ブログ以外でもDeep Researchを活用してます。 デザインガイドライン調査からプロンプト化まで デザインのガイドライン調査をしてそれをプロンプト化するみたいな使い方ですね。例えば「Material Design 3の最新ガイドラインを調査して、デザインレビュー用のチェックリストを作成して」みたいな感じで投げると、包括的な情報を集めてくれて、それをそのままプロンプトを作成する際の参考情報にしています。 SNS戦略の最適化 Xのガイドラインなど投稿の優位性とかも調査してます。「X(Twitter)でエンゲージメント率を上げるための最新の投稿戦略を調査して」って投げると、アルゴリズムの変更点から効果的な投稿時間、ハッシュタグ戦略まで調べてくれるんで、それをベースにプロンプトに組み込むための調査としてGemini Deep Researchを使ってる感じです。 他分野での応用例 この使い方って、いろんな分野に応用できるんですよね: マーケティング分野 : 各プラットフォームのガイドライン調査、競合他社のSNS戦略分析 コンテンツ制作 : デザイントレンドの調査、UI/UXのベストプラクティス調査 ビジネス戦略 : 業界動向の調査、新規事業のマーケットリサーチ 学術・研究 : 論文の初期調査、最新研究動向の調査 要は「初期調査→プロンプト化→業務効率化」っていう流れで、どんな分野でも使えるってことですね。技術者じゃなくても、マーケターでもデザイナーでも、研究者でも、みんなが使えるツールだと思います。 ただし!調査結果をそのまま鵜呑みにするのは危険ですよ。僕も必ず自分の目で確認してから使うようにしてます。AIが間違うこともあるし、情報が古い場合もあるので、最終的には人間がチェックするっていうのが大事ですね。効率化はしつつも、責任は人間が持つっていうスタンスが重要だと思います。 この活用ってちょっと前から話題になってるところなので、ぜひぜひその技術ブログって言う観点でも活用の道を模索していければいいなと個人的にめちゃくちゃ思ってます。体感乗り遅れてますけどね~~ 早くファクトチェック完璧にできるようになんねえかなぁ~ 龍ちゃん いろんなブログ書いてるので、ぜひ僕の個人ページでも見てやってください ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【実践解説】技術ブログ品質チェック術|Gemini Deep Researchで5分検証 first appeared on SIOS Tech. Lab .
アバター
挨拶 ども!6月は結構激しめにブログを投稿していたんですが、7月もがっつり忙しくなりそうで、ウハウハの龍ちゃんです。ブログサムネイル評価用プロンプトを結構な時間をかけて作りました。今までのように人にSlack投げて確認ではなく、生成AIにチャットを投げるだけで確認出来ており、サムネイルにも力が入っています。 さて!今回は「Azure Static Web Apps+Bicepの発展編:Azure Static Web Apps+Managed Functions+GitHub(カスタム認証)の環境をGitHub Actions after Bicepでデプロイ」というてんこ盛りの内容になっています。こちらを理解できれば、Azure Static Web Appsの基礎は出来上がったように感じます。 なかなかの長編になったので、初めての方は5日ほどかけてじっくりと、中級者以上の方はミスを指摘してやるってテンション感でご覧ください。 はじめに・概要 この記事で学べること 今回は、環境構築からデプロイまで横断的に解説していきます。以下にまとめます。 環境構築:DevContainer ローカル環境でAzure Static Web AppsとAzure Functionsを開発するために必要な環境 ローカル環境からBicepでAzureリソースを管理するのに必要な環境 Azure Functions開発(Managed Functions Node with TypeScript) APIの簡易的な仕様書から、TypeScriptによる実際のコーディング Azure Static Web Apps開発(Next.js) GitHub OAuthを活用した認証基盤 Managed Functionsと連携して認証状態を管理する方法 Bicep:ローカル環境からAzureリソースのコマンドデプロイ デプロイ環境をコード一発でデプロイ・クリーンナップできるようなbashファイル管理 GitHub Actions Azure Static Web AppsとAzure Functions(Managed Functions)をGitHub Actionsからデプロイする 設計から、Azure Static Web AppsとAPI統合、認証機能の実装方法。DevContainerからBicep、GitHub Actionsまでの一連の開発フローとして学習することができます。 以降はAzure Static Web AppsはSWAと省略した形で解説していきます。 技術スタック 使用している技術などは以下にまとめました。 カテゴリ 技術・ツール バージョン 備考 デプロイ環境 Azure Static Web Apps – Standardプラン:カスタム認証(GitHub) Azure Functions – Managed Functions 開発環境 DevContainer – – Azure CLI 2.74.0 – SWA CLI 2.0.6 – GitHub CLI 2.74.2 – Function Core Tools 4.0.7317 – フロントエンド Next.js 15.3.4 静的エクスポート React 19 – バックエンド Node.js + TypeScript – Azure Functions v4 Programming Model 今回は、データベースなどは抜きにしてSWA内ですべてが完結しているように構成しています。1点注意点としては、カスタム認証を使用する関係上、SWAのプラン設定をStandardにする必要があります。無料枠でも十分強力ですが、認証プロバイダーの追加設定などはStandardプランからでないと使用することができません。認証機能が必要ない方は、Maneged Functionsも無料枠があるのでBicepテンプレートをカスタマイズしてご利用ください。 コラム:Azure SWAのStandardとFreeプランの比較 Azure Static Web Appsには、個人のプロジェクトや小規模なウェブサイトに適した Freeプラン と、運用環境のアプリケーションや大規模なプロジェクトに適した Standardプラン があります。それぞれのプランで利用できる機能の範囲が異なり、特にManaged Functionsの利用方法に影響があります。 プラン別機能比較 機能 Freeプラン<br/>(個人/小規模プロジェクト向け) Standardプラン<br/>(運用/大規模プロジェクト向け) ウェブホスティング GitHub/Azure DevOps統合 グローバル分散 (静的コンテンツ) (静的コンテンツ) SSL証明書 (無料、自動更新) (無料、自動更新) ステージング環境 アプリあたり3つまで アプリあたり10個まで アプリの最大サイズ アプリあたり250MB アプリあたり500MB カスタムドメイン アプリあたり2つまで アプリあたり5つまで API (Azure Functions) マネージド (SWAに統合) マネージド または 独自のFunctionsアプリ利用 認証プロバイダー 事前構成済み (サービス定義済み) カスタム登録も可能 関数によるカスタムロール プライベートエンドポイント SLA (サービスレベル契約) なし 99.95% 帯域幅 サブスクリプションあたり月100GBまで (無料) サブスクリプションあたり月100GBまで (超過分課金) カスタマーサポート なし Managed Functionsが受ける影響 Azure Static Web AppsにおけるAPIバックエンドとしてのManaged Functionsは、選択するプランによって利用方法が大きく異なります。 Freeプランの場合 Managed Functionsのみ が利用可能です。これはAzure Static Web Appsに組み込まれたFunctionsアプリであり、ユーザーが個別にAzure Functionsリソースを作成・管理する必要がありません APIの実行回数には、 月間100万回の無料実行 が含まれます コールドスタートが発生しやすく、API呼び出しの最初の応答に時間がかかる場合があります Standardプランの場合 Managed Functions の利用に加えて、 既存の独自のAzure FunctionsアプリをStatic Web AppsのAPIバックエンドとして持ち込むことが可能 です 独自のFunctionsアプリを利用することで、HTTP以外のトリガー(例: タイマートリガー、キュートリガー)や、より複雑なバインディングを利用できるようになります 既存のFunctionsアプリをStatic Web Appsに統合したい場合に特に便利です どちらのプランを選ぶべきか? 以下の3つの観点から、Standardプランを検討してください: 1. アーキテクチャ戦略(マイクロサービス化) フロントエンドとバックエンドを分離 したマイクロサービス構成にしたい場合 既存のAzure Functionsアプリを再利用 したい、または独立して管理したい場合 HTTPエンドポイント以外のトリガー(タイマー、キュー等)が必要な場合 2. プロジェクトの成長・運用要件 予想されるトラフィック量が多い 、または帯域幅の無料枠を超える可能性がある場合 ミッションクリティカルなアプリケーション で99.95%のSLAが必要な場合 3つ以上のステージング環境 (開発、検証、本番等)が必要な場合 3. 認証・セキュリティ要件 カスタム認証プロバイダーの登録 が必要な場合(GitHub、Google以外の独自認証等) 関数によるカスタムロール 制御が必要な場合 プライベートエンドポイント でのセキュアな通信が必要な場合 まとめ 基本的には、個人的なプロジェクトやシンプルなウェブサイトであればFreeプランで十分ですが、ビジネス用途や大規模なアプリケーションにはStandardプランの検討をお勧めします。 今回の記事では、カスタム認証機能を使用するためStandardプランを選択していますが、認証が不要な場合はFreeプランでも同様の構成を構築できます!というか、課金が毎秒走っていると考えると怖いので、極力Freeプランを使うようにしています。 最終的な成果物 今回解説するアプリの動画です。ページとAPIをそれぞれ2つずつ作成し、認証あり・なしのパターンを網羅できるよう設計しています。 前提条件・準備 前提条件は以下の通りです。 Azureで有効なサブスクリプションを持っている GitHub.comに登録済み Dockerを使用できる環境 今回は、Azure Static Web AppsのStandardプランを使用するため、有効なサブスクリプションが必要です。また、GitHub Actionsを使用してデプロイを行い、認証プロバイダーにGitHub OAuthを使用するため、GitHub.comのアカウントは必須となります。 Dockerに関しては、今回の技術スタックはNode.jsに統一しているため、ローカル環境に有効なNode.js環境があれば開発を進めることができます。ただし、Dockerが入っていればコピー&ペーストで環境が立ち上がるため、こちらを推奨します。 プロジェクト設計・アーキテクチャ システム構成図 システム構成に関しては、上記のようになっています。作成するAzureリソースとしては、SWAのみとなっており、カスタム認証プロバイダーとしてGitHub OAuthを使用します。デプロイに関しては、Azureリソースに関してはBicep、アプリケーションのデプロイに関してはGitHub Actionsでそれぞれ管理します。 フロントエンドに関しては、Next.jsの静的エクスポートを使用してデプロイを行います。 ディレクトリ構造 . ├── .github # GitHub Actions用 ├── api # API用ディレクトリ ├── frontend # フロント用ディレクトリ Next.js静的エクスポート ├── infra # Infrastructure as Code用ディレクトリ └── swa-cli.config.json # SWA CLIのローカル起動用設定ファイル 全体のディレクトリ構成としては上記のようになります。それぞれ、他のリソースを参照せずに個別のディレクトリでそれぞれ開発をすることができます。 フロントエンド設計 ページ構成と各ページの目的 それでは、今回のアプリケーションのページ構成について見ていきましょう。まず、作成するルート一覧を明示しておきますね。 作成ルート一覧 / – トップページ(パブリック) /protected – 保護ページ(認証必須) シンプルな構成ですが、それぞれに明確な役割があります。 トップページ( / ) トップページは、いわば「認証の玄関口」として設計しています。ここでの主な目的は以下の通りです: 認証状態の可視化 : ユーザーが現在ログインしているかどうかを一目で分かるようにする 認証フローの起点 : 未認証ユーザーにとってのログイン入口 ユーザー情報の表示 : 認証済みユーザーには基本的なプロフィール情報を提示 ナビゲーションハブ : /protected へのリンク 実装としては、認証状態に応じて動的にUIを切り替える設計になっています。未認証時は「GitHubでログイン」ボタンを表示し、認証済み時はユーザー名やアバター、そして保護ページへのリンクを表示します。これにより、ユーザーは自分の状態を即座に把握できるというわけですね。 保護ページ( /protected ) 保護ページは、Azure SWAの認証機能の真価を発揮する場所です。ここでの設計ポイントは: Azure SWAによる保護 : フロントエンドでの認証チェックに依存しない 認証済みユーザー専用コンテンツ : より詳細なユーザー情報や機能の提供 セキュリティ情報の表示 : 認証システムの仕組みを理解してもらう この設計の面白いところは、ソースコード内で「認証チェック」を行わないことです。Azure SWAを使う場合は、サーバーレベルで保護されているため、そもそも未認証ユーザーはこのページにたどり着けません。 各ページのデザインは今回主題ではないので、生成AIを活用してデザインを生成してもらいます。 ルート保護設計とアクセスフロー Azure SWAの最大の魅力の一つが、宣言的なルート保護による強力なセキュリティ機能です。設定一つで堅牢な認証システムを実現できるというのは、本当に画期的ですよね。 アクセスフロー全体像 今回の設計で重要となる点としては、401エラー(未認証)が発生した瞬間に、ユーザーを /.auth/login/github に自動リダイレクトする仕組みです。これにより、ユーザーは「アクセス拒否」のような不親切な画面を見ることなく、スムーズに認証フローに誘導されます。 認証完了後は、元々アクセスしようとしていたページに戻ってくるのも、Azure SWAが自動的に処理してくれます。フロントエンド側で複雑なリダイレクト制御を書く必要がないというのは、大きなメリットです。 API設計 エンドポイント仕様の全体像 今回のシステムでは、以下の2つの主要エンドポイントを設計しました。 /api/user-info : ユーザー情報取得(認証状態問わず) /api/protected-data : 保護されたデータ取得(認証必須) /api/user-info では、フロントエンドから認証状態と未認証状態でのアクセスを確認するために、認証状態を問わずアクセスできるエンドポイントとして設計しています。 /api/protected-data では、認証されていない場合はエラーを返答し、ダミーデータを返すエンドポイントとして設計しています。 user-infoエンドポイントの設計 user-infoエンドポイントは、現在のユーザーの認証状態と基本情報を返すAPIです。未認証時でもエラーにならずにリクエストが成功するように設計します。 リクエスト仕様 Method: GET Path: /api/user-info Authentication: 不要 Parameters: なし レスポンス型定義 interface UserInfo { userId: string | null; name: string | null; email: string | null; provider: string | null; roles: string[]; isAuthenticated: boolean; } 成功レスポンス例(認証済み) { "userId": "github|12345678", "name": "yamada.taro", "email": "yamada.taro@example.com", "provider": "github", "roles": ["authenticated"], "isAuthenticated": true } 成功レスポンス例(未認証) { "userId": null, "name": null, "email": null, "provider": null, "roles": [], "isAuthenticated": false } ここで重要なのは、認証されていない場合も エラーではなく正常なレスポンス を返すことです。 isAuthenticated: false として状態を明示することで、フロントエンド側で適切な処理分岐ができます。 protected-dataエンドポイントの設計 protected-dataエンドポイントは、認証が必須のAPIです。認証されていないユーザーには401エラーを返し、アクセスを拒否します。 リクエスト仕様 Method: GET Path: /api/protected-data Authentication: 必須 Parameters: なし レスポンス型定義 interface ProtectedData { userId: string; message: string; timestamp: string; userNumber: number; } 成功レスポンス例 { "userId": "github|12345678", "message": "こんにちは、ユーザーgithub|12345678さん!", "timestamp": "2024-12-15T15:45:30.000Z", "userNumber": 456 } エラーレスポンスの統一設計 APIの一貫性を保つため、エラーレスポンス形式を統一しています。すべてのエラーレスポンスは以下の構造に従います。 エラーレスポンス型定義 interface ErrorResponse { error: string; message: string; } 認証エラー(401 Unauthorized) { "error": "Unauthorized", "message": "保護されたデータにアクセスするには認証が必要です" } サーバーエラー(500 Internal Server Error) { "error": "Internal server error", "message": "Application Crash" } この統一により、フロントエンド側でのエラーハンドリングが 大幅に簡素化 されます。どのAPIでも同じ構造でエラー情報を取得できるためです。 認証チェック方法の設計 Azure SWAでは、認証済みユーザーの情報が x-ms-client-principal ヘッダーにBase64エンコードされたJSON形式で渡されます。これは、Azure SWAの大きな特徴の一つですね。 ClientPrincipal型定義 interface ClientPrincipal { identityProvider: string; userId: string; userDetails: string; userRoles: string[]; } 認証チェックの流れは以下のようになります: リクエストヘッダーから x-ms-client-principal を取得 Base64デコードしてJSON解析 成功時は認証済みとして処理、失敗時は未認証として処理 このヘッダーは Azure SWAのリバースプロキシによって自動的に付与 されるため、偽装される心配はありません。 こちらの処理の流れは公式リファレンスにまとまっています。 開発環境構築(DevContainer) この章では、DevContainer環境を構築していきます。作成するディレクトリ構成は以下になります。 . └── .devcontainer └── devcontainer.json DevContainer設定  devcontainer.json 今回の開発に必要なツールを全て含んだDevContainer設定を作成します。Node.js、Azure CLI、Functions Core Tools、SWA CLIを含む開発環境を構築していきましょう。 { "name": "Azure SWA include API & Bicep Development", "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye", "features": { "ghcr.io/devcontainers/features/azure-cli:1": {}, "ghcr.io/devcontainers/features/github-cli:1": {} }, // npm@11.4.2に関してはnoticeが出ていたので対応 "postCreateCommand": "npm install -g npm@11.4.2 @azure/static-web-apps-cli azure-functions-core-tools@4", "customizations": { "vscode": { "extensions": [ "ms-azuretools.vscode-bicep", "GitHub.vscode-github-actions", "ms-vscode.azure-account" ] } }, "forwardPorts": [ 3000, 7071, 4280 ], "mounts": [ "source=${localEnv:HOME}/.azure,target=/home/node/.azure,type=bind,consistency=cached" ] } ベースイメージとインストール ベースイメージとしては、 Microsoftが提供しているNode環境 を使用しています。Node.js 20系の安定版が含まれており、今回のプロジェクトに必要な環境が整っています。 features として、Azure CLIとGitHub CLIを導入しています。これにより、Azureリソースの管理とGitHubとの連携が可能になります。 postCreateCommand で、npmパッケージとしてFunctions Core Tools@v4とSWA CLIをグローバルインストールしています。npmのバージョンアップも行っており、これは最新の機能とセキュリティ修正を適用するためです。 VS Code拡張機能 開発効率を上げるため、以下の拡張機能を自動インストールします: Bicep : Azureリソース定義のシンタックスハイライトと補完 GitHub Actions : ワークフロー編集の支援 Azure Account : Azure認証とリソース管理 ポートフォワーディング 開発中に使用する主要ポートを事前に設定しています: 3000 : Next.jsフロントエンド 7071 : Azure Functions API 4280 : SWA CLI統合サーバー マウント設定 "mounts": [ "source=${localEnv:HOME}/.azure,target=/home/node/.azure,type=bind,consistency=cached" ] ホスト側にある .azure ディレクトリをマウントしています。これにより、ホスト側で az login をしていればDevContainer環境内でも認証済みとして扱うことができます。 セキュリティに関する注意 この設定は、Azure認証情報をコンテナ内で共有するため、セキュリティリスクがあります。使用する場合は十分に注意し、本番環境や共有環境では適切な認証方法を検討してください。 開発環境の起動確認 環境を立ち上げた後は、各CLIが正常にインストールされているか確認しましょう。 Azure CLI確認 az --version # azure-cli 2.74.0 # core 2.74.0 # telemetry 1.1.0 # # Dependencies: # msal 1.32.3 # azure-mgmt-resource 23.3.0 # # Python location '/opt/az/bin/python3' # Config directory '/home/node/.azure' # Extensions directory '/home/node/.azure/cliextensions' # # Python (Linux) 3.12.10 (main, May 27 2025, 09:13:17) [GCC 10.2.1 20210110] # # Legal docs and information: aka.ms/AzureCliLegal # # # Your CLI is up-to-date. 併せてマウント設定が正しく動作しているか。認証状態の確認をしておきましょう。 az account show ホスト側で認証済みの場合、アカウント情報が表示されます。認証されていない場合は、以下のコマンドで認証を行ってください。 az login GitHub CLI確認 gh --version # gh version 2.74.2 (2025-06-18) # https://github.com/cli/cli/releases/tag/v2.74.2 SWA CLI確認 swa --version # 2.0.6 Functions Core Tools確認 func --version # 4.0.7317 それぞれのバージョン情報が表示されれば成功です。これで、Azure Static Web Appsの開発に必要な全てのツールが使用可能になりました! これで、DevContainer環境での開発準備が完了しました。次章からは、実際にAzure FunctionsのAPI実装に入っていきます。 Azure Functions API実装 この章では、Azure Functions環境を構築・実装をしていきます。作成するディレクトリ構成は以下になります。ファイルの作成自体は、直接作るのではなくFunctions Core Toolsを介して生成をしていきます。 プロジェクト初期化 . └── api ├── .funcignore ├── .gitignore ├── host.json ├── local.settings.json ├── package.json ├── package-lock.json ├── src │ └── functions │ ├── protected-data.ts │ └── user-info.ts ├── tsconfig.json └── .vscode └── extensions.json まずは、 api ディレクトリを用意してディレクトリに遷移します。中で初期化コマンドを実行すると以下のファイルが自動生成され、 npm install が実行されます。 mkdir api cd api func init --typescript これで初期化は完了です。次は、各エンドポイントの作成を進めていきます。エンドポイントは Functions Core Tools(公式リファレンス) で生成します。コマンドを通して作成することで、基本の関数が準備された状態でスムーズな開発を進めることができます。使用することができるテンプレートは以下のコマンドで取得することができます。 func templates list /api/user-info エンドポイント実装 /api/user-info エンドポイントの作成を進めていきます。 api ディレクトリで以下のコマンドを実行してください。 func new --name user-info --template "HTTP trigger" --authlevel "anonymous" 上記のコマンドで src/functions 内に user-info.ts というファイルが生成されます。認証レベル「匿名」で user-info というファイル名で作成されます。作成されたファイルの中には、 --name で指定した名前のパスに対してHELLO Worldを返す関数が作成されています。 中身を確認して、変更を加えてください。 import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions'; interface ClientPrincipal { identityProvider: string; userId: string; userDetails: string; userRoles: string[]; } interface UserInfo { userId: string; name: string; email: string; provider: string; roles: string[]; isAuthenticated: boolean; } export async function userInfo(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log('Processing user-info request'); try { // x-ms-client-principalヘッダーの取得 const clientPrincipalHeader = request.headers.get('x-ms-client-principal'); if (!clientPrincipalHeader) { context.log('ヘッダーにx-ms-client-principalが確認することができず、認証状態を確認することができませんでした。'); return { status: 200, jsonBody: { isAuthenticated: false, userId: null, name: null, email: null, provider: null, roles: [] } }; } // Base64デコード const clientPrincipalData = Buffer.from(clientPrincipalHeader, 'base64').toString('utf-8'); const clientPrincipal: ClientPrincipal = JSON.parse(clientPrincipalData); context.log('Client Principal:', clientPrincipal); // ユーザー情報の整形 const userInfo: UserInfo = { userId: clientPrincipal.userId, name: clientPrincipal.userDetails, email: clientPrincipal.userDetails, // GitHubの場合はユーザー名,Googleならメールアドレス provider: clientPrincipal.identityProvider, roles: clientPrincipal.userRoles || [], isAuthenticated: true }; return { status: 200, headers: { 'Content-Type': 'application/json' }, jsonBody: userInfo }; } catch (error) { context.log('Applicatoin Crash', error); return { status: 500, jsonBody: { error: 'Internal server error', message: 'Failed to process user information' } }; } } app.http('user-info', { methods: ['GET'], authLevel: 'anonymous', route: 'user-info', handler: userInfo }); こちらの関数での処理は、現在のユーザーの認証状態と基本情報を返すAPIです。特徴としては、未認証時でもエラーにならずにリクエストが成功します。 コードは大きく3つに分かれています。 ヘッダーから x-ms-client-principal を取得し、ない場合は status:200 (ユーザー情報なし)を返答 ヘッダーからデコードしてユーザー情報を取得し、 status:200 (ユーザー情報あり)を返答 上記の処理で問題が発生した場合は status:500 を返答 デコードの処理は、 公式リファレンスで紹介されている手法 をもとに構築しています。その他のAPIの実装はAPI設計に準拠しています。 /api/protected-data エンドポイント実装 /api/protected-data エンドポイントの作成を進めていきます。 api ディレクトリで以下のコマンドを実行してください。 func new --name protected-data --template "HTTP trigger" --authlevel "anonymous" 上記のコマンドで src/functions 内に protected-data.ts というファイルが生成されます。認証レベル「匿名」で protected-data というファイル名で作成されます。作成されたファイルの中には、 --name で指定した名前のパスに対してHELLO Worldを返す関数が作成されています。 中身を確認して、変更を加えてください。 import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions'; interface ClientPrincipal { identityProvider: string; userId: string; userDetails: string; userRoles: string[]; } interface ProtectedData { userId: string; message: string; timestamp: string; userNumber: number; } // 認証チェック用のヘルパー関数 function checkAuthentication(request: HttpRequest, context: InvocationContext): ClientPrincipal | null { const clientPrincipalHeader = request.headers.get('x-ms-client-principal'); if (!clientPrincipalHeader) { context.log('認証ヘッダーが見つかりませんでした'); return null; } try { const clientPrincipalData = Buffer.from(clientPrincipalHeader, 'base64').toString('utf-8'); return JSON.parse(clientPrincipalData); } catch (error) { context.log('Client principalの解析に失敗しました:', error); return null; } } // 軽量なユーザー固有データ生成 function generateUserData(userId: string): ProtectedData { // userIdをベースにした簡単なシード値 const seed = userId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); return { userId, message: `こんにちは、ユーザー${userId}さん!`, timestamp: new Date().toISOString(), userNumber: seed % 1000 // 0-999の範囲のユーザー番号 }; } export async function protectedData(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log('protected-dataリクエストを処理中'); try { // 認証チェック const clientPrincipal = checkAuthentication(request, context); if (!clientPrincipal) { return { status: 401, headers: { 'Content-Type': 'application/json' }, jsonBody: { error: 'Unauthorized', message: '保護されたデータにアクセスするには認証が必要です' } }; } context.log('認証済みユーザー:', clientPrincipal.userId); // 軽量なダミーデータを生成 const userData = generateUserData(clientPrincipal.userId); return { status: 200, headers: { 'Content-Type': 'application/json' }, jsonBody: userData }; } catch (error) { context.log('protected-dataリクエストの処理中にエラー:', error); return { status: 500, jsonBody: { error: 'Internal server error', message: '保護されたデータの取得に失敗しました' } }; } } app.http('protected-data', { methods: ['GET'], authLevel: 'anonymous', // SWAのManaged Functionsのため route: 'protected-data', handler: protectedData }); こちらの関数の処理は、認証されていないユーザーには401エラーを返し、認証済みの場合はダミーデータを返答します。 処理として、 /api/user-info と大差ありません。先ほどのヘッダーの取得からデコードまでをヘルパー関数として切り出しています。 処理取しては、大きく4つに分類されます。 checkAuthentication :ヘッダーから x-ms-client-principal を取得、デコードした情報を返答する。情報がない場合は、 null を返答 checkAuthentication からの戻り値が null : status:401 を返答 checkAuthentication からの戻り値がユーザー情報: generateUserData を使用してダミー情報を作成して、 status:200 で返答 上記の処理で問題が発生した場合は status:500 を返答 デコードの処理は、 公式リファレンスで紹介されている手法 をもとに構築しています。その他のAPIの実装はAPI設計に準拠しています。 ローカルでのAPI動作確認 それでは、作成したAPIが正しく動作するかローカル環境で確認していきましょう。 ローカル環境の制限事項 ローカル環境では認証ヘッダーが自動付与されないため、テスト範囲は限定的ですが、基本的な動作確認は可能です。 認証ヘッダーの模擬 Azure SWA の認証ヘッダー( x-ms-client-principal )は自動付与されません 認証済み状態のテストはローカルでは困難です 本格的な認証テストは本番環境またはSWA CLI統合環境で行う必要があります Azure Functions API のローカル起動 まず、APIディレクトリで Functions Core Tools を使ってローカルサーバーを起動します。 cd api func start 正常に起動すると、以下のような出力が表示されます: Azure Functions Core Tools Core Tools Version: 4.0.7317 Commit hash: N/A +5ca56d37938824531b691f094d0a77fd6f51af20 (64-bit) Function Runtime Version: 4.1038.300.25164 [2025-06-30T14:09:22.328Z] Worker process started and initialized. Functions: protected-data: [GET] http://localhost:7071/api/protected-data user-info: [GET] http://localhost:7071/api/user-info For detailed output, run func with --verbose flag. エンドポイントが正しく表示されていることを確認してください。 /api/user-info エンドポイントのテスト まずは認証状態を問わずアクセス可能な user-info エンドポイントをテストします。こちらはcURLでもよいですし、ブラウザで実行してもよいです。 cURLでのテスト curl http://localhost:7071/api/user-info 期待されるレスポンス ローカル環境では認証ヘッダーが存在しないため、未認証状態のレスポンスが返されます: { "userId": null, "name": null, "email": null, "provider": null, "roles": [], "isAuthenticated": false } 確認ポイント ステータスコードが 200 OK であること レスポンスの型が設計通りであること isAuthenticated が false になっていること 各プロパティが正しく null または空配列になっていること /api/protected-data エンドポイントのテスト 次に、認証が必須の protected-data エンドポイントをテストします。 cURLでのテスト curl http://localhost:7071/api/protected-data 期待されるレスポンス 認証ヘッダーが存在しないため、401エラーが返されます: { "error": "Unauthorized", "message": "保護されたデータにアクセスするには認証が必要です" } 確認ポイント ステータスコードが 401 Unauthorized であること エラーレスポンスの形式が統一されていること error と message プロパティが含まれていること デバッグとログ確認 開発中は、Function Core Tools のコンソール出力でデバッグ情報を確認できます。 ログ出力の確認 API関数内の context.log() による出力がコンソールに表示されます: [2025-06-30T14:13:03.946Z] Executing 'Functions.protected-data' (Reason='This function was programmatically called via the host APIs.', Id=3570bfd5-7ed0-4ea7-a34f-262df44324ac) [2025-06-30T14:13:03.951Z] protected-dataリクエストを処理中 [2025-06-30T14:13:03.951Z] 認証ヘッダーが見つかりませんでした [2025-06-30T14:13:03.952Z] Executed 'Functions.protected-data' (Succeeded, Id=3570bfd5-7ed0-4ea7-a34f-262df44324ac, Duration=5ms) 詳細ログの表示 より詳細なログが必要な場合は、 --verbose オプションを使用します: func start --verbose トラブルシューティング 開発時に詰まった点をトラブルシューティングとしてまとめておきます。 エンドポイントにアクセスできない Functions Core Tools が正常に起動しているか確認 ポート 7071 が他のプロセスで使用されていないか確認 ファイアウォール設定を確認 TypeScript コンパイルエラー npm run build でビルドエラーがないか確認 型定義の不整合がないかチェック レスポンスが期待通りでない context.log() でデバッグ出力を追加 リクエストヘッダーが正しく設定されているか確認 ソースコードに変更を加えたのに変更が反映されない func start は変更後のソースを読み取って同期してくれません 一度 Ctrl+C で実行を落として、再起動してみてください これで、ローカル環境でのAPI基本動作確認が完了しました。次に、フロントエンドの実装に移りましょう。認証機能を含む完全なテストは、SWA CLI での統合テスト環境で行います。 コラム:なぜauthlevelはanonymousでよいのか? Azure Functions では通常、API の認証レベルを function 、 admin 、 anonymous などから選択できます。しかし、Azure Static Web Apps の Managed Functions では anonymous を設定するのが適切です。 Azure Functions単体での認証レベル function :Function Key が必要 admin :Master Key が必要 anonymous :認証不要 SWA + Managed Functions での認証の仕組み Azure Static Web Apps では、認証・認可を SWA側で一元管理 します: SWAレベルでの保護 : staticwebapp.config.json でルート保護を定義 統合セキュリティ : /.auth システムによる認証管理 Functions側はanonymous :SWAが認証済みリクエストのみをFunctionsに転送 つまり、Functionsレベルでの認証をバイパスし、SWAの統合認証システムを活用することで、よりシームレスで一元化された認証管理が可能になります。Managed Functions では、この仕組みにより安全性を保ちながら開発効率を向上させることができるのです。 Next.jsフロントエンド実装 この章では、Next,js環境を構築・実装をしていきます。作成するディレクトリ構成は以下になります。初期設定はcreate-next-app経由で作成するため、作成するファイルは useAuth.ts / page.tsx / protected>page.tsx の3ファイルになります。 ./frontend ├── eslint.config.mjs ├── next.config.ts ├── next-env.d.ts ├── package.json ├── package-lock.json ├── postcss.config.mjs ├── README.md ├── public ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── page.tsx // トップページ │ │ └── protected │ │ └── page.tsx           // 認証済みルート │ └── hooks │ └── useAuth.ts // 認証系Hooks └── tsconfig.json Next.js設定(静的エクスポート対応) まずは frontend ディレクトリを作成して、ディレクトリに移動します。作成自体は、 npx create-next-app コマンドを通して自動生成を行っています。バージョンとしては15固定で、プロジェクト設定を行っています。 mkdir frontend cd frontend npx create-next-app@15 . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" 静的エクスポート設定のために next.config.ts を変種する必要があります。 import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ output: 'export', // 静的エクスポート設定 distDir: 'out', // ビルドファイルのエクスポート先設定 images: { unoptimized: true // 静的エクスポートでは使用できない画像圧縮の無効化 } }; export default nextConfig; これで、静的エクスポートが実装することができます。 useAuth認証カスタムフック実装 こちらのファイルは hooks ディレクトリを作成し、 useAuth.ts というファイル内にコピー&ペーストをしてください。 import { useState, useEffect } from 'react'; interface UserInfo { isAuthenticated: boolean; user?: { name?: string; email?: string; login?: string; }; } export const useAuth = () => { const [userInfo, setUserInfo] = useState<UserInfo>({ isAuthenticated: false }); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { const fetchUserInfo = async () => { try { const response = await fetch('/.auth/me'); if (response.ok) { const data = await response.json(); if (data.clientPrincipal) { setUserInfo({ isAuthenticated: true, user: { name: data.clientPrincipal.userDetails, email: data.clientPrincipal.userDetails, login: data.clientPrincipal.userId } }); } else { setUserInfo({ isAuthenticated: false }); } } else { setUserInfo({ isAuthenticated: false }); } } catch (err) { console.error('認証情報の取得に失敗しました:', err); setError('認証情報の取得に失敗しました'); setUserInfo({ isAuthenticated: false }); } finally { setLoading(false); } }; fetchUserInfo(); }, []); const login = () => { window.location.href = '/.auth/login/github'; }; const logout = () => { window.location.href = '/.auth/logout'; }; return { ...userInfo, loading, error, login, logout }; }; 処理としては、ログインとログアウトロジックがあります。こちらは、認証プロバイダー(GitHub)としてチューニングされています。 SWAでは認証済みの場合、 直接エンドポイントとして /.auth/me に認証情報が含まれます 。そちらに対して確認を行うことで、フロントエンド側から認証状態の確認を行うことができます。 認証状態の確認中は useState の loading で制御をしています。 トップページ実装 こちらは src > app > page.tsx ファイルを編集してください。まずはコピー&ペーストをしてください。 'use client'; import Link from 'next/link'; import { useAuth } from '@/hooks/useAuth'; import { useState } from 'react'; interface ApiUserInfo { userId: string; name: string; email: string; provider: string; roles: string[]; isAuthenticated: boolean; } interface ApiError { error: string; message: string; } export default function HomePage() { const { isAuthenticated, user, loading, login, logout } = useAuth(); const [apiData, setApiData] = useState<ApiUserInfo | null>(null); const [apiError, setApiError] = useState<string | null>(null); const [apiLoading, setApiLoading] = useState(false); const fetchUserInfo = async () => { setApiLoading(true); setApiError(null); setApiData(null); try { const response = await fetch('/api/user-info', { method: 'GET', headers: { 'Content-Type': 'application/json', }, credentials: 'include', // 認証情報を含める }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiUserInfo | ApiError = await response.json(); if ('error' in data) { setApiError(data.message || data.error); } else { setApiData(data); } } catch (error) { console.error('API呼び出しエラー:', error); setApiError(error instanceof Error ? error.message : 'APIの呼び出しに失敗しました'); } finally { setApiLoading(false); } }; if (loading) { return ( <div className="min-h-screen flex items-center justify-center"> <div className="text-center"> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div> <p className="text-gray-600">認証状態を確認中...</p> </div> </div> ); } return ( <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100"> <div className="container mx-auto px-4 py-16"> <div className="max-w-4xl mx-auto text-center"> <h1 className="text-5xl font-bold text-gray-900 mb-8"> Next.js 15 + Azure SWA </h1> <p className="text-xl text-gray-600 mb-12"> GitHub認証を使用したセキュアなWebアプリケーション </p> <div className="bg-white rounded-lg shadow-lg p-8 mb-8"> <h2 className="text-2xl font-semibold text-gray-800 mb-6"> 認証状態 </h2> {isAuthenticated ? ( <div className="space-y-4"> <div className="flex items-center justify-center space-x-2"> <div className="w-3 h-3 bg-green-500 rounded-full"></div> <span className="text-green-700 font-medium">認証済み</span> </div> {user && ( <div className="bg-gray-50 rounded-lg p-4 text-left max-w-md mx-auto"> <h3 className="font-medium text-gray-800 mb-2">ユーザー情報(クライアント側)</h3> {user.name && ( <p className="text-sm text-gray-600"> <span className="font-medium">名前:</span> {user.name} </p> )} {user.email && ( <p className="text-sm text-gray-600"> <span className="font-medium">メール:</span> {user.email} </p> )} {user.login && ( <p className="text-sm text-gray-600"> <span className="font-medium">ログイン:</span> {user.login} </p> )} </div> )} <div className="flex flex-col sm:flex-row gap-4 justify-center mt-6"> <Link href="/protected" className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition duration-200" > 保護されたページへ </Link> <button onClick={logout} className="bg-gray-500 hover:bg-gray-600 text-white font-medium py-3 px-6 rounded-lg transition duration-200" > ログアウト </button> </div> </div> ) : ( <div className="space-y-4"> <div className="flex items-center justify-center space-x-2"> <div className="w-3 h-3 bg-red-500 rounded-full"></div> <span className="text-red-700 font-medium">未認証</span> </div> <p className="text-gray-600 mb-6"> 保護されたコンテンツにアクセスするには、GitHubアカウントでログインしてください。 </p> <button onClick={login} className="bg-gray-900 hover:bg-gray-800 text-white font-medium py-3 px-6 rounded-lg transition duration-200 flex items-center space-x-2 mx-auto" > <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <path fillRule="evenodd" d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z" clipRule="evenodd" /> </svg> <span>GitHubでログイン</span> </button> </div> )} </div> {/* API情報取得セクション */} <div className="bg-white rounded-lg shadow-lg p-8 mb-8"> <h2 className="text-2xl font-semibold text-gray-800 mb-6"> API情報取得 </h2> <div className="space-y-4"> <button onClick={fetchUserInfo} disabled={apiLoading} className="bg-green-600 hover:bg-green-700 disabled:bg-green-400 text-white font-medium py-3 px-6 rounded-lg transition duration-200 flex items-center space-x-2 mx-auto" > {apiLoading ? ( <> <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div> <span>取得中...</span> </> ) : ( <> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> </svg> <span>サーバーからユーザー情報を取得</span> </> )} </button> {apiError && ( <div className="bg-red-50 border border-red-200 rounded-lg p-4 text-left max-w-md mx-auto"> <h3 className="font-medium text-red-800 mb-2">エラー</h3> <p className="text-sm text-red-600">{apiError}</p> </div> )} {apiData && ( <div className={" rounded-lg p-4 text-left max-w-md mx-auto" + (apiData.isAuthenticated ? ' bg-green-50 border border-green-200' : ' bg-red-50 border border-red-200')} > <h3 className={"font-medium mb-2" + (apiData.isAuthenticated ? " text-green-800": " text-red-800")}>サーバー側ユーザー情報</h3> <div className={"space-y-1 text-sm " + (apiData.isAuthenticated ? " text-green-700": " text-red-700")}> <p><span className="font-medium">認証状態:</span> {apiData.isAuthenticated ? '認証済み' : '未認証'}</p> <p><span className="font-medium">ユーザーID:</span> {apiData.userId}</p> <p><span className="font-medium">名前:</span> {apiData.name}</p> <p><span className="font-medium">メール:</span> {apiData.email}</p> <p><span className="font-medium">プロバイダー:</span> {apiData.provider}</p> <p><span className="font-medium">ロール:</span> {apiData.roles.length > 0 ? apiData.roles.join(', ') : 'なし'}</p> </div> </div> )} </div> </div> <div className="text-center text-gray-500"> <p className="text-sm"> このアプリケーションはAzure Static Web Appsで認証を管理しています </p> </div> </div> </div> </div> ); } ロジック部分 ロジック部分を抽出しました。こちらは、ボタンを押した際に /api/user-info に対してGETリクエストを送信して問い合わせを行っています。APIリクエストの状態管理は useState の apiLoading でエラーに関しては useState の apiError で管理をしています。 interface ApiUserInfo { userId: string; name: string; email: string; provider: string; roles: string[]; isAuthenticated: boolean; } interface ApiError { error: string; message: string; } const [apiData, setApiData] = useState<ApiUserInfo | null>(null); const [apiError, setApiError] = useState<string | null>(null); const [apiLoading, setApiLoading] = useState(false); const fetchUserInfo = async () => { setApiLoading(true); setApiError(null); setApiData(null); try { const response = await fetch('/api/user-info', { method: 'GET', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiUserInfo | ApiError = await response.json(); if ('error' in data) { setApiError(data.message || data.error); } else { setApiData(data); } } catch (error) { console.error('API呼び出しエラー:', error); setApiError(error instanceof Error ? error.message : 'APIの呼び出しに失敗しました'); } finally { setApiLoading(false); } }; 表示部分 表示部分としてはコードとして膨大なので、ソース添付としては割愛します。処理としては単純です。 認証確認中は useAuth から提供される loading をフラグとしてローディング画面を表示しています。 認証後は、APIアクセス中はapiLoadingでボタンの非活性化とローディングの表示、エラーが発生した場合はエラーの表示を行っています。 protectedページ実装 こちらは src > app > protected > page.tsx ファイルを新規作成してください。まずはコピー&ペーストをしてください。 'use client'; import Link from 'next/link'; import { useAuth } from '@/hooks/useAuth'; import { useState } from 'react'; interface ProtectedData { userId: string; message: string; timestamp: string; userNumber: number; } interface ApiError { error: string; message: string; } export default function ProtectedPage() { const { user, loading, logout } = useAuth(); const [protectedData, setProtectedData] = useState<ProtectedData | null>(null); const [apiError, setApiError] = useState<string | null>(null); const [apiLoading, setApiLoading] = useState(false); const fetchProtectedData = async () => { setApiLoading(true); setApiError(null); setProtectedData(null); try { const response = await fetch('/api/protected-data', { method: 'GET', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) { if (response.status === 401) { throw new Error('認証が必要です'); } throw new Error(`HTTP error! status: ${response.status}`); } const data: ProtectedData | ApiError = await response.json(); if ('error' in data) { setApiError(data.message || data.error); } else { setProtectedData(data); } } catch (error) { console.error('保護されたAPI呼び出しエラー:', error); setApiError(error instanceof Error ? error.message : '保護されたデータの取得に失敗しました'); } finally { setApiLoading(false); } }; if (loading) { return ( <div className="min-h-screen flex items-center justify-center"> <div className="text-center"> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div> <p className="text-gray-600">ユーザー情報を読み込み中...</p> </div> </div> ); } return ( <div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-100"> <div className="container mx-auto px-4 py-16"> <div className="max-w-4xl mx-auto"> <div className="text-center mb-12"> <div className="flex items-center justify-center space-x-2 mb-4"> <div className="w-4 h-4 bg-green-500 rounded-full"></div> <span className="text-green-700 font-medium text-lg">保護されたエリア</span> </div> <h1 className="text-4xl font-bold text-gray-900 mb-4"> 🎉 認証成功! </h1> <p className="text-xl text-gray-600"> このページは認証済みユーザーのみがアクセスできます </p> </div> <div className="bg-white rounded-lg shadow-lg p-8 mb-8"> <h2 className="text-2xl font-semibold text-gray-800 mb-6 text-center"> あなたの情報 </h2> {user ? ( <div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg p-6 mb-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> {user.name && ( <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">名前</div> <div className="text-lg text-gray-900">{user.name}</div> </div> )} {user.email && ( <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">メールアドレス</div> <div className="text-lg text-gray-900">{user.email}</div> </div> )} {user.login && ( <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">GitHubユーザー名</div> <div className="text-lg text-gray-900">@{user.login}</div> </div> )} <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">アクセス日時</div> <div className="text-lg text-gray-900"> {new Date().toLocaleString('ja-JP')} </div> </div> </div> </div> ) : ( <div className="text-center py-8"> <p className="text-gray-500">ユーザー情報が見つかりません</p> </div> )} <div className="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-6"> <h3 className="text-lg font-semibold text-blue-900 mb-3"> 🔒 セキュリティ情報 </h3> <ul className="text-blue-800 space-y-2"> <li>• このページはAzure SWAレベルで保護されています</li> <li>• 未認証ユーザーは自動的にGitHubログインにリダイレクトされます</li> <li>• 認証情報はAzure Static Web Appsで安全に管理されています</li> </ul> </div> <div className="flex flex-col sm:flex-row gap-4 justify-center"> <Link href="/" className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition duration-200 text-center" > ホームに戻る </Link> <button onClick={logout} className="bg-red-500 hover:bg-red-600 text-white font-medium py-3 px-6 rounded-lg transition duration-200" > ログアウト </button> </div> </div> {/* 保護されたデータ取得セクション */} <div className="bg-white rounded-lg shadow-lg p-8 mb-8"> <h2 className="text-2xl font-semibold text-gray-800 mb-6 text-center"> 🛡 保護されたデータ </h2> <div className="space-y-6"> <div className="text-center"> <p className="text-gray-600 mb-4"> サーバー側で認証を確認し、あなた専用のデータを取得します </p> <button onClick={fetchProtectedData} disabled={apiLoading} className="bg-purple-600 hover:bg-purple-700 disabled:bg-purple-400 text-white font-medium py-3 px-6 rounded-lg transition duration-200 flex items-center space-x-2 mx-auto" > {apiLoading ? ( <> <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div> <span>データ取得中...</span> </> ) : ( <> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> </svg> <span>保護されたデータを取得</span> </> )} </button> </div> {apiError && ( <div className="bg-red-50 border border-red-200 rounded-lg p-6"> <div className="flex items-center space-x-2 mb-2"> <svg className="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> <h3 className="font-medium text-red-800">エラー</h3> </div> <p className="text-sm text-red-600">{apiError}</p> </div> )} {protectedData && ( <div className="bg-gradient-to-r from-purple-50 to-pink-50 border border-purple-200 rounded-lg p-6"> <div className="flex items-center space-x-2 mb-4"> <svg className="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> <h3 className="font-medium text-purple-800">取得成功 🎯</h3> </div> <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">ユーザーID</div> <div className="text-lg text-gray-900 font-mono">{protectedData.userId}</div> </div> <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">ユーザー番号</div> <div className="text-lg text-gray-900 font-bold "> #{protectedData.userNumber} </div> </div> <div className="bg-white rounded-lg p-4 md:col-span-2"> <div className="text-sm text-gray-500 font-medium">パーソナライズメッセージ</div> <div className="text-lg text-gray-900">{protectedData.message}</div> </div> <div className="bg-white rounded-lg p-4 md:col-span-2"> <div className="text-sm text-gray-500 font-medium">データ生成時刻</div> <div className="text-lg text-gray-900 font-mono"> {new Date(protectedData.timestamp).toLocaleString('ja-JP')} </div> </div> </div> <div className="mt-4 p-3 bg-purple-100 rounded-lg"> <p className="text-sm text-purple-700"> 💡 このデータはあなたのユーザーIDに基づいて動的に生成され、 サーバー側で認証を確認した後にのみ提供されます。 </p> </div> </div> )} </div> </div> <div className="text-center text-gray-500"> <p className="text-sm"> Azure Static Web Apps + GitHub認証で実現したセキュアなアプリケーション </p> </div> </div> </div> </div> ); } ロジック部分 ロジック部分を抽出しました。こちらは、ボタンを押した際に /api/protected-data に対してGETリクエストを送信して問い合わせを行っています。APIリクエストの状態管理は useState の apiLoading でエラーに関しては useState の apiError で管理をしています。 SWA側で401エラーが発生した場合は、アクセスできないように組みますが将来的なエラーハンドリングのために仮で実装しています。 interface ProtectedData { userId: string; message: string; timestamp: string; userNumber: number; } interface ApiError { error: string; message: string; } const [protectedData, setProtectedData] = useState<ProtectedData | null>(null); const [apiError, setApiError] = useState<string | null>(null); const [apiLoading, setApiLoading] = useState(false); const fetchProtectedData = async () => { setApiLoading(true); setApiError(null); setProtectedData(null); try { const response = await fetch('/api/protected-data', { method: 'GET', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) { if (response.status === 401) { throw new Error('認証が必要です'); } throw new Error(`HTTP error! status: ${response.status}`); } const data: ProtectedData | ApiError = await response.json(); if ('error' in data) { setApiError(data.message || data.error); } else { setProtectedData(data); } } catch (error) { console.error('保護されたAPI呼び出しエラー:', error); setApiError(error instanceof Error ? error.message : '保護されたデータの取得に失敗しました'); } finally { setApiLoading(false); } }; 表示部分 表示部分としてはコードとして膨大なので、ソース添付としては割愛します。処理としては単純です。 認証確認中は useAuth から提供される loading をフラグとしてローディング画面を表示しています。 認証後は、APIアクセス中はapiLoadingでボタンの非活性化とローディングの表示、エラーが発生した場合はエラーの表示を行っています。 ローカルでのフロントエンド動作確認 ローカルでフロントエンドのテストをします。APIとの接続を行っていないので、APIアクセスはすべて失敗するので画面上のテストのみになります。 npm run dev # http://localhost:3000 ローカル結合テスト(SWA CLI) ここでは、SWA CLIを使用してフロントエンドとAzure Functionsの結合テストを行っていきます。SWA CLIを使うことで、SWAに上げた時の挙動を認証部分を含めてエミュレーターで確認することができます。 SWA CLI設定 プロジェクトルートで、以下のコマンドを入力することで対話的に swa-cli.config.json を作成することが可能です。 swa init 設定項目は公式のこちら にまとまっています。若干設定が複雑な部分があるため、今回の環境に合わせた構成ファイルに対して解説します。 swaConfigLocation に関してのみ次の節で解説を行います。 フロントもバックもSWA CLIで起動する こちらの構成ファイルでは、 swa start でフロントもバックのリソースも立ち上がるようになります。利点としては、コマンド一つで立ち上げることができるので楽な点ですね。 フロントに関しては、devコマンドで起動されているためフロントのコードを変更することで自動的に更新(ホットリロード)が入ります。バックに関しては、更新を反映させるためには一度コマンドを落として再度実行する必要があります。 { "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", "configurations": { "swa-api-bicep-sample": { "appLocation": "frontend", "apiLocation": "api", "outputLocation": "out", "apiLanguage": "node", "appBuildCommand": "npm run build", "apiBuildCommand": "npm run build --if-present", "run": "npm run dev", "appDevserverUrl": "http://localhost:3000", "swaConfigLocation": "frontend/config/local" } } } フロントのみSWA CLIで起動してバックは起動済みのものを使用する こちらの構成ファイルでは、 swa start でフロントのリソースのみを立ち上げ、バックエンドのリソースは提供済みのものを使用します。こちらの利点としては、バックエンドのリソースのみを分離して立ち上げ直しができる点ですね。その代わり、実行には func start コマンドを別途実行してあげる必要があります。 { "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", "configurations": { "swa-api-bicep-sample": { "appLocation": "frontend", "apiLocation": "api", "outputLocation": "out", "apiLanguage": "node", "appBuildCommand": "npm run build", "apiBuildCommand": "npm run build --if-present", "run": "npm run dev", "appDevserverUrl": "http://localhost:3000", "apiDevserverUrl": "http://localhost:7071", "swaConfigLocation": "frontend/config/local" } } } SWA設定ファイル(staticwebapp.config.json) こちらのファイルは、認証ルートの定義やSWA側でサーバー側で発生したエラーをキャッチしてルーティングを制御します。先ほどの swa-cli.config.json で swaConfigLocation を設定していましたが、これは開発環境と本番環境で二つのファイルを構成する必要があるためです。開発環境では、認証プロバイダーの設定をSWA CLIのエミュレーターで代用します。本番環境では、SWAの環境変数から認証プロバイダーに必要な情報を読み取って認証プロバイダーを構成します。 本番環境用の構成ファイルを使用してSWA CLIでローカルで立ち上げると認証時に失敗します。 SWA 設定ファイルは大部分がフロントのルーティング定義であるため、 frontend > config とディレクトリを定義してその中にディレクトリ単位で local と prod と分割して同名ファイルを保存します。 ./frontend └── config ├── local │ └── staticwebapp.config.json └── prod └── staticwebapp.config.json 開発環境用 SWAに認証されるとユーザーには自動的に「authenticated」と「anoymous」というロールが割り振られます 。SWA Configでは、ロールによって表示することができるルート制御というのがあり、今回であれば /protected ルートでは authenticated ロールにアクセスを許可しています。それ以外のルートに関しては、200で許可しています。もし未認証の方が /protected にアクセスした場合はSWA側で401エラーが発生して、自動的に認証画面にリダイレクトするように responseOverrdes で設定しています。ここをエラー用のページに設定しておけば、自動でエラー画面を表示するなんてこともできます。 platform の設定はAzure Functionsの設定になります。今回であればnode系を使用していたため設定しています。 { "routes": [ { "route": "/protected/*", "allowedRoles": ["authenticated"] }, { "route": "/*", "statusCode": 200 } ], "responseOverrides": { "401": { "redirect": "/.auth/login/github", "statusCode": 302 } }, "platform": { "apiRuntime": "node:18" } } 本番環境 大枠は開発環境と同じですが、こちらでは auth でカスタム認証プロバイダーの設定がされています。 設定方法は公式のこちらで言及 があります。こちらはSWAの環境変数から値を読み取っているため、SWA側の設定として GITHUB_CLIENT_ID と GITHUB_CLIENT_SECRET を設定してあげる必要があります。こちらは、Azureのリソース作成の手順の際に合わせて設定します。 { "auth": { "identityProviders": { "github": { "registration": { "clientIdSettingName": "GITHUB_CLIENT_ID", "clientSecretSettingName": "GITHUB_CLIENT_SECRET" } } } }, "routes": [ { "route": "/protected/*", "allowedRoles": ["authenticated"] }, { "route": "/*", "statusCode": 200 } ], "responseOverrides": { "401": { "redirect": "/.auth/login/github", "statusCode": 302 } }, "platform": { "apiRuntime": "node:18" } } フロントエンド + API結合確認 それでは、接続してテストを開始しましょう。これまでの設定が完了していれば、ルートディレクトリで以下のコマンドでエミュレーターが起動します。もし起動しない方は、swa-cli.config.jsonの設定を再度確認してください。 http://localhost:7071 ready で1分以上固まっている方は、apiディレクトリで func start コマンドを実行してください。 # swa-cli.config.jsonでの設定による起動 swa start 動作確認テストの実施 さて、ここまででフロントエンドとAPIの実装が完了しましたね!いよいよ統合テストの時間です。今回は正常系の動作確認に集中して進めていきます。Azure SWAの素晴らしいところは、認証周りの面倒な処理を自動でやってくれることなので、エラーハンドリングよりも「ちゃんと期待通りに動いているか」を確認することが重要ですね。 実際のテストでは、未認証状態と認証状態の両方で動作を確認していきます。特に認証フローがスムーズに動作するかどうかが、このプロジェクトの肝になる部分です。 未認証状態での動作確認 まずは未認証状態から確認していきましょう。ブラウザでシークレットモードを開くか、既存のセッションをクリアしてからテストを開始します。 トップページ( / )での確認項目 : ページにアクセスすると「未認証状態」と表示される 「サーバーからユーザー情報を取得」ボタンをクリックしても、ユーザー情報は取得できない(nullやundefinedが返される) 「GitHubでログイン」ボタンが表示され、クリックすると認証画面に遷移する 保護ページ( /protected )での確認項目 : URLを直接入力してアクセスを試みると、自動的に認証画面に遷移する この時点では、Azure SWAが未認証ユーザーを自動的に認証フローに誘導してくれるので、エラー画面が表示されることはありません。これがSWAの便利なところですね! 認証状態での動作確認 続いて、GitHub認証を完了した状態での動作を確認します。認証が成功すると、トップページにリダイレクトされるはずです。認証画面は以下のような画面になります。Providerに関しては、埋められた状態で開かれるはずです。UsernameはGitHub認証の場合はユーザー名になります。(表示名ではありません @以下です) トップページ( / )での確認項目 : ページ上部に「認証状態」と表示される 「サーバーからユーザー情報を取得」ボタンをクリックすると、GitHubから取得したユーザー情報が表示される 「保護されたページ」ボタンと「ログアウト」ボタンが表示される 保護ページ( /protected )での確認項目 : ページに正常にアクセスできる 「保護されたデータを取得」ボタンをクリックすると、サーバーサイドのAPIからダミーデータが取得される 認証が正しく動作していれば、Azure Functionsに送信される x-ms-client-principal ヘッダーにユーザー情報が含まれているので、APIから適切なレスポンスが返ってきます。 テスト実施時のポイント テストを実施する際に気をつけておきたいポイントをいくつか挙げておきますね。 Azure SWAの自動処理について : 未認証時の保護リソースへのアクセスや、 /api/protected-data への直接アクセスは、SWAの設定により自動的に認証画面にリダイレクトされます。そのため、401エラーが画面に表示されることはなく、ユーザーは常にスムーズな認証フローを体験できます。 セッション管理の確認 : ページをリロードしても認証状態が維持されることを確認してください。これはAzure SWAが内部的にセッション管理を行っているためです。 API呼び出しの動作 : 認証状態でのAPI呼び出しでは、Azure SWAが自動的に認証ヘッダーを付与してくれるので、フロントエンド側で特別な処理は不要です。これも開発者としてはとても楽な部分ですね。 今回のテストはシンプルですが、実際のプロダクションで必要な基本的な動作はすべてカバーできています。正常系がしっかり動作することを確認できれば、Azure SWAとNext.js、Azure Functionsの統合は成功です! インフラ構築(Bicep) これまでの章でローカルの開発は完了しました。ここからは、実際にAzure環境へのデプロイをするための準備を行います。具体的にはBicepとパラメーターファイルを作成してAzureのリソースの定義を行い、作成したファイルを使用してGitHubにデプロイに必要な変数の定義とAzureリソースを作成するbashファイルを用意します。 注意点:ここら先はAzure Static Web Appsに対してリソースの作成を行います。今回はStandardプランでリソースを作成するため、もし不安な方はFree版でのデプロイで練習をしておきましょう。別の環境を使って詳細にデプロイするためのガイドを書いているので、 こちらのブログ を参照して練習してみてください。 事前準備 Bicepファイルとパラメーターファイルの説明などはこちらで詳しく解説 しています。ぜひ一度お読みください。 こちらの章で目指す内容は以下になります。 Azure CLI:Azure Static Web AppsのリソースをStandardプランで作成する 手動:GitHub認証プロバイダーの設定完了 GitHub CLI:GitHub Enviromentにデプロイに必要な情報が設定されている 作成に当たって目指すディレクトリ構成は以下になります。 ./infra ├── bicep │ ├── main.bicep │ └── parameters.json └── scripts └── deploy.sh GitHubの設定 まずは今まで設定したリソースをGitHub上にpushしておきましょう。GitHub CLIで environment を設定するためには事前に environment を作成しておく必要があります。 production という名前で environment を作成してください。設定は 公式のドキュメントを参考に設定 してください。 値の登録自体は、GitHub CLI経由で実行することができます。ですが、Enviromentの作成はGitHub API経由でしか行うことができません。ちょっと設定がややこしいので、ここは手動で作成しています。 ここで取得するべき情報としてはリポジトリのURLになります。 Bicepテンプレート作成 main.bicep 詳細な設定に関しては公式リファレンスを参照してください。 Microsoft.Web/staticSites Microsoft.Web/staticSites/config @description('Static Web App名') param staticWebAppName string @description('デプロイするリージョン') param location string = resourceGroup().location @description('GitHubリポジトリのURL') param repositoryUrl string @description('デプロイ対象のブランチ') param branch string = 'main' @description('フロントエンドのソースフォルダパス') param appLocation string = 'frontend' @description('APIのソースフォルダパス') param apiLocation string = 'api' @description('ビルド出力フォルダパス') param outputLocation string = 'out' @description('GitHub OAuth App のClient ID') param githubClientId string @description('GitHub OAuth App のClient Secret') @secure() param githubClientSecret string // Static Web App リソース resource staticWebApp 'Microsoft.Web/staticSites@2024-11-01' = { name: staticWebAppName location: location sku: { name: 'Standard' tier: 'Standard' } properties: { repositoryUrl: repositoryUrl branch: branch buildProperties: { appLocation: appLocation apiLocation: apiLocation outputLocation: outputLocation skipGithubActionWorkflowGeneration: false } stagingEnvironmentPolicy: 'Enabled' } } // アプリケーション設定(GitHub OAuth用の環境変数) resource staticWebAppSettings 'Microsoft.Web/staticSites/config@2022-03-01' = { name: 'appsettings' parent: staticWebApp properties: { GITHUB_CLIENT_ID: githubClientId GITHUB_CLIENT_SECRET: githubClientSecret } } // アウトプット @description('Static Web Appsのエンドポイント') output appBaseUrl string = 'https://${staticWebApp.properties.defaultHostname}' @description('Static Web Appsの名前') output resourceName string = staticWebAppName @description('GitHubリポジトリのURL') output repositoryUrl string = repositoryUrl @description('フロントエンドのソースフォルダパス') output appLocation string = appLocation @description('APIのソースフォルダパス') output apiLocation string = apiLocation @description('ビルド出力フォルダパス') output outputLocation string = outputLocation 課金に重要なパラメーターとしては sku になります。ここをFreeにするかStandardにするかで課金が走るか決定します。(個人的には検証目的で建てたらすぐ消しましょう) staticWebAppSettings に関しては、SWAでカスタム認証プロバイダー(GitHub)を構成するのに必要な値を環境変数として設定しています。機密情報であるため、パラメーターファイルには記述せずに環境変数として定義して、デプロイ時のみ呼び出して埋め込むという方法で管理します。 パラメーターファイルで定義している内容をそのままアウトプットしている値が複数ありますが、こちらは後続のbashファイルでGitHub CLIを通してEnvriomentにValiableとして登録するために出力しています。 パラメータファイル設定 parameters.json { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "staticWebAppName": { "value": "swa-api-deploy-bicep" }, "location": { "value": "East Asia" }, "repositoryUrl": { "value": "https://github.com/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxx" }, "branch": { "value": "main" }, "appLocation": { "value": "deploy/frontend" }, "apiLocation": { "value": "api" }, "outputLocation": { "value": "." } } } appLocation / apiLocation / outputLocation に関しては後続で設定するGitHub Actionsと連携した値になります。 repositoryUrl に関しては自分のリポジトリの値に変更してください。 デプロイ用bashスクリプト deploy.sh Bicepファイルとパラメーターファイルが完成したのでデプロイする準備が整いました。ですが、このままではAzure CLIのコマンドとGitHub CLIコマンドを別々にたたく必要があります。それを解消するためにデプロイの流れをbashファイルにまとめます。 まずは、ルートディレクトリに .env ファイルを作成してください。こちらのファイルは .git と同じディレクトリ(リポジトリルート)に配置するようにしてください。 GITHUB_CLIENT_ID=xxxxxxxxxxxxxxxxx GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxx GITHUB_ENVIRONMENT="production" RESOURCE_GROUP_NAME=swa-bicep-test GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET に関しては、まだ設定していないのでマスクしたままで大丈夫です。こちらの設定には、SWAをデプロイしてURLを確定させる必要があります。 これで.envが立派な機密情報になりました。.gitignoreに設定して間違ってもpushしないように気を付けましょう。 #!/bin/bash # エラー時に停止 set -e log_info() { echo -e "\033[1;34m$1\033[0m"; } log_success() { echo -e "\033[1;32m$1\033[0m"; } log_warning() { echo -e "\033[1;33m$1\033[0m"; } log_error() { echo -e "\033[1;31m$1\033[0m"; } DEPLOYMENT_NAME="swa-deployment-$(date +%Y%m%d-%H%M%S)" CONFIG_FILE="$(git rev-parse --show-toplevel)/.env" if [ -f "$CONFIG_FILE" ]; then echo "📁 設定ファイル読み込み: $CONFIG_FILE" source "$CONFIG_FILE" else echo "⚠ 設定ファイルが見つかりません: $CONFIG_FILE" echo " .env.example をコピーして設定してください" exit 1 fi # 必要な環境変数の確認(機密情報のみ) required_vars=("GITHUB_CLIENT_ID" "GITHUB_CLIENT_SECRET" "RESOURCE_GROUP_NAME") for var in "${required_vars[@]}"; do if [ -z "${!var}" ]; then echo "エラー: 環境変数 $var が設定されていません" exit 1 fi done # GitHub CLIがインストールされているかチェック if ! command -v gh &> /dev/null; then log_error "GitHub CLI (gh) がインストールされていません" log_error "インストール方法: https://cli.github.com/" exit 1 fi # GitHub CLIの認証チェック if ! gh auth status &> /dev/null; then log_error "GitHub CLIが認証されていません" log_error "認証方法: gh auth login" exit 1 fi # リポジトリのルートディレクトリかチェック if ! git rev-parse --is-inside-work-tree &> /dev/null; then log_error "Gitリポジトリ内で実行してください" exit 1 fi # 現在のリポジトリ情報を取得(デバッグ用) CURRENT_REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "unknown") log_info "📍 対象リポジトリ: $CURRENT_REPO" # リソースグループ作成(存在しない場合) log_info "" log_info "📦 ローカル開発用リソースグループ確認・作成..." if ! az group show --name $RESOURCE_GROUP_NAME &> /dev/null; then log_info "🔧 リソースグループ作成中: $RESOURCE_GROUP_NAME" az group create --name $RESOURCE_GROUP_NAME --location $LOCATION log_success "✅ リソースグループ作成完了" else log_success "✅ リソースグループ既存: $RESOURCE_GROUP_NAME" fi echo "Azure Static Web Appをデプロイ中..." az deployment group validate \ --resource-group "$RESOURCE_GROUP_NAME" \ --template-file infra/bicep/main.bicep \ --parameters @infra/bicep/parameters.json \ --parameters githubClientId="$GITHUB_CLIENT_ID" \ --parameters githubClientSecret="$GITHUB_CLIENT_SECRET" # Bicepテンプレートでデプロイ(機密情報のみ環境変数から渡す) az deployment group create \ --resource-group "$RESOURCE_GROUP_NAME" \ --template-file infra/bicep/main.bicep \ --parameters @infra/bicep/parameters.json \ --parameters githubClientId="$GITHUB_CLIENT_ID" \ --parameters githubClientSecret="$GITHUB_CLIENT_SECRET" \ --name ${DEPLOYMENT_NAME} # 全outputを一度に取得 OUTPUTS=$(az deployment group show \ --resource-group "$RESOURCE_GROUP_NAME" \ --name "$DEPLOYMENT_NAME" \ --query "properties.outputs" -o json) echo "✅ デプロイ結果を取得しました" # 各値を抽出 APP_BASE_URL=$(echo "$OUTPUTS" | jq -r '.appBaseUrl.value') STATIC_WEB_APP_NAME=$(echo "$OUTPUTS" | jq -r '.resourceName.value') REPOSITORY_URL=$(echo "$OUTPUTS" | jq -r '.repositoryUrl.value') APP_LOCATION=$(echo "$OUTPUTS" | jq -r '.appLocation.value') API_LOCATION=$(echo "$OUTPUTS" | jq -r '.apiLocation.value') OUTPUT_LOCATION=$(echo "$OUTPUTS" | jq -r '.outputLocation.value') # 結果を表示 log_success "" log_success "Azure Static Web Appのデプロイが完了しました!" log_success "アプリのベースURL: $APP_BASE_URL" log_success "リソース名: $STATIC_WEB_APP_NAME" log_success "リポジトリURL: $REPOSITORY_URL" log_success "アプリの場所: $APP_LOCATION" log_success "APIの場所: $API_LOCATION" log_success "出力の場所: $OUTPUT_LOCATION" # === 新機能: デプロイトークンの取得とGitHub Environment設定 === log_info "" log_info "🔑 Azure Static Web Appsのデプロイトークンを取得中..." # デプロイトークンを取得 DEPLOY_TOKEN=$(az staticwebapp secrets list \ --name "$STATIC_WEB_APP_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --query "properties.apiKey" -o tsv) if [ -z "$DEPLOY_TOKEN" ]; then log_error "デプロイトークンの取得に失敗しました" exit 1 fi log_success "✅ デプロイトークンを取得しました" # GitHub環境の作成と設定 log_info "" log_info "🌍 GitHub環境 '$GITHUB_ENVIRONMENT' を設定中..." # 注意: GitHub CLIは現在のGitリポジトリを自動認識します # このスクリプトはGitリポジトリ内で実行する必要があります # Environment変数の設定 log_info "" log_info "📝 Environment変数を設定中..." # app_location if gh variable set APP_LOCATION --body "$APP_LOCATION" --env "$GITHUB_ENVIRONMENT" 2>/dev/null; then log_success "✅ 変数 'app_location' を設定: $APP_LOCATION" else log_error "❌ 変数 'app_location' の設定に失敗しました" fi # api_location if gh variable set API_LOCATION --body "$API_LOCATION" --env "$GITHUB_ENVIRONMENT" 2>/dev/null; then log_success "✅ 変数 'api_location' を設定: $API_LOCATION" else log_error "❌ 変数 'api_location' の設定に失敗しました" fi # output_location if gh variable set OUTPUT_LOCATION --body "$OUTPUT_LOCATION" --env "$GITHUB_ENVIRONMENT" 2>/dev/null; then log_success "✅ 変数 'output_location' を設定: $OUTPUT_LOCATION" else log_error "❌ 変数 'output_location' の設定に失敗しました" fi # デプロイトークンをシークレットに設定 log_info "" log_info "🔐 デプロイトークンをシークレットに設定中..." # 注意: gh secret setは現在のGitリポジトリの指定した環境にシークレットを設定します if gh secret set AZURE_STATIC_WEB_APPS_API_TOKEN --body "$DEPLOY_TOKEN" --env "$GITHUB_ENVIRONMENT" 2>/dev/null; then log_success "✅ シークレット 'deploy_token' を設定しました" else log_error "❌ シークレット 'deploy_token' の設定に失敗しました" log_warning "⚠ 手動で設定してください:" log_warning " リポジトリ設定 > Environments > $GITHUB_ENVIRONMENT > Secrets" log_warning " シークレット名: deploy_token" log_warning " 値: [デプロイトークン]" fi # 最終結果サマリー log_success "" log_success "🎉 すべての設定が完了しました!" log_success "" log_success "📋 設定内容サマリー:" log_success " - Azure Static Web App: $STATIC_WEB_APP_NAME" log_success " - アプリURL: $APP_BASE_URL" log_success " - GitHub環境: $GITHUB_ENVIRONMENT" log_success " - 設定された変数:" log_success " • app_location: $APP_LOCATION" log_success " • api_location: $API_LOCATION" log_success " • output_location: $OUTPUT_LOCATION" log_success " - 設定されたシークレット:" log_success " • deploy_token: [設定済み]" log_success "" log_success "🚀 GitHub Actionsでのデプロイが可能になりました!" 長いbashファイルですが、以下のステップを順次実行しています。 環境変数の取得 各種環境の確認 Bicepを使用したデプロイ検証・実行 Azureリソースから情報取得 GitHub CLIを経由したSecret・Valiableの設定 リザルト表示 各コマンドに関しての詳細な説明はこちら で行っています。権限を振って実行してみてください。 chmod +x ./infra/scripts/deploy.sh ./infra/scripts/deploy.sh # リザルトで以下が出たら成功!! #📋 設定内容サマリー: # - Azure Static Web App: swa-api-deploy-bicep # - アプリURL: https://blue-bay-01b32fb00.2.azurestaticapps.net # - GitHub環境: production # - 設定された変数: # • app_location: deploy/frontend # • api_location: api # • output_location: . # - 設定されたシークレット: # • deploy_token: [設定済み] 1分ぐらいで実行されると思います。リザルトが表示されたら、「アプリURL」にアクセスしてみてください。Welcomeページが表示されれば成功です。 環境作成 無事デプロイが確認出来たら、次は認証プロバイダーの設定を行いましょう。 GitHub > Settings > Developer Settingsにアクセス してください。 リポジトリのSettingsではなくUser Settingsのほうにアクセスしてね! OAuth Appsの作成を行いましょう。 設定項目としては、 設定項目 値 Application name 自由に決めてください Homepage URL アプリURL Authorization callback URL アプリURL/.auth/login/github/callback Enable Device Flow True 設定をするとClient IDとClient Secretが表示されます。そちらの値を .env ファイルに入れて再度 deploy.sh を起動させましょう。 ./infra/scripts/deploy.sh # リザルトで以下が出たら成功!! #📋 設定内容サマリー: # - Azure Static Web App: swa-api-deploy-bicep # - アプリURL: https://blue-bay-01b32fb00.2.azurestaticapps.net # - GitHub環境: production # - 設定された変数: # • app_location: deploy/frontend # • api_location: api # • output_location: . # - 設定されたシークレット: # • deploy_token: [設定済み] これで、SWAの環境変数に GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET が設定されました。もし不安な方はAzure Portalから確認しましょう。 CI/CD構築(GitHub Actions) ここでは、GitHub Actionsの設定を行っています。前提条件として、GitHubリポジトリのEnviroment(production)に値が設定済みである必要があります。 Name 説明 AZURE_STATIC_WEB_APPS_API_TOKEN SWAのデプロイトークン API_LOCATION APIのディレクトリ APP_LOCATION アプリのディレクトリ OUTPUT_LOCATION ビルドファイルの位置 今回作成するファイルのディレクトリ構成は以下になります。 ./.github └── workflows └── deploy.yaml ワークフロー設計・アーキテクチャ ワークフローでは、フロントビルドとデプロイを分離しています。フロントのビルドと並行してAPIのテストを実行するように設計しています。 build-frontend : フロントエンドの並列ビルド test-api : APIの並列ビルド deploy-swa : アーティファクトを使用したデプロイ 視覚化した図を以下に示します。 全体ワークフロー こちらのファイルをコピー&ペーストしてGitHub >Actionsのタブから手動実行しましょう。設定が完璧であれば、無事デプロイされるかと思います。 name: Build and Deploy to Azure Static Web Apps on: workflow_dispatch: jobs: # フロントエンドビルドジョブ build-frontend: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup Node.js for Frontend uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "frontend/package-lock.json" - name: Install Frontend Dependencies working-directory: ./frontend run: npm ci - name: Build Frontend working-directory: ./frontend run: npm run build - name: Upload Frontend Build Artifacts uses: actions/upload-artifact@v4 with: name: frontend-build path: | frontend/out/ retention-days: 1 # APIビルドジョブ test-api: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup Node.js for API uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "api/package-lock.json" - name: Install API Dependencies working-directory: ./api run: npm install - name: Build API working-directory: ./api run: npm run build - name: Run API Tests working-directory: ./api run: npm run test # Azure Static Web Appsデプロイジョブ deploy-swa: needs: [build-frontend, test-api] runs-on: ubuntu-latest environment: production env: APP_LOCATION: ${{ vars.APP_LOCATION || 'deploy/frontend' }} OUTPUT_LOCATION: ${{ vars.OUTPUT_LOCATION || '.' }} API_LOCATION: ${{ vars.API_LOCATION || 'api' }} steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Download Frontend Artifacts uses: actions/download-artifact@v4 with: name: frontend-build path: ./deploy/frontend - name: Copy Static Web App Configuration run: | cp frontend/config/prod/staticwebapp.config.json deploy/frontend/ - name: check working-directory: ./deploy/frontend/ run: | ls -la cat staticwebapp.config.json - name: Deploy to Azure Static Web Apps uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }} action: "upload" app_location: ${{ env.APP_LOCATION }} api_location: ${{ env.API_LOCATION }} output_location: "${{ env.OUTPUT_LOCATION }}" skip_app_build: true skip_api_build: false フロントエンドビルドジョブ こちらでは、フロントエンドのビルドを行い結果をArtifactとして保存しています。 actions/setup-node@v4 を利用してnode_modulesをキャッシュしています。 build-frontend: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup Node.js for Frontend uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "frontend/package-lock.json" - name: Install Frontend Dependencies working-directory: ./frontend run: npm ci - name: Build Frontend working-directory: ./frontend run: npm run build - name: Upload Frontend Build Artifacts uses: actions/upload-artifact@v4 with: name: frontend-build path: | frontend/out/ retention-days: 1 APIテストジョブ こちらでは、バックエンドのビルドを行いテストを実行しています。今回はテストが失敗したら通知を行う等の対応を行っていませんが、本格運用ではここにテスト関連の処理をまとめて切り出すことができます。 actions/setup-node@v4 を利用してnode_modulesをキャッシュしています。 # APIビルドジョブ test-api: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup Node.js for API uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "api/package-lock.json" - name: Install API Dependencies working-directory: ./api run: npm install - name: Build API working-directory: ./api run: npm run build - name: Run API Tests working-directory: ./api run: npm run test SWAデプロイジョブ実装 こちらでは、以下の手順を順次実行しています。 「フロントエンドビルドジョブ」で作成した静的ファイルを復元 内部に本番環境用ルート定義を埋め込み Azure/static-web-apps-deploy@v1 を使用してデプロイ フロントエンドはビルド済みファイルをデプロイのみ任せて実行 バックエンドはビルドとデプロイを任せて実行 # Azure Static Web Appsデプロイジョブ deploy-swa: needs: [build-frontend, test-api] runs-on: ubuntu-latest environment: production env: APP_LOCATION: ${{ vars.APP_LOCATION || 'deploy/frontend' }} OUTPUT_LOCATION: ${{ vars.OUTPUT_LOCATION || '.' }} API_LOCATION: ${{ vars.API_LOCATION || 'api' }} steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Download Frontend Artifacts uses: actions/download-artifact@v4 with: name: frontend-build path: ./deploy/frontend - name: Copy Static Web App Configuration run: | cp frontend/config/prod/staticwebapp.config.json deploy/frontend/ - name: check working-directory: ./deploy/frontend/ run: | ls -la cat staticwebapp.config.json - name: Deploy to Azure Static Web Apps uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }} action: "upload" app_location: ${{ env.APP_LOCATION }} api_location: ${{ env.API_LOCATION }} output_location: "${{ env.OUTPUT_LOCATION }}" skip_app_build: true skip_api_build: false Copy Static Web App Configuration では、本番環境用の staticwebapp.config.json を復元したフロントのビルド済みディレクトリにコピーしています。 Azure/static-web-apps-deploy@v1 では、 skip_app_build と skip_api_build でアクション内のビルドプロセスを制御しています。 そもそもこのアクションは、アプリの構成を自動で読み取りビルドとデプロイを行っています。フロントエンドでルーティングファイルの制御を行うためにフロントエンドはビルドをスキップしてデプロイだけで使用しています。 GitHub Actions効率化紹介 今回のワークフローでは、ビルド時間の短縮とリソース効率化を重視した設計を行いました。特に並列処理とキャッシュ戦略により、従来の逐次処理と比較して大幅な時間短縮を実現できます。 独立したNodeキャッシュ戦略 各ジョブで異なる cache-dependency-path を指定することで、フロントエンドとAPIで独立したキャッシュを管理できます。これにより、一方の依存関係が変更されても他方のキャッシュが無効化されることを防げます。 # フロントエンドジョブでのキャッシュ設定 - name: Setup Node.js for Frontend uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "frontend/package-lock.json" # APIジョブでのキャッシュ設定 - name: Setup Node.js for API uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "api/package-lock.json" cache-dependency-path を明示的に指定することで、GitHub Actionsはそのファイルのハッシュをキャッシュキーとして使用します。フロントエンドとAPIで異なるpackage-lock.jsonを参照することで、以下のメリットがあります: フロントエンドの依存関係のみ変更時:APIのキャッシュはそのまま利用 APIの依存関係のみ変更時:フロントエンドのキャッシュはそのまま利用 両方変更時:それぞれ個別にキャッシュを再構築 フロントエンドジョブでは npm ci を使用しています。これはCI環境向けの最適化されたコマンドで、package-lock.jsonを厳密に遵守し、高速インストールを実現します。 アーティファクト活用による並列処理最適化 アーティファクトを使用することで、ビルドジョブとデプロイジョブを完全に分離できます。これにより、デプロイ時にビルドを再実行する必要がなくなり、処理時間を大幅に短縮できます。 # ビルドジョブでのアーティファクト保存 - name: Upload Frontend Build Artifacts uses: actions/upload-artifact@v4 with: name: frontend-build path: | frontend/out/ retention-days: 1 # デプロイジョブでのアーティファクト復元 - name: Download Frontend Artifacts uses: actions/download-artifact@v4 with: name: frontend-build path: ./deploy/frontend 今回の構成では、 build-frontend と test-api が並列で実行されます。これにより、従来の逐次処理と比較して大幅な時間短縮が期待できます。 将来的なテストへの拡張性 段階的テスト導入への対応 現在のAPIテストジョブは、将来的なテスト拡張を見据えた設計になっています。 - name: Build API working-directory: ./api run: npm run build - name: Run API Tests working-directory: ./api run: npm run test テスト失敗時の処理拡張 本格運用時には、テスト失敗時の通知機能を追加できます。以下のような拡張が可能です: - name: Run API Tests working-directory: ./api run: npm run test continue-on-error: false # テスト失敗時にワークフローを停止 # 将来的な拡張例 - name: Notify Test Failure if: failure() uses: actions/github-script@v6 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: 'APIテストが失敗しました。詳細はワークフローログを確認してください。' }) 並列テスト実行の利点 フロントエンドビルドとAPIテストを並列実行することで、以下のメリットがあります: 時間効率 : 最も時間のかかる処理に全体時間が依存 早期発見 : APIテストの失敗を早期に検出 リソース効率 : GitHub Actionsの同時実行枠を有効活用 シークレット・環境変数の効率的管理 GitHub Actionsでは、Secretsと環境変数を適切に分離して管理することが重要です。 environment: production env: APP_LOCATION: ${{ vars.APP_LOCATION || 'deploy/frontend' }} OUTPUT_LOCATION: ${{ vars.OUTPUT_LOCATION || '.' }} API_LOCATION: ${{ vars.API_LOCATION || 'api' }} Secrets(機密情報)には AZURE_STATIC_WEB_APPS_API_TOKEN などのデプロイトークンを、Variables(環境固有設定)には APP_LOCATION などのディレクトリ設定を使い分けます。 本番環境での動作確認 お疲れさまでした!ここまでで無事にAzure SWAのデプロイが完了しましたね。でも、ここからが本当の勝負です。実際に本番環境で全ての機能が期待通りに動作するかを確認していきましょう。 私も最初の頃は「ローカルで動いてるから大丈夫だろう」と思っていましたが、本番環境では思わぬ設定の違いでハマることがよくありました。特に認証周りは環境による差が出やすい部分なので、しっかりと検証していきますね。 もし何かうまく動作しない部分があった場合は、まずはブラウザの開発者ツールでエラーがないか確認して、必要に応じてAzure Portalでのログ確認も行ってみてください。認証周りは環境設定に依存する部分が多いので、OAuth Appの設定やSWAの環境変数も再度チェックしてみると良いでしょう。 未認証状態での動作確認 シークレットモードやプライベートブラウジングで本番URLにアクセスしてみてください。まずはトップページ( / )から確認します。 期待される動作: ページ上部に「未認証状態」と表示される 「GitHubでログイン」ボタンが表示される 「サーバーからユーザー情報を取得」ボタンをクリックしても、ユーザー情報は取得できない( isAuthenticated: false が返される) 次に、保護されたページへの直接アクセスを試してみます。URLバーに /protected を入力してアクセスしてください。 期待される動作: 自動的にGitHub認証画面にリダイレクトされる エラー画面が表示されることはない これがAzure SWAの素晴らしいところですね。設定ファイル( staticwebapp.config.json )で定義したルート保護が自動的に働いて、未認証ユーザーを認証フローに誘導してくれます。 GitHub認証の実行 「GitHubでログイン」ボタンをクリックして、実際の認証フローを確認します。 認証フローの流れ: ボタンクリックで /.auth/login/github にリダイレクト GitHub認証画面が表示される GitHubでの認証完了後、元のページにリダイレクト GitHub認証画面では、作成したOAuth Appの情報が表示されます。App nameやAuthorization callback URLが正しく設定されていることを確認してください。 認証完了後は、トップページに戻ってきて以下のような変化が確認できるはずです: ページ上部に「認証済み」と表示される ユーザー情報(GitHubのユーザー名など)が表示される 「保護されたページへ」「ログアウト」ボタンが表示される セッション管理の確認 認証状態でページをリロードしても、認証状態が維持されることを確認してください。これはAzure SWAが内部的にセッション管理を行っているためです。 また、別タブで同じサイトを開いても、認証状態が共有されることも確認してみましょう。 API動作テスト 認証が正しく動作することが確認できたら、次はAPIの動作を確認していきます。 user-infoエンドポイントのテスト 認証済み状態で「サーバーからユーザー情報を取得」ボタンをクリックしてください。 期待される結果: { "userId": "github|あなたのGitHubユーザーID", "name": "あなたのGitHubユーザー名", "email": "あなたのGitHubユーザー名", "provider": "github", "roles": ["authenticated"], "isAuthenticated": true } これで、Azure SWAからAzure Functionsに認証ヘッダー( x-ms-client-principal )が正しく送信されて、API側で適切にデコードされていることが確認できます。 ブラウザの開発者ツールのネットワークタブでも確認してみてください。 /api/user-info へのリクエストが200で成功していて、上記のようなレスポンスが返っていることが確認できるはずです。 未認証状態でのuser-infoテスト シークレットモードで未認証状態でも同じボタンをクリックしてみてください。 期待される結果: { "userId": null, "name": null, "email": null, "provider": null, "roles": [], "isAuthenticated": false } このAPIは認証状態を問わずアクセス可能な設計になっているので、未認証でもエラーにならずに適切なレスポンスが返ることを確認できます。 protected-dataエンドポイントのテスト 保護されたページ( /protected )にアクセスして、「保護されたデータを取得」ボタンをクリックしてください。 期待される結果: { "userId": "github|あなたのGitHubユーザーID", "message": "こんにちは、ユーザーgithub|あなたのGitHubユーザーIDさん!", "timestamp": "2024-12-15T15:45:30.000Z", "userNumber": 数値 } userNumber は、ユーザーIDをベースにした簡単なシード値から生成される0-999の範囲の数値です。同じユーザーなら常に同じ値が返されることも確認してみてください。 未認証でのprotected-dataアクセステスト これは少し技術的なテストですが、直接APIエンドポイントにアクセスしてみましょう。 シークレットモードで https://あなたのサイトURL/api/protected-data に直接アクセスしてください。 期待される動作: 401エラーが返される または、Azure SWAのルート保護により認証画面にリダイレクトされる 実際には、SWAの設定によってはAPI直接アクセスも認証フローに誘導される可能性があります。どちらの動作でも、未認証ユーザーがデータにアクセスできないことが重要です。 セキュリティチェック 最後に、セキュリティ面での動作を確認していきます。 ルート保護の確認 未認証状態で以下のURLに直接アクセスしてみてください: /protected /protected/任意のパス どちらも認証画面にリダイレクトされることを確認してください。これは staticwebapp.config.json で設定した以下のルールが動作しているためです: { "route": "/protected/*", "allowedRoles": ["authenticated"] } 認証情報の適切な伝達 開発者ツールのネットワークタブで、認証済み状態でのAPI呼び出しを確認してください。 重要なポイント: API呼び出し時に、フロントエンド側で特別な認証ヘッダーを設定していない Azure SWAが自動的に x-ms-client-principal ヘッダーを付与している このヘッダーは開発者ツールでは見えないが、サーバー側では受信できている これがAzure SWAの大きなメリットの一つですね。フロントエンド側で複雑な認証処理を書く必要がなく、SWAが自動的に認証情報をバックエンドに伝達してくれます。 セッション継続性の確認 認証済み状態で以下を試してみてください: ページのリロード 別タブでの同サイトアクセス ブラウザを閉じて再度開く(セッションストレージのテスト) 通常のWebアプリケーションと同様に、ブラウザを閉じるまでは認証状態が維持されることを確認してください。 ログアウト機能の確認 「ログアウト」ボタンをクリックして、正常にログアウトできることを確認してください。 期待される動作: /.auth/logout にリダイレクト 認証状態がクリアされる トップページに戻り、未認証状態の表示になる お疲れさまでした!これで本番環境での動作確認は完了です。すべての機能が期待通りに動作していれば、Azure SWA + Next.js + Azure Functionsの認証統合システムが正常に稼働していることが確認できました。 リソースのクリーナップ 今回はAzure SWAをStandardプランで使用しています。こちらは動かしていれば、課金が走るようになっています。検証が確認できたら、心苦しいですがクリーナップしましょう。 Azure Portalからの削除でもよいのですが、せっかくなのでクリーナップ用のbashスクリプトを作成しました。bashスクリプト化しておくことのメリットはコマンド一つで環境の作成・削除ができる点です。GitHubのOAuth Appsの設定とEnvironmentの作成は手動ですが、完全自動化ではないですけどね… クリーナップスクリプト #!/bin/bash # エラー時に停止 set -e log_info() { echo -e "\033[1;34m$1\033[0m"; } log_success() { echo -e "\033[1;32m$1\033[0m"; } log_warning() { echo -e "\033[1;33m$1\033[0m"; } log_error() { echo -e "\033[1;31m$1\033[0m"; } CONFIG_FILE="$(git rev-parse --show-toplevel)/.env" 2>/dev/null || CONFIG_FILE=".env" if [ -f "$CONFIG_FILE" ]; then log_info "📁 設定ファイル読み込み: $CONFIG_FILE" source "$CONFIG_FILE" else log_error "⚠ 設定ファイルが見つかりません: $CONFIG_FILE" log_error " .env.example をコピーして設定してください" exit 1 fi # 必要な環境変数の確認 required_vars=("RESOURCE_GROUP_NAME" "STATIC_WEB_APP_NAME") for var in "${required_vars[@]}"; do if [ -z "${!var}" ]; then log_error "エラー: 環境変数 $var が設定されていません" exit 1 fi done log_info "🎯 削除対象: $STATIC_WEB_APP_NAME (リソースグループ: $RESOURCE_GROUP_NAME)" # Azure CLIがインストールされているかチェック if ! command -v az &> /dev/null; then log_error "Azure CLI (az) がインストールされていません" log_error "インストール方法: https://docs.microsoft.com/cli/azure/install-azure-cli" exit 1 fi # Azure CLIの認証チェック if ! az account show &> /dev/null; then log_error "Azure CLIが認証されていません" log_error "認証方法: az login" exit 1 fi # リソースグループの存在確認 log_info "" log_info "📦 リソースグループの確認..." if ! az group show --name $RESOURCE_GROUP_NAME &> /dev/null; then log_warning "⚠ リソースグループ '$RESOURCE_GROUP_NAME' が見つかりません" log_warning " 削除する必要がありません" exit 0 else log_success "✅ リソースグループ確認: $RESOURCE_GROUP_NAME" fi # Static Web Appリソースの存在確認 log_info "" log_info "🔍 Azure Static Web App リソースを確認中..." # 指定されたリソースの存在確認 if az staticwebapp show \ --name "$STATIC_WEB_APP_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --output none 2>/dev/null; then # リソース詳細の取得 SWA_DETAILS=$(az staticwebapp show \ --name "$STATIC_WEB_APP_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --query "{name:name, defaultHostname:defaultHostname, sku:sku.name, location:location}" \ -o json) log_success "✅ 削除対象リソースが見つかりました:" echo "$SWA_DETAILS" | jq -r '" • 名前: \(.name)"' echo "$SWA_DETAILS" | jq -r '" • URL: \(.defaultHostname)"' echo "$SWA_DETAILS" | jq -r '" • プラン: \(.sku)"' echo "$SWA_DETAILS" | jq -r '" • リージョン: \(.location)"' RESOURCE_EXISTS=true else log_warning "⚠ 指定されたAzure Static Web App '$STATIC_WEB_APP_NAME' が見つかりません" log_warning " リソースグループ: $RESOURCE_GROUP_NAME" log_info "削除する必要がありません" exit 0 fi # 確認プロンプト log_warning "" log_warning "⚠ 以下の操作を実行します:" log_warning " 1. Azure Static Web App '$STATIC_WEB_APP_NAME' の削除" log_warning " 2. リソースグループ '$RESOURCE_GROUP_NAME' は保持します" log_warning " 3. GitHub環境設定は保持します" log_warning "" read -p "続行しますか? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then log_info "操作をキャンセルしました" exit 0 fi # === Azure Static Web App リソースの削除 === log_info "" log_info "🗑 Azure Static Web App リソースを削除中..." log_info "🔧 削除中: $STATIC_WEB_APP_NAME" if az staticwebapp delete \ --name "$STATIC_WEB_APP_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --yes 2>/dev/null; then log_success "✅ 削除完了: $STATIC_WEB_APP_NAME" else log_error "❌ 削除失敗: $STATIC_WEB_APP_NAME" exit 1 fi log_success "✅ Azure Static Web App リソースの削除が完了しました" # === 削除完了サマリー === log_success "" log_success "🎉 クリーンアップが完了しました!" log_success "" log_success "📋 実行内容サマリー:" log_success " ✅ Azure Static Web App削除: $STATIC_WEB_APP_NAME" log_success " ✅ リソースグループ保持: $RESOURCE_GROUP_NAME" log_success " ✅ GitHub環境設定保持: 変更なし" log_success "" log_success "📌 注意事項:" log_success " • リソースグループ '$RESOURCE_GROUP_NAME' は保持されています" log_success " • GitHub環境設定はそのまま残っているので、再デプロイ可能です" log_success "" log_success "🚀 Azure リソースのクリーンアップ完了!" クリーナップスクリプトの実行 作成時と同じく、環境変数を .env ファイルから読み込んで削除処理を実行します。既にdeployスクリプト実行時に .env ファイルは設定済みのはずなので、そのまま使用できます。 chmod +x ./infra/scripts/cleanup.sh ./infra/scripts/cleanup.sh 実行すると、以下のような確認が表示されます: 🎯 削除対象: swa-api-deploy-bicep (リソースグループ: swa-bicep-test) ⚠ 以下の操作を実行します: 1. Azure Static Web App 'swa-api-deploy-bicep' の削除 2. リソースグループ 'swa-bicep-test' は保持します 3. GitHub環境設定は保持します 続行しますか? (y/N): y を入力すると削除処理が開始されます。 削除される内容と保持される内容 削除されるもの: Azure Static Web Appリソース 関連するManaged Functions カスタム認証プロバイダー設定 保持されるもの: リソースグループ(他のリソースがある可能性を考慮) GitHub Environment設定(再デプロイ時に再利用可能) GitHub OAuth App(再利用可能) 完全削除を希望する場合 もし完全にクリーンな状態に戻したい場合は、以下も手動で削除してください: Azure側: リソースグループ全体(他にリソースがない場合) 必要に応じて Azure CLI の認証情報もクリア GitHub側: OAuth App(Settings > Developer settings > OAuth Apps から削除) Environment設定(リポジトリの Settings > Environments から削除) これで、課金を気にすることなく安心して検証を終了できますね。再度環境を作りたい場合は、deployスクリプトを実行すれば簡単に復旧できるのも、Infrastructure as Codeのメリットです! まとめ 今回は Azure Static Web Apps と Azure Functions を使った Next.js アプリケーションの認証 API 統合について、DevContainer での開発環境構築から本番デプロイまでの完全なワークフローを詳しく見てきました。 技術統合のポイント この構成の最大の魅力は、 フロントエンドとバックエンドを統一プラットフォームで管理できる点 にあります。Azure SWA が提供する認証機能と Azure Functions の API を組み合わせることで、複雑な認証フローを比較的シンプルに実装できました。特に x-ms-client-principal ヘッダーを使った認証情報の受け渡しは、SWA 独自の仕組みとして理解しておくと実装がスムーズになります。 開発体験の向上 DevContainer を使った開発環境は、チーム開発での環境差異を解消する大きなメリットがありました。Azure CLI や SWA CLI、Functions Core Tools がすべて統一環境で利用でき、ローカルでの統合テストも swa start コマンド一つで実行できる点は、開発効率を大幅に向上させます。 運用面での実用性 GitHub Actions を使った CI/CD パイプラインでは、 フロントエンドビルドと API テストを並列実行 することで、デプロイ時間の短縮を実現しました。また、Bicep を使った IaC により、インフラの再現性と管理性も確保できています。Standard プランは課金が発生しますが、カスタム認証プロバイダーや SLA が必要な本格的なプロジェクトには必須の選択肢です。 コメント ここまで出来たら、Azure Static Web Appsの基本は一通り理解できたかともいます。あとは運用の要望に合わせてチューニングするエンタープライズの領域に入っていきます。Bicepを使用して人間の手が入る可能性を減らせば減らすほど、問題が少なくなると思うので積極的にスクリプト化できるものはしていきましょう! 長々とした記事ですがお疲れ様でした!! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Azure SWA×Next.js認証API統合を実践解説【DevContainer〜本番まで】 first appeared on SIOS Tech. Lab .
アバター
はじめに こんにちはPSの佐々木です GitHub Copilot、ChatGPT、Claude等の生成AIツールは、現代のソフトウェア開発において不可欠な存在となっています。しかし、これらの生成AIが提案するコードには、意図せずライセンス違反を引き起こす可能性が潜んでいます。本記事では、生成AI開発におけるライセンス違反リスクと、SCANOSSを活用した効果的な対策について解説します。 生成AI開発におけるライセンス違反リスク 生成AIが学習するオープンソースコード 生成AIは膨大なオープンソースコードから学習しており、その中には様々なライセンスが適用されたコードが含まれています。AI が学習データから再現するコードには、以下のようなリスクが存在します: 意図しないライセンス違反 生成AIが提案したコードが、GPLやAGPLライセンスの既存コードと酷似している可能性 開発者が気づかないうちに、商用製品にコピーレフトライセンスのコードを組み込んでしまうリスク 従来のライブラリ依存関係では発見できない、コードスニペットレベルでの違反 企業が直面する課題 ライセンス違反リスクを恐れて、生成AIツールの導入を躊躇する企業が増加 開発効率向上のメリットと、法的リスクの間でのジレンマ 既存のライセンス検査ツールでは検出できない新しいタイプのリスク 具体的なリスクシナリオ ケース1: コードスニペットの意図しない複製 # 生成AIが提案したコードの例 def quicksort(arr): if len(arr) <= 1: return arr pivot = arr[len(arr) // 2] left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] return quicksort(left) + middle + quicksort(right) このようなコードが、実際にはGPLライセンスの既存プロジェクトから学習されている可能性があります。 ケース2: アルゴリズムの実装パターン 特定のアルゴリズムの実装において、生成AIが学習したコピーレフトライセンスのコードと同じパターンを再現してしまうケース。 主要なOSSライセンスと違反リスク パーミッシブライセンス(低リスク) MIT License 商用利用可能、制限が最小限 著作権表示のみ必要 生成AIで再現されても比較的リスクは低い Apache License 2.0 特許権の明確な規定あり 変更箇所の明記が必要 企業利用において安全性が高い BSD License シンプルで制限が少ない 商用利用に適している コピーレフトライセンス(高リスク) GPL (GNU General Public License) 最も注意が必要なライセンス 派生物もGPLライセンスでの配布が必要 商用製品での利用時、全ソースコードの開示義務 生成AIが無意識に組み込むと重大なリスク AGPL (GNU Affero General Public License) GPLよりもさらに厳しい条件 SaaS提供時でもソースコード開示が必要 クラウドサービス開発で特に危険 LGPL (GNU Lesser General Public License) ライブラリ自体の変更時のみソースコード開示 動的リンクの場合は比較的安全 弱いコピーレフトライセンス(中リスク) MPL (Mozilla Public License) ファイルレベルでのコピーレフト 変更したファイルのみソースコード開示 他のライセンスコードとの組み合わせは可能 SCANOSSによる生成AIコードの検知と対策 SCANOSSの独自技術 コードスニペットレベルでの検出 SCANOSSは、従来のライセンス検査ツールでは困難だった、わずか数行のコードスニペットからでも元のOSSコンポーネントとライセンスを特定できます: フィンガープリンティング技術 : コードの構造的特徴を分析し、部分的なコードからも元のライセンスを特定 機械学習による高精度マッチング : 大量のオープンソースコードデータベースから学習したパターンマッチング リアルタイム分析 : 開発中にリアルタイムでライセンス確認が可能 生成AIコードの包括的スキャン 開発ワークフロー統合 # 生成AIで作成されたコードの包括的スキャン scanoss scan --ai-generated-code --include-snippets /path/to/project # 特定のライセンスポリシーを適用したスキャン scanoss scan /path/to/project --policy-file corporate-policy.json --fail-on-license GPL-3.0 # CI/CDパイプラインでの自動スキャン scanoss scan . --output-format spdx --include-ai-analysis 検出できる違反パターン 生成AIによる既存コードの部分的な複製 Stack Overflowなどからコピーしたコードスニペット 依存関係に含まれないコードレベルでのライセンス違反 バイナリファイルに埋め込まれたコードの検出 実践的な生成AI開発でのライセンス管理 開発プロセスへの組み込み 1. コード生成時の即座チェック # 開発者のワークフロー例 # 1. 生成AIでコードを生成 # 2. SCANOSSで即座にライセンスチェック # 3. 問題があれば代替案を検討 # 4. 安全なコードのみを採用 2. CI/CDパイプライン統合 プルリクエスト時の自動スキャン 本番デプロイ前の包括的チェック ライセンス違反の検出時の自動アラート 3. 継続的な監視 新しい依存関係の追加時の自動チェック 既存コードの定期的な再スキャン ライセンスポリシーの更新時の全体チェック 企業でのライセンスポリシー策定 リスクレベル別の対応 高リスク(GPL、AGPL) : 原則使用禁止、例外時は法務承認必須 中リスク(LGPL、MPL) : 使用条件の明確化、適切な分離 低リスク(MIT、Apache、BSD) : 適切な著作権表示で使用可能 生成AIコードの承認プロセス 開発者による初期スキャン 自動化ツールによる検証 法務部門による最終承認(高リスクライセンス検出時) まとめ 生成AI時代のソフトウェア開発において、ライセンス管理は新たな複雑さを増しています。従来のライブラリレベルでの検出では捉えきれない、コードスニペットレベルでの違反リスクが現実のものとなっています。 SCANOSSのような専門的なツールを活用することで、以下のメリットを実現できます: 技術的メリット コードスニペットレベルでの高精度検出 生成AIコードの包括的な分析 開発ワークフローへのシームレスな統合 ビジネスメリット 法的リスクの大幅な軽減 生成AI導入による開発効率向上の安全な実現 コンプライアンス要件の確実な遵守 今後、生成AIがさらに普及する中で、適切なライセンス管理体制の構築は、企業の競争優位性を維持するための必須要件となるでしょう。早期にこれらの仕組みを整備し、安全に生成AIのメリットを享受できる環境を構築することが重要です。 サイオステクノロジーではSCAN OSSをサポートしています。 https://sios.jp/products/oss/scanoss/ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 生成AI時代のライセンス管理:SCANOSSによる違反リスクの検知と対策 first appeared on SIOS Tech. Lab .
アバター
はじめに こんにちは!重要な機能開発を任されて最近まで業務で手一杯だったなーがです。Windowsで開発環境を構築する際にGitやSSHなど、さまざまなツールの設定に時間を取られていませんか?PCを買い替えたり、突然PCが動かなくなったりしたときに新しい環境をセットアップするたびに同じ設定を繰り返すのは大変ですよね。 今回はそうした設定ファイルをGithubリポジトリとして管理することで、いつでも同じ開発環境を再現できる「 dotfiles 」という仕組みと、Windows環境に特化した具体的な構成をご紹介します。私も業務で使用しているPCのリプレース作業が近づいていることもあり、せっかくなので書いてみることにしました。スクリプトファイルは最後に付録として載せておきます! 📁 dotfilesリポジトリの紹介 今回ご紹介するのは、Windows環境向けに作成されたdotfilesリポジトリです。このリポジトリは、設定ファイルを一元管理し、簡単なコマンド一つで環境のセットアップを完了させることを目的としています。GitHubで「dotfiles」と検索してみると、多くの人が自身の設定ファイルを公開しています。 https://github.com/search?q=dotfiles&type=repositories リポジトリは以下のような構成になっています。ここでは比較的一般的な設定ファイルに絞って紹介します。 dotfiles/ ├── README.md # このファイル ├── setup.ps1 # セットアップスクリプト ├── ssh/ # SSH設定 │ └── config ├── VSCode/ # Visual Studio Code設定 │ ├── settings.json │ └── keybindings.json └── WindowsTerminal/ # Windows Terminal設定 └── settings.json VS CodeやWindows Terminalといった主要な開発ツールの設定ファイルが、すべてこのリポジトリで管理されているのがわかりますね。 ここでキモとなっているのが setup.ps1 で、Ubuntu等のLinux環境の方は XXXX.sh を用意されていると思います。私はメイン業務がWindowsなので、今回はPowerShellのスクリプトファイルしか記載していません。このスクリプトはこのリポジトリ内で管理している各設定ファイルと各設定ファイルが本来配置される箇所とのシンボリックリンクを作成する役割があります。シンボリックリンクを張ることで、各設定ファイルをわざわざ配置することなく参照でき、各設定ファイルに行った変更は全てこのリポジトリ内で管理できるというメリットがあります。 このスクリプトファイルですが、一昔前であればPowerShellやシェルスクリプトの知識が必要で少しハードルが高かったですが、現在はClaudeやGitHub CopilotをはじめとしたViveコーディングが可能なため「この設定ファイルのシンボリックリンクを作成するスクリプトを作成して」とお願いして、少し手直しするだけで簡単に実現できます。いい時代になりましたね…かく言うこのスクリプトも大枠はAIに作成してもらいました! 🚀 セットアップ方法 セットアップは非常にシンプルです。PowerShellスクリプトを実行するだけで、必要な設定ファイルへのシンボリックリンクが自動的に作成されます。 1. シンボリックリンクの作成 まず、管理者権限でPowerShellを開き、リポジトリ内にある setup.ps1 スクリプトを実行します。 # PowerShellを管理者として実行 .\\setup.ps1 このスクリプトを実行すると、以下の場所に設定ファイルのシンボリックリンクが作成され、リポジトリでの変更が即座に反映されるようになります。 VS Code設定 : %APPDATA%\\Code\\User\\ Windows Terminal設定 : %LOCALAPPDATA%\\Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\ SSH設定 : %USERPROFILE%\\.ssh\\ Git設定 : %USERPROFILE%\\.gitconfig その他のshell設定 : .bashrc , .bash_profile , .vimrc など ⚙️ オプション セットアップスクリプトには、便利なオプションも用意されています。 強制上書きモード すでに設定ファイルが存在する場合でも強制的に上書きしてシンボリックリンクを作成します。 .\\setup.ps1 -Force カスタムパス指定 dotfilesを異なる場所にクローンした場合でも、パスを指定して実行できます。 .\\setup.ps1 -DotfilesPath "C:\\path\\to\\your\\dotfiles" さいごに 今回は、Windows環境における設定ファイルの管理と自動化を行うdotfilesリポジトリを紹介しました。このような仕組みを活用することで、環境構築の手間を大幅に削減し、より本質的な開発作業に集中できます。 ぜひ、このリポジトリを参考に、ご自身の最強の開発環境を構築してみてはいかがでしょうか。 付録 <# .SYNOPSIS dotfilesフォルダ内のファイルのシンボリックリンクを作成するスクリプト .DESCRIPTION dotfilesフォルダ内にある設定ファイルを、適切な場所にシンボリックリンクとして作成します。 管理者権限が必要です。 .PARAMETER DotfilesPath dotfilesフォルダのパス(デフォルト: .) .PARAMETER Force 既存のファイルを上書きするかどうか .EXAMPLE .\Create-DotfilesSymlinks.ps1 .EXAMPLE .\Create-DotfilesSymlinks.ps1 -DotfilesPath "C:\dotfiles" -Force #> param( [Parameter(Mandatory = $false)] [string]$DotfilesPath = ".", [Parameter(Mandatory = $false)] [switch]$Force ) # 管理者権限チェック function Test-Administrator { $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } # シンボリックリンク作成関数 function New-SymbolicLink { param( [string]$LinkPath, [string]$TargetPath ) try { # リンク先のディレクトリが存在しない場合は作成 $linkDir = Split-Path -Parent $LinkPath if (-not (Test-Path $linkDir)) { New-Item -ItemType Directory -Path $linkDir -Force | Out-Null Write-Host "ディレクトリを作成しました: $linkDir" -ForegroundColor Green } # 既存のファイル/リンクをチェック if (Test-Path $LinkPath) { # 既存のパスがシンボリックリンクかどうか確認 $item = Get-Item -Path $LinkPath -Force $isSymLink = $item.Attributes -band [System.IO.FileAttributes]::ReparsePoint if ($isSymLink) { # シンボリックリンクの場合はスキップ Write-Warning "シンボリックリンクが既に存在します: $LinkPath (スキップ)" return $false } else { # 既存ファイルは削除 Remove-Item $LinkPath -Force Write-Host "既存のファイルを削除しました: $LinkPath" -ForegroundColor Yellow } } # シンボリックリンクを作成 New-Item -ItemType SymbolicLink -Path $LinkPath -Target $TargetPath -Force | Out-Null Write-Host "シンボリックリンクを作成しました: $LinkPath -> $TargetPath" -ForegroundColor Green return $true } catch { Write-Error "シンボリックリンクの作成に失敗しました: $LinkPath -> $TargetPath" Write-Error $_.Exception.Message return $false } } # メイン処理 function Main { Write-Host "=== Dotfiles シンボリックリンク作成スクリプト ===" -ForegroundColor Cyan # 管理者権限チェック if (-not (Test-Administrator)) { Write-Error "このスクリプトは管理者権限で実行する必要があります。" Write-Host "PowerShellを管理者として実行してから再度お試しください。" -ForegroundColor Yellow exit 1 } # dotfilesフォルダの存在チェック if (-not (Test-Path $DotfilesPath)) { Write-Error "dotfilesフォルダが見つかりません: $DotfilesPath" exit 1 } $DotfilesPath = Resolve-Path $DotfilesPath Write-Host "dotfilesフォルダ: $DotfilesPath" -ForegroundColor Cyan # ファイルマッピング定義 # 必要に応じて追加・変更してください $fileMappings = @{ # Git設定 ".gitconfig" = "$env:USERPROFILE\.gitconfig" # VS Code設定 "VSCode\settings.json" = "$env:APPDATA\Code\User\settings.json" "VSCode\keybindings.json" = "$env:APPDATA\Code\User\keybindings.json" # Windows Terminal設定 "WindowsTerminal\settings.json" = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json" # SSH設定 "ssh\config" = "$env:USERPROFILE\.ssh\config" # その他の設定 ".bashrc" = "$env:USERPROFILE\.bashrc" ".bash_profile" = "$env:USERPROFILE\.bash_profile" ".vimrc" = "$env:USERPROFILE\.vimrc" ".minttyrc" = "$env:USERPROFILE\.minttyrc" ".wslconfig" = "$env:USERPROFILE\.wslconfig" } $successCount = 0 $skipCount = 0 $errorCount = 0 Write-Host "`n--- シンボリックリンクの作成を開始します ---" -ForegroundColor Cyan foreach ($mapping in $fileMappings.GetEnumerator()) { $sourcePath = Join-Path $DotfilesPath $mapping.Key $targetPath = $mapping.Value # ソースファイルの存在チェック if (-not (Test-Path $sourcePath)) { Write-Warning "ソースファイルが見つかりません: $sourcePath (スキップ)" $skipCount++ continue } Write-Host "`n処理中: $($mapping.Key)" $result = New-SymbolicLink -LinkPath $targetPath -TargetPath $sourcePath -ForceOverwrite $Force if ($result) { $successCount++ } else { if ((Test-Path $targetPath) -and -not $Force) { $skipCount++ } else { $errorCount++ } } } # 結果サマリー Write-Host "`n=== 処理結果 ===" -ForegroundColor Cyan Write-Host "成功: $successCount" -ForegroundColor Green Write-Host "スキップ: $skipCount" -ForegroundColor Yellow Write-Host "エラー: $errorCount" -ForegroundColor Red if ($errorCount -eq 0) { Write-Host "`nシンボリックリンクの作成が完了しました!" -ForegroundColor Green } else { Write-Host "`n一部のシンボリックリンクの作成に失敗しました。上記のエラーメッセージを確認してください。" -ForegroundColor Yellow } } # スクリプト実行 Main ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post PCの環境構築を迅速かつ簡単に!dotfilesで設定管理を始めよう first appeared on SIOS Tech. Lab .
アバター
挨拶 ども!最近はClaudeの制限を見ると嬉しくなっちゃう龍ちゃんです。最近は日常作業をプロンプト化して、効率化を模索しています。割と毎日触っているので、それなりにストレスも抱えてブログを書けるぐらいにはナレッジが溜まりましたね。 今回は、Claudeに感謝を持ちつつ「もうちょっとこうして!」という不安を解消するためのプロンプトテクニックについてまとめていこうと思います。 公式が出しているプロンプトジェネレーターを使うのも一つの手ですよ! よくあるClaude暴走パターン Claude使ってて「もう!」って思ったことありませんか?以下、僕が普段困っているClaudeあるあるをまとめました。 前提として人間側が投げるプロンプトが雑であるところが原因になります。 パターン1:過剰なArtifact生成問題 気づいたら大規模なArtifactが作られるなんてことありませんか?これはよくある「Claude君止まって!案件」ですね。実際に僕が出会った暴走例を以下に示します。 典型例 ユーザー:「Chakra UIのモーダルでJSON文字列を表示したい」 Claude:「Chakra UIのモーダルでJSON文字列を表示する方法をご紹介します。」 → 高機能もりもりモーダルの作成 期待していたもの 50行程度のシンプルなコード Chakra UIの基礎的なモーダルでJSON文字列を表示する方法 現実 300行弱の実践的なコード 高機能モーダル(表示切替機能) 高機能モーダル(コピー機能) 高機能モーダル(編集機能) その他の構造化暴走例 勝手に体系化 :「プロジェクト管理のコツある?」→ 階層化された管理手法分類 勝手にステップ化 :「転職活動どうしよう」→ 12段階の転職完全ロードマップ 勝手にチェックリスト化 :「会議の準備って何する?」→ 50項目の準備チェックリスト 普通に質問として聞きたいだけなのに、なぜか教科書レベルの資料を作られてしまう現象です。 パターン2:スレッド肥大化問題 まだ聞きたいことがあったのに、「スレッドの上限に達しました、別スレッドでお願いします」と言われたことありませんか?Artifactがv22なんて更新地獄を味わったことなど、気になることを聞いているとスレッドの容量がパンパンになって、Claudeの動作もなんだか遅くなっている…なんてことありませんか? 典型的な流れ 軽い質問をする Claudeが詳細な回答 + アーティファクト生成 「もうちょっと詳しく」と追加質問 さらに詳細な資料が追加される やり取りが膨大になる スレッド上限に到達してリセット 文脈が失われて振り出しに戻る あるあるシーン ユーザー:「このコードどう思う?」 Claude:改善版コード + 詳細解説を生成 ユーザー:「エラーハンドリングも追加して」 Claude:完全版コード + エラー対応ガイドを生成 ユーザー:「テストも書いて」 Claude:テストコード + テスト戦略文書を生成 …(10往復後) システム:「スレッド上限に達しました」 パターン3:人間追いつけない問題 「昨今のAIの問題は、人間の理解が追い付いていないから一番のボトルネックは人間の頭」って投稿をXで見かけた気がするんですけど、これ結構好きなんですよね。実際にWebSearch系の機能がついてから、ネットに接続できるようになったじゃないですか。あれから調べもの系はClaudeかGeminiに任せたほうが精度高いんですよね。その情報を確認しますけど、読み込ませて質問ベースで聞いちゃうほうが早いんです。こりゃ困ったなってところですね。 ボトルネックは人間の頭 Claudeの処理速度 vs 人間の理解速度のギャップが生む問題。 典型例 ユーザー:「新サービスのアイデア考えて」 Claude:(10秒で) - 詳細な事業計画 - 市場分析レポート - 技術仕様書 - マーケティング戦略 - 収益予測 人間:「えーっと...まず何から読めば...」(理解に30分) よくあるパターン 情報過多で頭パンク :求めた以上の情報量で思考停止 判断材料が多すぎて決められない :選択肢を増やしすぎて逆に困る 完璧すぎて修正しにくい :隙のない提案で改善点が見つからない Claudeが優秀すぎて、人間側の処理が追いつかない現象です。 共通する課題 これらの暴走パターンに共通するのは: 過剰な解決志向 :問題を見つけると完璧に解決しようとする 文脈の読み違い :軽い質問を重要なタスクと誤解 成果物化癖 :何でもアーティファクトにしたがる 完璧主義 :中途半端よりも完全版を作りたがる いろいろ分析してみましたが、結局のところ人間側のプロンプトがあいまいってところに落ち着くんですよね。というかそこを頑張らないと人間がいる意味がないといいますか…技術として向上していく必要があるなって話です。 AIが優秀になるにつれて、「何を求めているのか」「どのレベルの回答が欲しいのか」を明確に伝えるスキルが重要になってきています。つまり、プロンプトエンジニアリングが日常的な必須スキルになっているということですね。 そんなわけで、普段使っているプロンプトテクニックといえるほどでもないですが、暴走を抑えることができるプロンプトを紹介していきます。 次の章からは、実際にプロンプトテクニックを紹介していきます。 Claude制御テクニック集 Claudeの暴走は「Claudeが悪い」のではなく、 100%人間側のプロンプトが曖昧 なのが根本原因。明確な指示で適切にコントロールしましょう。 基本原則:「Claudeは優秀すぎるから、明確に指示する」 まず大前提です。Claudeはめちゃくちゃ賢いやる気満々な子です。そしてすごくいい子です。基本全工程だし、めちゃくちゃほめてくれます。でも、会社の先輩や同僚みたいに自分の思考の癖や理解しているレベルを理解はしてくれません。なので、めちゃくちゃ賢くてやる気のある赤子ぐらいに思ってるとちょうどよいです。 Claudeは曖昧な質問を見ると「きっと詳しく知りたいんだろう」と解釈して、全力で応えようとします。それが「暴走」に見える現象の正体です。 なので、トークン数を気にせずにプロンプトは詳細であるほど良いですね。多少の例を渡すFew-shotやone-shotなんて技術ありましたけど、あれはゴールを明確にするって良い手法ですよね。この辺は海外の論文を読んだりすると詳細な分析が流れてくるので、検索するとおもしろいですよ。 今回は、体感できる簡単なテクニックを解説していきます。 制御テクニック1:成果物制御テクニック これはダイレクトにArtifact生成を制御するプロンプトになります。例で紹介した「Chakra UIのモーダルでJSON文字列を表示したい」では、大体はArtifact生成を進めますが、時にはチャット形式で返答することもあります。これは求めている形式を指定していないことで起きます。 根本原因 Claudeが勝手に「Goal」を定義して作ってしまう 「JavaScriptの関数について教えて」→ Claude「詳細なリファレンスが必要だ!」 「プロジェクト管理のコツある?」→ Claude「体系的なガイドを作ろう!」 Goalの定義 簡単なのは、「Goalの定義」を行うことです。以下がテンプレートになります。 Chakra UIのモーダルでJSON文字列を表示したい Goal:初心者でもわかるようにコード内にコメント多めで、コードサンプルArtifactを生成して Goal: として最終的なゴールを示してあげることで、Claudeはそこに向かって走ってくれます。今回はサンプルなので、簡単なゴール設定になっています。ただ、最終的な成果物の制限を追加することで向かってくれます。効果測定のために別のプロンプトを張ってみますね。 Chakra UIのモーダルでJSON文字列を表示したい Goal:初心者でもわかるようにコード内にコメント多めで、マークダウン形式でコード解説付きArtifactを生成して こちらでマークダウンファイルが作成されます。一言表現を変えるだけで幅が変わります。 成果物の否定 こちらも効果的です。普段だとやってほしいことを書くじゃないですか?これはそちらとは逆ですね。やってほしくないことを伝えるのも効果的です。強い言葉であるほど意識してくれますね。この辺は、スレッドが長くなるほど効果が薄れていく感じは多少あります。過去の情報って忘れていくんですよね。 ✅ 良い例: 「成果物を作らずに、考え方だけ教えて」 「Artifactは不要で、チャットで答えて」 「アプリ作成は不要、アドバイスだけお願いします」 「資料にしないで、会話として答えて」 「禁止事項:Artifact生成」 前提の共有 こちらは先ほど紹介した上の二つよりもさらにライトですが、普段使いとしては十分かもしれません。だんだん指示が友達に与えるみたいになっているのが良いんですよね。 ✅ 良い例: 「3行くらいで説明して」 「要点だけお願いします」 「一言でどう思う?」 「概要だけざっくりと」 「普通に会話として答えて」 段階的アプローチ これはおすすめです。「Goalの明確化」や「成果物の否定」や「前提の共有」のテクニックを文章として暗黙に伝えています。よく使うのは「ステップバイステップ」ですね。 ✅ 良い例: 「まずは概要だけ教えて、詳しく知りたくなったら追加で質問するから」 「まず一言で説明して、分からなかったら詳しく聞くよ」 「基本だけ教えて、応用は後で」 「ステップバイステップで」 龍ちゃんおすすめプロンプト 個人的に設定しているシステムプロンプトを共有しますね。ブログの校閲として使っているClaudeなんですが、これを設定しておくと精度が爆上がりしました。 情報を作成をする前に具体的なプランを明示して許可を取ってから実行をしてください。 許可が出るまで生成はしないでください。 明確な指示がない場合は、文章の校閲をしてほしいと解釈してください。 制御テクニック2:会話継続テクニック スレッドの上限まで達成したことがある方は、もうClaude沼にどっぷりと浸かっていますね。素晴らしいことです。でも、スレッド上限に達してしまって、また前提の共有からなんてやってられないですよね。Claudeはメモリ機能がないので、スレッドではまた別のClaudeとお話することになります。関係値を一から作る必要があります(ぴえんである)。 そんな時に使えるテクニックについて紹介します。 引継ぎ資料作成 → 新スレッド移行 これはスレッドがパンパンになる直前にやっておくとマジで助かります。スレッドの内容をPDFで出力させてしまい、それを新規スレッドの初回に入力として与えることで疑似的にスレッドの会話を継続させることができます。 ✅ 良い例: 「今までの議論を引継ぎ資料にまとめて。新しいスレッドでその資料をベースに続きをやりたい」 「現時点の作業までで決定したことを引継ぎ資料として作成して」 AI間役割分担によるトークン分散 これは、そもそもスレッドの上限に達しないようにするテクニックです。処理を事前に人間側で分散しておき、その段階ごとにスレッドを分けて中間ファイルを生成させておきます。最終的な成果物ファイルを生成する前に段階を踏ませることで、スレッド内のトークン数を分散させます。 ✅ 良い例: 調査用 Claude:「xxxxxxxxに関する調査をしてマークダウン形式でまとめて」 プロンプト生成用 Claude:「添付したファイルをもとに〇〇〇生成用プロンプトを生成して」 生成用 Claude:「添付ファイルからよろしく~」 Artifactベースの新規Artifact作成 これは生成されたものをベースに新規のArtifactを生成することで、対象とするArtifact自体を軽量化する手法です。Artifactベースに新規のArtifactを作成する場合は以下の二つの目的で生成します。 軽量版Artifactを生成する:いらない要素をそぎ落とす 新規Artifactを生成する:大きくなりすぎたファイルを分割する 軽量版 大きいArtifactから内容を抽出して、軽量化することができます。Artifact生成でいらない要素がくっついてくることがたまにありますよね?それらをそぎ落とすことで、軽量化されます。Artifactの中身をセクションごとに分割することで、対象とするファイルの大きさを小さくすることもできます。 ✅ 良い例: 「このアーティファクト重くなりすぎたから、軽量版で作り直して」 「要点だけに絞った簡易版を新しく作って」 「今の成果物をベースに、コンパクト版お願いします」 新規Artifact ✅ 良い例: 「今までの議論を踏まえて、新しいアーティファクトで整理し直して」 「別の成果物として、今回の結論をまとめて」 「前のは置いといて、新規で作り直そう」 段階的成果物更新 これは、小さなArtifactを段階的に更新する手法です。プロンプトでArtifact生成を止めておいて、「段階的に」と指示をすることで小さなArtifactに対して段階的更新をかけていきます。これの良い点は、Artifact生成を人間がちゃんと監視しているので、「なんか方向性が違う!」というのがすぐわかる点がよいですね! ✅ 良い例: 「成果物を少しずつ更新していこう」 「この部分だけ先に反映させて」 「一気に全部やらずに、段階的に進めよう」 制御テクニック3:ペース調整テクニック(人間主導の学習プロセス) これはClaudeが出力したコードを正として使用するのではなく、 人間がペースを握って学習を進める 方法です。体感的には、Claudeが100%完璧なコードを出すケースは少ないですが、領域を限定して小さく出力させることで、より確実に動くコードを生成できます。 基本的な学習ステップ 大枠を作成してもらう – 全体像を把握 特定の領域に対して深堀り質問 – 段階的に理解を深める わからない部分は説明を求める – 疑問点を即座に解決 理解度確認のために自分の言葉で例え話 – 学習の定着を図る 学習フロー図 制御テクニック3(実践例):Kubernetesの学習過程 この手法を活用して、Kubernetesが一切わからない段階から、新規作成したDaemonSetを検証環境にデプロイできるレベルまで到達した実例をご紹介します。 1. 大枠把握からスタート 入力例: k8sに関しては一ミリもわからないのですが、初心者が理解しておくべきポイントをまとめてください。 出力例: Kubernetesの初心者向け重要ポイントをまとめます: ## Kubernetesとは何か Kubernetesは、コンテナ化されたアプリケーションを自動的に管理・運用するためのオープンソースのプラットフォームです。「k8s」と略されることが多く、これは「k」と「s」の間に8文字あることから来ています。 ## 基本概念 - **クラスター**: Kubernetesが管理するサーバー群全体 - **ノード**: 実際にアプリケーションが動く個々のサーバー - **Pod**: アプリケーションの最小実行単位 - **デプロイメント**: Podの管理・配置を行う仕組み ~~~~~以下略~~~~~ 2. 理解確認型学習 入力例: ノードとDaemonSetとPodの理解から始めればよいかな? 出力例: はい、それはとても良い学習順序です! ## なぜこの3つから始めるのが良いか この3つの関係を理解すると、Kubernetesの基本的な構造が見えてきます: - **ノード** → 物理的な土台(サーバーやコンピューター) - **DaemonSet** → 管理の仕組み(「各ノードに1つずつ配置する」というルール) - **Pod** → 実際に動くアプリケーション ## 学習の順序 1. まずノードを理解(土台) 2. 次にPodを理解(実際に動くもの) 3. 最後にDaemonSetを理解(管理の仕組み) ~~~~~以下略~~~~~ 3. 段階的深堀り学習 入力例: Podが最小構成なのは知っています。 PodがDockerのコンテナーって理解であってる? 出力例: いえ、少し違います。Pod ≠ Dockerコンテナです。 ## 正しい関係 PodはDockerコンテナを入れる箱のようなものです: Pod(箱) ├── Container 1(メインアプリケーション) ├── Container 2(サイドカーコンテナ)※オプション └── 共有リソース(ネットワーク、ストレージ) ## 重要なポイント - 1つのPodに複数のコンテナを含めることができる - 同じPod内のコンテナは同じネットワーク(IPアドレス)を共有 - 多くの場合、1Pod = 1Container として使用される ~~~~~以下略~~~~~ 続きの質問: Pod内にContainerがある関係なのね。Podとコンテナは同一のものではないけど、要素として共通点は多い印象かな ただ、k8sの環境ではPodという概念で管理しているだけって感じ? 出力例: まさにその通りです!とても良い理解ですね。 ## あなたの理解は正確です Pod ⊃ Containerの関係で、確かに機能的には似ている部分が多いです。 ## なぜPodという概念が存在するのか 1. **複数コンテナの協調動作**: 1つのアプリケーションを複数の小さなコンテナで構成 2. **リソース共有**: 同じPod内のコンテナは簡単にデータをやり取りできる 3. **運用の単位**: Kubernetes では Pod単位でスケジューリング・管理 ## 実際の開発では 90%以上のケースで「1Pod = 1Container」で運用されています。 Podは「コンテナを管理するための仕組み」と考えるのが適切です。 ~~~~~以下略~~~~~ 4. 理解度チェックの要求 入力例: ノード上には複数のPodが含まれる可能性がある Deploymentは管理しているクラスター全体で特定の数配置できる DaemonSetはノードの数分だけPodをノード内に一つ配置できる であってる? 出力例: はい、完璧に正確です! ## あなたの理解 ✓ ✓ **ノード上には複数のPodが含まれる可能性がある** ✓ **Deploymentはクラスター全体で特定の数配置できる** ✓ **DaemonSetはノードの数分だけPodをノード内に一つ配置できる** ## 図解で確認 ```bash クラスター全体 ├── ノード1 │ ├── Pod A(Deployment管理) │ ├── Pod B(Deployment管理) │ └── Pod X(DaemonSet管理)← 各ノードに必ず1つ ├── ノード2 │ ├── Pod C(Deployment管理) │ └── Pod Y(DaemonSet管理)← 各ノードに必ず1つ └── ノード3 └── Pod Z(DaemonSet管理)← 各ノードに必ず1つ ``` ## 使い分け - **Deployment**: Webアプリケーションなど、必要な数だけ配置 - **DaemonSet**: ログ収集、監視エージェントなど、全ノードに配置が必要 ~~~~~以下略~~~~~ 5. ネクストステップ 入力例: 次はネームスペースについて勉強したい このテクニックの効果 メリット 学習者主導 : 自分のペースで理解を深められる 段階的理解 : 一度に大量の情報を処理しなくて済む 確実な定着 : 理解度を確認しながら進むため、知識が定着しやすい 応用可能 : 様々な技術領域で応用できる 適用シーン 新しい技術の学習 複雑なシステムの理解 コード理解とデバッグ 設計パターンの習得 実践のコツ 1. 質問の仕方 ❌ 悪い例:「Kubernetesを教えて」 ✅ 良い例:「Kubernetesの基本概念を3つに絞って教えて」 2. 理解度確認 ❌ 悪い例:「わかりました」 ✅ 良い例:「つまり、Podは家でコンテナは住人のような関係ですね?」 3. 段階的深堀り ❌ 悪い例:「すべて詳しく説明して」 ✅ 良い例:「まずノードの概念から理解したい」 このテクニックを使うことで、AIを効果的な個人チューターとして活用し、確実にスキルアップを図ることができます。 まとめ 今回は、Claudeの制御テクニックについて詳しく見てきました。最初は「Claude君止まって!」と思っていた暴走も、実は人間側のプロンプトが曖昧だったことが根本原因だったんですね。 今日から使える3つのポイント 1. 明確な指示が全ての基本 Goal設定や成果物の否定、前提共有で明確に方向性を示すことが重要です。 2. スレッド管理で効率的な会話継続 引継ぎ資料作成→新スレッド移行や、AI間役割分担でスレッド上限問題を解決できます。 3. 人間主導の学習プロセス 大枠把握→理解確認→段階的深堀り→理解度チェックの流れで、確実にスキルアップを図れます。 おわりに Claudeを「めちゃくちゃ賢くてやる気のある赤子」だと思って接すると、うまく付き合えるかもしれません。明確な指示を与えてあげることで、本当に強力なパートナーになってくれます。 この知識を活かして、より効率的なAI活用にチャレンジしてみてください!皆さんも、ぜひ自分なりのClaude調教術を編み出してみてくださいね。 それでは、良いClaude調教ライフを! いろいろなAIのブログ 書いてますので、ぜひ読んでみてね! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude調教術|暴走パターンを制御する3つのプロンプトテクニック first appeared on SIOS Tech. Lab .
アバター
はじめに こんにちは、サイオステクノロジーの小沼 俊治です。 これまで、オリジナルの MCP サーバーを開発するノウハウを、以下の記事で公開してきました。 第1弾: オリジナルのちょっと便利な MCP サーバー を作ってみた 第2弾: オリジナルのちょっと便利な『リモート MCP サーバー』を作ってみた その第3弾として、2025年6月下旬に、ローカル MCP サーバーをパッケージングして手軽に配布できる「Desktop Extensions(以下、デスクトップ拡張、または DXT と略して表す)」の仕組みが公開されたので、その最新ノウハウを共有します。 Claude Desktop Extensions: One-click MCP server installation for Claude Desktop anthropics/dxt ローカルMCPサーバーは、AIエージェントを自由に拡張できる非常に便利な機能です。しかし、これまでは開発ツールの準備や設定ファイルの編集など、インストールが複雑で IT リテラシーが高くないと導入が難しいという課題がありました。 今回の DXT の登場により、 パッケージングされたローカル MCP サーバーは、マウスのクリックやドラッグ&ドロップといった簡単な操作でデスクトップ拡張としてインストールできるようになります。 これはMCPサーバーの普及を大きく加速させる画期的な仕組みであり、私自身も大変ワクワクしています! 本ハンズオンはローカル MCP サーバーを対象とするため、第1弾「 オリジナルのちょっと便利な MCP サーバー を作ってみた 」の続きとして解説を進めます。引き続き、MCP サーバーの実装には Node.js を使用します。 構成概要 筆者が動かした際の主な構成要素は以下の通りです。 DXT 配布元となるローカル MCP サーバーの開発 PC Windows 10 Professional Claude desktop for Windows version 0.11.6 Windows PowerShell 5.1.19041.5737 Node.js v22.15.0 DXT 配布先となるローカル MCP サーバーの利用 PC Windows 10 Professional Claude desktop for Windows version 0.11.6 ハンズオンを構成する環境は以下の通りです。 MCP ホストは、ローカル MCP サーバーを組み込める Claude desktop のネイティブアプリ版を利用します。 配布元 PC の構成における主な考慮事項は以下の通りです。 MCP サーバーを稼働させる環境は Node.js 環境を選択しています。 TypeScript で実装したソースコードのトランスパイルや、デスクトップ拡張としてパッケージングするため Node.js のインストールが必要です。 配布先 PC の構成における主な考慮事項は以下の通りです。 デスクトップ拡張としてインストールした MCP サーバーの稼働には、Claude の組込み Node.js を利用できるため、別途 Node.js のインストールは不要です。 Claude にバンドルされた Node.js の利用について TypeScript で実装して JavaScript にトランスパイルされたローカル MCP サーバーを動かす場合には、Claude にバンドルされた Node.js を利用できます。 デフォルトで有効になっていますが、Claude desktop 画面左上のハンバーガーメニュー(三本線)から「ファイル > 設定…」で表示した設定画面で、左ペインより「エクステンション」を選び、画面下部の「詳細設定」ボタンをクリックすると組み込み Node.js の有効状態が確認できます。 ハンズオンの手順でパッケージングするローカル MCP サーバーの設定ファイルやソースコードは、以下の GitHub リポジトリで公開しています。必要に応じてご活用ください。 hands-on-mcp-sios-apisl @ GitHub v1.0.0 : オリジナルのちょっと便利な MCP サーバーを実装 配布元 PC で基礎環境の構築(第1弾の環境復元) 以前の記事「 オリジナルのちょっと便利な MCP サーバー を作ってみた 」で開発したローカル MCP サーバーのソースコード等のリソースが手元にある場合には、本章はスキップして以下の章へ進んでください。 ローカル MCP サーバーを Desktop Extensions で便利に配布してみた | 配布元 PC で DXT ファイルを作成 Claude desktop for Windows インストール MCP ホストに Web 版ではなくアプリ版の Claude desktop for Windows を利用するため、以下公式手順を参考に Windows PC へインストールします。 Claude for Desktopのインストール | Anthropicヘルプセンター Node.js インストール パッケージの作成に Node.js を必要とするため、以下手順を参考に Windows PC へインストールします。 初期環境構築: Node.js on Windows デスクトップ拡張にするローカル MCP サーバーの用意 ローカル MCP サーバーのリソースを用意する方法は2通りあります。お使いの環境に合わせて、いずれかの方法でリポジトリを取得してください。 方法1:Git コマンドで取得する Git がインストールされている場合は、以下のコマンドでリモートリポジトリをクローンします。 PS C:\Users\...\development> git clone https://github.com/Toshiharu-Konuma-sti/hands-on-mcp-sios-apisl.git -b v1.0.0 方法2:ブラウザでダウンロードする Git がインストールされていない、またはコマンド操作に不慣れな場合は、ブラウザから GitHub の以下 URL にアクセスし、「Source code (zip)」をクリックして Zip ファイルを取得します。 Release v1.0.0 · Toshiharu-Konuma-sti/hands-on-mcp-sios-apisl 配布元 PC で DXT ファイルを作成 配布元 PC でローカル MCP サーバーをパッケージングして、デスクトップ拡張(DXT ファイル)として配布できるようにします。 デスクトップ拡張のパッケージ作成には dxt コマンドが必要ですが、通常、このコマンドをグローバルインストール( npm install -g @anthropic-ai/dxt )すると管理者権限やルート権限が求められます。 本ハンズオンでは管理者権限なしで dxt コマンドのパッケージを直接実行できる npx コマンドを使って進めます。 パッケージングするリソースの準備 デスクトップ拡張としてパッケージングするために、ローカル MCP サーバーが実行可能な状態にして、必要なリソースを準備します。 依存パッケージの準備 まず始めに、依存パッケージをダウンロードします。 PS C:\Users\...\hands-on-mcp-sios-apisl> npm install 次に、TypeScript から JavaScript のソースコードをトランスパイルして生成するため、ビルドを行います。 PS C:\Users\...\hands-on-mcp-sios-apisl> npm run build アイコンの準備 アイコンの設定は必須ではありませんが、 配布先の利用者にとって製品としての完成度が高まるため、設定をお勧めします。 今回は「 FLAT ICON DESIGN -フラットアイコンデザイン- 」様より、ローカル MCP サーバーの機能(天気予報)にちなんだアイコンを使用させていただきます。 天気のアイコン素材 用意したアイコンは、プロジェクトリポジトリの直下に image\ フォルダを作成し、その中に PNG 形式の画像ファイルとして保存します。 準備できた構成 ここまでの手順で、パッケージングに必要なソースコードや画像などのリソースが以下の構成で準備できたことを確認してください。これが整っていれば、いよいよデスクトップ拡張(DXT)を作成するパッケージング手順に進めます。 PS C:\Users\...\hands-on-mcp-sios-apisl> tree /f C:. │ .gitignore │ LICENSE │ package-lock.json │ package.json │ README.md │ tsconfig.json │ ├─build │ │ index.js │ ├─interfaces │ │ areaApi.js │ └─services │ areaWeatherForecast.js ├─image │ f_f_traffic_4_s512_f_traffic_4_0bg.png ├─node_modules │ : │ (省略) │ └─src │ index.ts ├─interfaces │ areaApi.ts └─services areaWeatherForecast.ts マニフェストの新規作成 dxt init コマンドを使うと、デスクトップ拡張のパッケージングに必要な manifest.json ファイルを対話形式で簡単に作成できます。以下のコマンドを実行してください。 PS C:\Users\...\hands-on-mcp-sios-apisl> npx @anthropic-ai/dxt init This utility will help you create a manifest.json file for your DXT extension. Press ^C at any time to quit. ✔ Extension name: hands-on-mcp-sios-apisl ✔ Author name: SIOS Technology, Inc. API Solution Service Line Devision. ✔ Display name (optional): The hands-on local MCP server ✔ Version: 1.0.0 ✔ Description: A bit useful locat MCP server to check a weather! ✔ Add a detailed long description? no ✔ Author email (optional): ✔ Author URL (optional): https://api-ecosystem.sios.jp ✔ Homepage URL (optional): ✔ Documentation URL (optional): https://tech-lab.sios.jp/archives/48129 ✔ Support URL (optional): ✔ Icon file path (optional, relative to manifest): image/f_f_traffic_4_s512_f_traffic_4_0bg.png ✔ Add screenshots? no ✔ Server type: Node.js ✔ Entry point: build/index.js ✔ Does your MCP Server provide tools you want to advertise (optional)? no ✔ Does your MCP Server provide prompts you want to advertise (optional)? no ✔ Add compatibility constraints? no ✔ Add user-configurable options? no ✔ Keywords (comma-separated, optional): ✔ License: MIT ✔ Add repository information? yes ✔ Repository URL: git+https://github.com/Toshiharu-Konuma-sti/hands-on-mcp-sios-apisl.git Created manifest.json at /home/hoge/handson/hands-on-mcp-sios-apisl/manifest.json Next steps: 1. Ensure all your production dependencies are in this directory 2. Run 'dxt pack' to create your .dxt file 対話形式で入力する主要な項目とその内容は以下の通りです。 Extension name: DXT のファイル名になります。 Author name: 作成者名を入力します。 Display name: デスクトップ拡張として表示される名称です。 Description: 入力すると、デスクトップ拡張の名称の下に説明文として表示されます。 Author URL: 入力すると、作成者が該当 URL へのリンクとなります。 Icon file path: PNG 画像ファイルのパスを入力すると、デスクトップ拡張のアイコンを設定できます。 Entry point: ローカル MCP サーバーのスクリプトファイルパスを入力します。 対話形式での入力が完了したら、 manifest.json ファイルができあがったことを確認します。 PS C:\Users\...\hands-on-mcp-sios-apisl> ls Mode LastWriteTime Length Name ---- ------------- ------ ---- : -a---- 2025/07/01 10:47 705 manifest.json : DXT ファイルの作成 いよいよ、ローカルMCPサーバーをパッケージ形式のデスクトップ拡張として作成します。プロジェクトのルートディレクトリで、以下のコマンドを実行します。 PS C:\Users\...\hands-on-mcp-sios-apisl> npx @anthropic-ai/dxt pack Validating manifest... Manifest is valid! 📦 hands-on-mcp-sios-apisl@1.0.0 Archive Contents 27B BUILD.bat 2.1kB build/index.js : 110.9kB node_modules/zod-to-json-schema/dist/ [and 78 more files] 509.2kB node_modules/zod/lib/ [and 25 more files] Archive Details name: hands-on-mcp-sios-apisl version: 1.0.0 filename: hands-on-mcp-sios-apisl-1.0.0.dxt package size: 5.0MB unpacked size: 22.8MB shasum: 04fc9aff3e343e1e0fa2afaf3a537ed51a71a09a total files: 817 ignored (.dxtignore) files: 692 Output: C:\Users\...\hands-on-mcp-sios-apisl\hands-on-mcp-sios-apisl.dxt コマンドの実行が成功したら、デスクトップ拡張ができあがったことを確認します。 PS C:\Users\...\hands-on-mcp-sios-apisl> ls Mode LastWriteTime Length Name ---- ------------- ------ ---- : -a---- 2025/07/01 10:56 5282475 hands-on-mcp-sios-apisl.dxt : できあがったパッケージ形式のデスクトップ拡張は、ウェブサイトで公開やクラウドのファイルサーバーで共有などを活用して利用者に配布します。 配布先 PC で DXT ファイルからインストール ここからは、配布された DXT ファイルを使って 配布先 PC でローカル MCP サーバーをインストールし、利用可能にするまでの手順 を説明します。 DXT ファイルの入手 まず、インストールしたいローカル MCP サーバーのパッケージ形式のデスクトップ拡張を、開発者からウェブ経由などで事前に受け取ってください。 DXT ファイルからインストール Windows のスタートメニューから Claude desktop を起動します。プロンプトの入力ボックス左下部にある「検索とツール」ボタンをクリックしても、オリジナルのデスクトップ拡張(オリジナルの MCP サーバー)がリストに無いため、まだインストールされていないことが確認できます。 Claude desktop 画面の左上にある三本線のハンバーガーメニューから「ファイル > 設定…」メニューを選択して設定画面を表示し、左ペインから「エクステンション」を選びます。 エクステンション画面が表示されている状態で、エクスプローラーを起動します。事前に入手した DXT ファイルをエクスプローラーから Claude desktop のエクステンション画面へドラッグアンドドロップします。 エクステンション画面上で DXT ファイルをドロップすると、オリジナルのローカル MCP サーバーのインストール画面に切り替わり、内容を確認したうえで「インストール」ボタンをクリックしてインストールします。 インストールが終わったら設定画面の上部にある「< すべての拡張機能」を選択して戻ります。 エクステンション画面にオリジナルのローカル MCP サーバーが追加されていることが確認できたら、設定画面の右上の×ボタンをクリックして画面を閉じます。 プロンプトの入力ボックス左下部にある「検索とツール」ボタンをクリックすると、今度はオリジナルの MCP サーバーがリスト存在することが確認できます。 これで、デスクトップ拡張のインストール手順は完了です。インストールしたローカルMCPサーバーでどのようなことができるかについては、「 オリジナルのちょっと便利な MCP サーバー を作ってみた 」で紹介している動画をご覧ください。 まとめ Desktop Extensions(デスクトップ拡張)は、いかがでしたでしょうか? 今回の記事では、この Desktop Extensions がローカル MCP サーバーの配布をいかに容易にするか、そして MCP サーバーの普及にどう貢献するかについてご紹介しました。 ぜひ、皆さんも一度試して、その便利さを実感してみてください。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post ローカル MCP サーバーを Desktop Extensions で便利に配布してみた first appeared on SIOS Tech. Lab .
アバター
挨拶 ども!6/25にGemini CLIがリリースされて、7月に入ってから気づいて出遅れてしまった龍ちゃんです。Xでだいぶ話題になっていますね。noteの「 プログラミング1ミリも分からない人がGemini CLI始めるまでの全手順(Win篇) 」を見て、Gemini CLIがリリースを知りました。まぁ私はエンジニアなので、エンジニア目線で使い方を模索していこうと思っています。Gemini CLIだと、いろいろとカスタマイズができそうってことで、入門していきます。 弊社では、Geminiをワークスペースで契約をしているので普段から使えています。(生成AIサービスも福利厚生という時代になりましたね…) 今回は「DevContianerでGemini CLIを使うことができる環境をセットアップし、システムプロンプトを設定してURLからXの投稿文を生成」を目標として環境構築からセットアップまで行っていきます。 それでは始めましょう。 1. はじめに – なぜDevContainer × Gemini CLIなのか 皆さんは、AI開発環境の「汚染問題」に悩まされていませんか? 今回DevContainer×Gemini CLIの環境を構築した背景は、 チーム開発での環境差異を根本的に解決したい からです。個人利用なら全く問題ないのですが、チーム開発となると話は別ですよね。 グローバルインストールでホスト側に設定ファイルをゴリゴリ書き込んでいると、「なんで俺のGeminiとお前のGeminiは違うんや!」という環境差異が必ず発生します。開発者は自由に自分の環境を作ってしまう自由人ですからね。新卒や別担当者が参画した際に「あれ?このコマンド入ってない」で一日潰れる光景、想像に難くないでしょう。 そこで注目したいのが、 2025年6月にGoogle公式リリースされたGemini CLI です。無料枠が1日1000回と太っ腹で、実用性は十分。DevContainerと組み合わせることで、 安全で再現可能な開発環境 が実現できます。 実践的な検証として、普段Claude Projectで使用しているプロンプトを使って「雑プロンプト」「Claude Project」「Gemini CLI」の3パターンで品質比較も行います。 今回の記事で学べること: DevContainerでGemini CLIを安全に動かす設定方法 GEMINI.mdによるプロジェクト固有カスタマイズ システムプロンプトの効果的な設定テクニック 実践:URLからテック系ブログのX投稿文を生成してdocs配下に自動保存 それでは、チーム開発を前提とした「Geminiの回答品質担保」環境を一緒に構築していきましょう! 2. Gemini CLIとDevContainerの基礎知識(800文字) 2.1 Gemini CLIとは Gemini CLIは、2025年6月にGoogleが公式リリースしたオープンソースAIエージェントです。Apache 2.0ライセンスで提供されており、企業利用も安心ですね。 主要機能として以下が挙げられます: ファイル操作 : プロジェクトファイルの読み書き・編集 コマンド実行 : シェルコマンドの実行とレスポンス取得 Web検索 : リアルタイム情報収集 MCP対応 : Model Context Protocolによる拡張性 特に注目したいのが サンドボックス機能 です。これは、AI実行環境を隔離することで、予期しないシステム変更を防ぐGemini CLI独自の安全機能となります。 2.2 DevContainerとは DevContainerは、VS Code DevContainersの略称で、 コンテナベースの開発環境 を提供する技術 です。従来の「俺の環境では動くんだけどなあ」問題を根本的に解決してくれます。 AI開発における特有のメリットとして、 環境破壊防止 が挙げられます。AIエージェントが予期しないパッケージインストールやシステム変更を行っても、ホスト環境は完全に保護されるわけです。 コンテナ化により、チームメンバー全員が 同一の実行環境 を共有できるため、「なんで俺の環境だと動かないんだ」という状況を回避できます。これは、特に新卒研修や外部パートナーとの協業において威力を発揮しますね。 2.3 組み合わせの利点 DevContainer × Gemini CLIの組み合わせには、4つの主要メリットがあります。 安全性 : Gemini CLIのサンドボックス機能とDevContainerの隔離環境により、 二重の安全保障 が実現されます。AIが暴走してもホスト環境への影響は皆無です。 再現性 : 環境設定が .devcontainer/devcontainer.json としてコード化されるため、 誰でも同じ環境を瞬時に再現 できます。チーム間での環境差異によるトラブルが根絶されますね。 拡張性 : MCP対応により、プロジェクト固有の機能追加が容易です。継続的な改善と標準化が可能で、 組織全体での知見蓄積 に貢献します。 コスト効率 : Google Workspace統合により、既存の企業インフラとシームレスに連携できます。新たなライセンス購入や複雑な導入プロセスが不要で、 企業導入のハードルが格段に下がります 。 実際のプロジェクトでどちらを選ぶべきか、迷うところですが、チーム開発を前提とするなら、この組み合わせが現時点での個人的ベストプラクティスと言えるでしょう。 3. 環境準備と前提条件 3.1 必要なツール 今回の検証はGoogle Workspace環境で行っています。@google.comではなく@<自社ドメイン>.comアカウントでの検証ですが、認証方法が若干異なるだけなので、あまり気にしなくて大丈夫です。 Docker環境については、今回WSL2にDocker Engineを直接インストールして検証しています。ただし、 個人利用の方はMicrosoft公式が推奨している Docker Desktop の構築 をお勧めします。 企業でお使いの方、特に新人・若手の皆さんへ : Docker Desktopには商用利用時のライセンス制約があります。必ず上長または情報システム部門(最低でも同僚)に確認を取ってください。ライセンス問題は最悪の場合、訴訟に発展するリスクがあるため、慎重に対応しましょう。 3.2 Google Workspace環境の設定 Google Workspace環境では、 Gemini for Google Cloud API が有効化されたプロジェクトが必要になります。このAPIが有効になっていない場合、Gemini CLIの認証段階でエラーが発生します。 Google Cloud Consoleでプロジェクトを作成し、以下の手順でAPIを有効化してください: Google Cloud Console → APIとサービス → ライブラリ “Gemini API”で検索 Gemini for Google Cloud APIを選択して「有効にする」 3.3 DevContainer環境の準備 DevContainerを使用するため、以下のツールが必要です: VS Code : 最新版を推奨 DevContainers拡張機能 : Microsoft公式の拡張機能 Docker環境 : Docker DesktopまたはDocker Engine VS Codeの拡張機能タブで「DevContainers」を検索してインストールしてください。この拡張機能により、コンテナ内での開発が可能になります。 3.4 事前確認チェックリスト 作業を始める前に、以下の項目を確認してください: Docker環境の動作確認 : docker --version でバージョンが表示される VS Code + DevContainers拡張機能 : 正常にインストール済み Google Workspace アカウント : 組織アカウントでのログインが可能 Google Cloud プロジェクト : Gemini APIが有効化済み ネットワーク環境 : Google APIsへのアクセスが可能(企業ファイアウォール確認) これらの準備が整ったら、実際の環境構築に進むことができます。次のセクションでは、DevContainerの設定ファイル作成から始めていきましょう! 4. DevContainer設定 4.1 プロジェクト構造の作成 それでは、今回作成するプロジェクトのディレクトリ構成から見ていきます。 . ├── .devcontainer │ └── devcontainer.json # DevContainer設定の心臓部 ├── docs # 作業結果を保存するディレクトリ ├── .gemini # Gemini CLI設定ディレクトリ │ └── settings.json # API Key等の設定 ├── GEMINI.md # システムプロンプト設定ファイル ├── .gitignore └── README.md ここがポイント! 特に注目していただきたいのが** GEMINI.md **です。このファイルがシステムプロンプトを保存する重要な役割を担います。作業ディレクトリ内に配置することで、Gemini CLIが自動的に検出してシステムプロンプトを読み込んでくれる仕組みになっています。 docs ディレクトリは、Gemini CLIが生成した成果物を整理して保存するためのディレクトリです。チーム開発では、生成された結果を適切に管理することで、ナレッジの蓄積と共有が可能になります。これは、私の検証環境でも非常に重宝している構成ですね。 4.2 devcontainer.jsonの詳細設定 続いて、DevContainerの設定で最も重要な devcontainer.json の内容を詳しく見ていきます。WSL環境での動作を想定した設定になっています。 設定項目は公式リファレンス にまとまっています。 { "name": "Gemini CLI Development (DinD)", "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye", "features": {}, "postCreateCommand": "npm install -g npm@11.4.2 @google/gemini-cli ", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "remoteUser": "node", "customizations": { "vscode": { "extensions": [ "ms-vscode-remote.remote-containers", "ms-vscode-remote.remote-wsl" ], "settings": { "terminal.integrated.defaultProfile.linux": "bash" } } } } 4.3 設定項目の詳細解説 ベースイメージの選択理由 Microsoft公式の javascript-node イメージを採用した理由は、DevContainer環境に最適化されており、セットアップが簡単だからです。このイメージでは UID:1000 が node ユーザーとして事前設定されており、権限管理がスムーズに行えます。 他のDevContainerイメージでは vscode ユーザーが使われることが多いですが、Node.js環境では node ユーザーが標準的です。これは、npmのグローバルインストール時の権限問題を回避するためでもあります。 拡張機能について DevContainer環境では、リモート開発に必要な拡張機能を事前にインストールしておくと作業効率が向上します。特に remote-containers と remote-wsl は、環境切り替えやデバッグ作業で重宝します。 皆さんも、普段使っている拡張機能がある場合は、ここに追加しておくと良いでしょう。 postCreateCommand こちらでGemini CLIのインストールを行っています。 インストール方法に関しては公式リファレンス の情報を参考に行っています。 4.4 環境起動と動作確認 この設定により、チーム全体で一貫したGemini CLI開発環境を構築できます。実際にこの環境を起動して動作確認を行っていきます! 起動手順 Windows環境の場合 : F1 を押してコマンドパレットを開く 「DevContainers: Reopen in Container」を選択 コンテナのビルドが開始されます 4.5 Sandbox環境を使わない設計判断 Gemini CLIには Sandbox という便利機能が目玉の一つとしてあります。こちらは、ファイルに直接変更を加える前にDocker内のSandboxでプレビューとして変更をシミュレートしてくれる便利機能です。 今回は生成をメインに扱うので、そもそもファイル新規作成が目的なため、軽量化とDevContainerの複雑な設定を回避するために使わないと判断しました。 シンプル構成を選択した背景 : 新規生成がメイン : 既存ファイル変更ではなく新規作成が目的 環境構築の簡素化 : DonDやDinDの複雑な設定を回避 軽量化 : 不要な機能を削ることでセットアップを高速化 必要な場合は DooD か DinD (DevContainer内でDockerを使用する)で組み込んで使用できますが、そこまで本格的に使うなら、WSLにNode.jsを直接インストールする方が簡単かもしれませんね。そこはプロジェクトの要件に応じて判断していただければと思います。 5. Gemini CLI基本セットアップ まず初めに Gemini CLIのトラブルシューティングのリンク を載せておきます。 5.1 認証設定 それでは、ターミナルを開いて以下のコマンドを実行してください。 # Google Cloud プロジェクトIDの設定(必須) export GOOGLE_CLOUD_PROJECT=your-project-id # Gemini CLI初回実行 gemini おそらくですが、以下のような画面が出てきて設定が始まります。 選択すると次は認証方式の設定ですね。 初回実行の場合は、認証方式を聞かれます。 Login with Google Gemini API Key (AI Studio) Vertex AI 多くの方は「Login with Google」を選択して、よく見慣れたGoogle認証画面でログインするでしょう。 設定項目をつらつらと答えていけば、認証設定は完了です。 5.2 基本設定ファイル(settings.json) { "sandbox": false, "theme": "GitHub", "model": "gemini-2.0-flash-exp", "contextFileName": [ "GEMINI.md" ] } 次に設定ファイルです。 プロジェクトルートに .gemini ディレクトリを作成 して settings.json ファイルを作成してください。 今回は sandbox を使わないためfalseに設定しています。もしここをtrueにすると、Dockerコマンドが使えないとのことでエラーが発生します。 theme に関しては初回に答えた内容ですね。Gemini CLIのコード部分の装飾に関わります。 model はデフォルトで選択するモデルですね。最後に contextFileName はシステムプロンプトとして認識するファイル名になります。この配列に追加していくと好きなファイルをシステムプロンプトとしてぶち込むことが可能になります。 設定の反映には一度Gemini CLIを抜けて再度入りなおす必要があります。自動反映はしてくれないですね。 動作的にまだ安定していない部分もありますが、その辺はこれからってところですね。 モデル設定のトラブルシューティング モデル設定が無視される場合( GitHub Issue #2205 ): export GEMINI_MODEL="gemini-2.0-flash-exp" gemini --model gemini-2.0-flash-exp 5.3 動作確認 設定が完了したら、適当に質問してみてください。 # 基本的な動作確認 現在のプロジェクト構造を教えてください # ファイル操作の確認 docs/test.md に今日の日付と動作確認結果を記録してください 普段のGeminiとあんまり使用感は変わらないですね。ローディングがGUIと違ってプログラミングチックで見慣れた光景だなって感じです。 正常に動作していれば、質問に対する回答が返ってきて、 docs ディレクトリにファイルが作成されるはずです。これで基本的なセットアップは完了です! 6. GEMINI.mdによるプロジェクト固有設定 6.1 GEMINI.mdの役割と重要性 待望のシステムプロンプトを設定していきましょう!これは先ほどのCLI設定ファイルで指定したファイル名「 GEMINI.md 」をプロジェクトルートに作成すればOKです。 このファイルの面白いところは、 階層構造に応じた柔軟なプロンプト管理 が可能な点です。プロジェクトルートに作成すればプロジェクト全体のシステムプロンプトとして機能し、特定のディレクトリ(例:docs階層)に配置すれば、そのディレクトリ内での作業時のみ有効なシステムプロンプトになります。 これは、実際のコーディング作業で威力を発揮します。ディレクトリごとに異なるシステムプロンプトを作成することで、命名規則やコーディング規約をルールとして定義でき、プロジェクトが変わっても同一品質を担保できるわけです。ただし、作成と管理は相当大変そうですが…この ディレクトリ単位でのプロンプト分割機能はGemini CLI の魅力的な機能ですね。 6.2 実用的なGEMINI.md設定例 では、実践的なプロンプトを設定していきます。普段は300行のプロンプトを使用しているのですが、ファイルサイズも大きいので軽量版を用意しました。以下をプロジェクトルートに作成した GEMINI.md に挿入してください。 # 軽量版 エンジニア向けX投稿自動生成システム ## 🎯 概要 URLのみの入力で、記事品質評価からA/Bテスト用投稿文まで自動生成する包括的システム。 ## 🔄 自動実行プロセス ### Phase 1: 記事分析・品質評価 - web_fetchで記事の完全コンテンツ取得・構造解析 - 技術的正確性評価(技術スタック正確性、実装レベル、対象読者レベル) - 評価結果:A(高品質)/ B(標準)/ C(要注意) - 記事価値・差別化ポイント抽出 ### Phase 2: ハッシュタグ効果分析 - 2-3回のweb_searchで技術分野別調査・6個以内で選定 ### Phase 3: A/Bテスト版投稿文作成(2種類) **タイプA: 効果重視・数値訴求型** - 具体的な数値効果・Before/After型アピール **タイプB: 課題共感・解決提案型** - エンジニアの「あるある」課題→解決策への流れ ### Phase 4: 最適化・検証 - 280バイト制限内最大化・品質評価連動調整 ## 📊 最終出力フォーマット ### 1. 記事品質評価レポート 📋 記事品質評価結果 【総合評価】A / B / C 【技術的正確性】⭐⭐⭐⭐⭐ (5点満点) 【実装レベル】プロトタイプ / 基本実装 / 本格実装 / 企業レベル 【対象読者】初級者 / 中級者 / 上級者 / 専門家 【実用性】⭐⭐⭐⭐⭐ (5点満点) 【主要な技術要素】・【注意事項・制限】 ### 2. A/Bテスト投稿文(2種類) 各タイプごとに以下を含む: - 280バイト以内の最適化投稿文 - バイト数詳細(本文・絵文字・URL・ハッシュタグ・合計) - 特徴・ターゲット ### 3. 投稿タイミング戦略 - 最優先推奨・代替オプション ### 4. 最適化効果サマリー - 技術用語最適化効果・A/Bテスト推奨順位 ## 🔧 実行トリガー URLのみ入力時に自動実行 6.3 システムプロンプトの動作確認 正しくGemini CLIに認識されているか確認しましょう。Gemini CLIを起動した状態で以下のコマンドを入力して表示されれば成功です。 # 現在認識しているシステムプロンプトを表示 /memory show # システムプロンプトを更新したので再読み込み /memory refresh 6.4 実際の動作テスト この状態で以下のプロンプトを入力すれば、自動実行されてdocsディレクトリに結果が出力されるはずです。 https://tech-lab.sios.jp/archives/48061 > @docs/に出力したコンテンツを保存して 一定時間経過後にdocsディレクトリにファイルが作成されていると思います。私の検証環境では以下のような結果が出力されました: 出力例 : URLを分析し、指定されたフォーマットでX(旧Twitter)への投稿案を作成しました。 ### 1. 記事品質評価レポート 📋 **記事品質評価結果** 【総合評価】A 【技術的正確性】⭐⭐⭐⭐⭐ 【実装レベル】本格実装 【対象読者】中級者 【実用性】⭐⭐⭐⭐⭐ 【主要な技術要素】 ・Azure Static Web Apps ・Bicep (Infrastructure as Code) ・Azure CLI ・SWA CLI ・Bash Scripting 【注意事項・制限】 ・提示されているスクリプトはローカル開発用です。 ・デプロイトークンなどの機密情報はGitで管理しないよう注意が必要です。 ### 2. A/Bテスト投稿文(2種類) --- **【タイプA:効果重視・数値訴求型】** **テキスト案** Azure SWAの構築、手作業で時間かかっていませんか?🤔 Bicepを使った自動化で、作業時間を70%も短縮!🚀 この記事のスクリプトをコピペするだけで、ローカル開発環境のデプロイが数分で完了します。 `https://tech-lab.sios.jp/archives/48061` #Azure #Bicep #IaC #DevOps #自動化 **バイト数詳細** ・本文: 1[1]57 ・絵文字: 8 ・URL: 23 ・ハッシュタグ: 41 ・合計: 229 バイト **特徴・ターゲット** 「70%削減」という具体的な数値を提示し、即時的な効果を求めるエンジニアにアピールします[1]。多忙な開発者や、すぐに導入できる解決策を探している層がターゲットです。 --- **【タイプB:課題共感・解決提案型】** **テキスト案** 「またこの手作業か…」Azure SWAのデプロイ、面倒に感じていませんか? Bicepと自動化スクリプトで、その悩みを解決!✨ リソース作成からデプロイまでを完全自動化する具体的な手法を解説。もう手動管理には戻れません。 `https://tech-lab.sios.jp/archives/48061` #Azure #Bicep #StaticWebApps #IaC **バイト数詳細** ・本文: 151 ・絵文字: 4 ・URL: 23 ・ハッシュタグ: 36 ・合計: 214 バイト **特徴・ターゲット** 開発者が共感しやすい「手作業の煩わしさ」という課題から入り、記事がその解決策であることを提示します。手動でのインフラ管理に課題を感じている、より丁寧な解説を求めるエンジニア層に響きやすい投稿です。 --- ### 3. 投稿タイミング戦略 * **最優先推奨:** 平日の業務時間中(10:00〜18:00)。技術情報に関心のあるエンジニアがアクティブな時間帯です。特に昼休み(12:00-13:00)や、業務が一段落する夕方(17:00-18:00)が効果的です。 * **代替オプション:** 平日の夜(21:00〜23:00)。自己学習や情報収集を行うエンジニアにリーチできる可能性があります。 ### 4. 最適化効果サマリー * **技術用語最適化:** 記事の核心技術である「Bicep」「Azure Static Web Apps」「IaC」をハッシュタグに含めることで、これ らの技術に関心を持つターゲット層へのリーチを最大化しました。 * **A/Bテスト推奨順位:** 1. **タイプA**を先に試すことを推奨します。「70%削減」という強いインパクトを持つ数値は、クリック率を高める可能性が最も高いです。 2. 次に、より丁寧な課題解決アプローチである**タイプB**を投稿し、エンゲージメントの違いを比較します。 Sources: [1] 作業時間を70%短縮!BicepによるAzure Static Web Apps自動構築 | (https://tech-lab.sios.jp/archives/48061) この結果が正常に出力されれば、システムプロンプトの設定は完了です! 7. 実践的な運用とチーム活用 7.1 チーム開発での管理手法 今回のシステムは生成に主軸を置いているため、 作成した成果物をGitHub上で体系的に管理できる という大きな利点があります。従来は、Geminiで出力したものをSlackに都度コピー&ペーストで移行していましたが、この方法では過去の生成結果の検索や再利用が困難でした。 Git管理による品質保証プロセス では、プロンプトの品質はプロンプト自体に依存するため、 PRベースでの変更管理 が理想的です。生成されたドキュメントをベースに運用を行い、定期的な改修判断を行うプロセスが重要になります。 GitHubの利点である バージョン管理機能 により、プロンプトの変遷を追跡できるのは非常に価値があります。ソースコードと同様に、プロンプトも技術資産として体系的に管理すべき時代になってきました。LLMのバージョンアップによって相性の良し悪しが変わる世界では、PRでの管理ができることは大変重要ですね。 7.2 GEMINI.mdの実用的活用パターン 6章で解説した ディレクトリ単位でのGEMINI.md配置機能 は、業務フローの最適化において強力な武器となります。作業内容ごとにプロンプトをカスタマイズできることで、業務効率の大幅な向上が期待できます。 具体的な活用例 : プロジェクト構成での使い分け project-root/ ├── GEMINI.md # プロジェクト全体の基本プロンプト ├── docs/ │ └── GEMINI.md # ドキュメント生成専用プロンプト ├── src/ │ └── GEMINI.md # コード生成・レビュー専用プロンプト └── tests/ └── GEMINI.md # テストコード生成専用プロンプト 7.3 A/Bテスト戦略の実装 今回の検証では、プロンプト内部でA/Bテストを実装しましたが、 長期的な効果測定 であればディレクトリ単位での分離がより効果的です。 ディレクトリベースA/Bテスト例 : social-media-posts/ ├── strategy-a/ │ ├── GEMINI.md # 数値重視アプローチ │ └── generated/ # 生成結果保存 └── strategy-b/ ├── GEMINI.md # 課題共感アプローチ └── generated/ # 生成結果保存 この構成により、 期間を区切った戦略比較 や チーム内での並行テスト が可能になります。 7.4 プロンプトの継続的改善 プロンプトエンジニアリングは試行錯誤の連続です。一度の修正で完璧になることは稀なので、 定期的にレビューを重ねてプロンプトを一歩ずつ改善していく必要があります 。(というか一度で完璧なプロンプトって絶対作れないですよね…) 生成結果の品質に課題を感じた際は、どの部分が期待と異なるのかを具体的に特定し、プロンプトの該当箇所を少しずつ調整していくアプローチが必要です。 7.5 組織展開での考慮事項 チーム全体でDevContainer × Gemini CLI環境を導入する際は、以下の点に注意が必要です: 技術的制約事項 Docker環境の組織統一 Google Workspace認証の権限設定 ファイアウォール設定の調整 運用ルールの策定 プロンプト変更権限の明確化 生成コンテンツの品質基準定義 セキュリティガイドラインの整備 特にコーディングに活用するのであれば、今回はそぎ落としたsandboxに関しては必須レベルです。コードはすでに元のものがある前提なので、sandboxなしの場合であればgitコマンドで戻すしか選択肢がなくなってしまいます。 この環境が組織に定着すれば、 AI活用による生産性向上 と 品質の標準化 が同時に実現できるでしょう。皆さんも、ぜひプロジェクトの要件に応じてカスタマイズしてみてください! まとめ 今回は、DevContainer × Gemini CLIを組み合わせた安全で再現可能な開発環境の構築方法について詳しく解説してきました。 この環境構築で実現できたこと は以下の通りです: チーム開発の課題解決 : 「なんで俺のGeminiとお前のGeminiは違うんや!」という環境差異問題を根本的に解決し、誰でも同じ環境を瞬時に再現できるようになりました。DevContainerによるコンテナ化で、新卒や別担当者が参画してもスムーズに開発に参加できる環境が整います。 安全性の確保 : Gemini CLIのサンドボックス機能とDevContainerの隔離環境による二重の安全保障で、AIが暴走してもホスト環境への影響は皆無です。企業レベルでの導入も安心して行えます。 プロジェクト固有のカスタマイズ : GEMINI.mdによるディレクトリ単位でのシステムプロンプト管理により、作業内容に応じた最適化が可能になりました。コーディング、ドキュメント生成、テスト作成など、用途別にプロンプトを使い分けることで業務効率が大幅に向上します。 Git管理によるプロンプト資産化 : プロンプトもソースコードと同様にバージョン管理できるため、PRベースでの品質管理と継続的改善が実現できます。LLMのバージョンアップに対応した管理も可能になりますね。 この環境が組織に定着すれば、 AI活用による生産性向上と品質の標準化 が同時に実現できるでしょう。特に、従来Slackにコピー&ペーストしていた生成結果を体系的に管理できるようになったのは大きな進歩です。 皆さんも、ぜひプロジェクトの要件に応じてカスタマイズしてみてください!定期的にレビューを重ねてプロンプトを一歩ずつ改善していくことで、さらに高度な自動化システムへと発展させていけるはずです。 付録:強化版プロンプト # 強化版 エンジニア向けX投稿自動生成システム ## 🎯 概要 URLのみの入力で、記事品質評価からA/Bテスト用投稿文まで自動生成する包括的システム。技術的正確性の評価と異なるアプローチでの複数投稿文を同時作成。 --- ## 🔄 自動実行プロセス(強化版) ### **Phase 1: 記事分析・品質評価** #### **Step 1-1: 記事内容完全取得** ``` 実行内容: - web_fetchでブログ記事の完全コンテンツ取得 - 記事構造の解析(見出し、コードブロック、図表等) - 文字数・技術密度の測定 ``` #### **Step 1-2: 技術的正確性評価** ``` 評価項目: 【技術スタック正確性】 - 使用技術の最新性(2024-2025年基準) - 技術的実装の適切性 - セキュリティ・ベストプラクティスの考慮 【実装レベル判定】 - プロトタイプ / 基本実装 / 本格実装 / 企業レベル - コードの完成度・品質 - エラーハンドリング・例外処理の有無 【対象読者レベル】 - 初級者(学習中・1-2年目) - 中級者(実務経験3-5年) - 上級者(リーダー・アーキテクト級) - 専門家(特定分野のエキスパート) 評価結果:A(高品質)/ B(標準)/ C(要注意) ``` #### **Step 1-3: 記事価値・差別化ポイント抽出** ``` 分析内容: - 解決する具体的課題 - 既存解決策との違い・優位性 - 実用性・再現性の程度 - 学習コスト・導入難易度 - ビジネス価値・ROI ``` ### **Phase 2: ハッシュタグ効果分析** #### **Step 2-1: 技術分野別ハッシュタグ調査** ``` 検索戦略(2-4回のweb_search実行): 検索1: "[主要技術名] ハッシュタグ 効果的 X Twitter 2025" 例:"NestJS ハッシュタグ 効果的 X Twitter 2025" 検索2: "[技術領域] エンジニア ハッシュタグ トレンド 2025" 例:"Web開発 エンジニア ハッシュタグ トレンド 2025" 検索3: "[解決課題] 自動化 効率化 ハッシュタグ X" 例:"Slack Bot 自動化 効率化 ハッシュタグ X" 検索4: "[関連ツール] [技術スタック] ハッシュタグ 人気" 例:"Google Docs API NotebookLM ハッシュタグ 人気" ``` #### **Step 2-2: ハッシュタグ効果性評価・選定** ``` 評価基準: - トレンド性(2024-2025年の話題性) - 技術特化度(エンジニアの検索意図との一致) - 関連性(記事内容との直接的関連度) - バイト効率(半角英語活用による節約効果) 選定ルール: - 6個以内 - 合計30-40バイト以内 - 半角英語優先(#Azure, #API, #OSS等) ``` ### **Phase 3: A/Bテスト版投稿文作成(3種類)** #### **投稿文タイプ A: 効果重視・数値訴求型** ``` 【特徴】 - 具体的な数値効果を最前面に配置 - Before/After型の劇的改善アピール - 実用性・効率化を強調 【構成テンプレート】 🚀 [劇的効果の数値]で[技術課題]を解決! 📝 [具体的解決手法]で[時間短縮・効率化]を実現 ⚡ 技術スタック: [半角英語技術名] 🔧 [実装詳細・注意点] 詳細→ [URL] #ハッシュタグ 【表現例】 - "30分→3分に短縮!" - "90%の作業削減を実現" - "チーム全体で月40時間節約" ``` #### **投稿文タイプ B: 課題共感・解決提案型** ``` 【特徴】 - エンジニアの「あるある」課題から開始 - 共感を呼ぶ問題提起 - 解決策への自然な流れ 【構成テンプレート】 😰 [多くのエンジニアが抱える課題]で困ってませんか? 💡 [技術手法]を使えば[解決方法]で楽になります ⚡ [具体的技術スタック]で実装 🎯 [期待効果・メリット] 解決方法→ [URL] #ハッシュタグ 【表現例】 - "まだ手動でSlack通知作ってるの?" - "Google Docsの更新確認、毎回開いてチェックしてる?" - "同じようなレポート作成に毎週2時間かけてませんか?" ``` #### **投稿文タイプ C: 技術トレンド・学習促進型** ``` 【特徴】 - 最新技術動向・トレンドに言及 - スキルアップ・学習意欲を刺激 - 将来性・キャリア価値をアピール 【構成テンプレート】 🔥 2025年注目の[技術分野]トレンド 📚 [技術手法・ツール]の実装方法を解説 ⭐ [学習メリット・キャリア価値] 🚀 [実際の活用場面・ビジネス価値] 詳細ガイド→ [URL] #ハッシュタグ 【表現例】 - "2025年のAI活用はここまで進化" - "NotebookLMとSlack連携がゲームチェンジャー" - "この技術知ってるかで来年の評価が変わる" ``` ### **Phase 4: 最適化・検証** #### **Step 4-1: バイト数計算・最適化** ``` 各投稿文について: - 全角・半角混在でのバイト数正確計算 - 280バイト制限内での情報量最大化 - 技術用語の半角化による節約効果測定 - URL(23バイト)・絵文字・ハッシュタグ含めた総計算 ``` #### **Step 4-2: 品質評価連動調整** ``` 記事品質評価結果に基づく調整: 【A評価(高品質)記事】 - より技術的権威性を強調 - 上級者向けの詳細情報を含める - 業界標準・ベストプラクティスをアピール 【B評価(標準)記事】 - 実用性・学習効果を前面に - 初中級者向けの親しみやすい表現 - 「試してみる価値あり」感を演出 【C評価(要注意)記事】 - 学習・実験的価値をアピール - 制限事項・注意点を適切に含める - 過度な効果アピールを避ける ``` --- ## 📊 最終出力フォーマット ### **1. 記事品質評価レポート** ``` 📋 記事品質評価結果 【総合評価】A / B / C 【技術的正確性】⭐⭐⭐⭐⭐ (5点満点) 【実装レベル】プロトタイプ / 基本実装 / 本格実装 / 企業レベル 【対象読者】初級者 / 中級者 / 上級者 / 専門家 【実用性】⭐⭐⭐⭐⭐ (5点満点) 【主要な技術要素】 - [抽出された技術スタック] - [解決する課題] - [提供価値・効果] 【注意事項・制限】 - [記事内で言及されている制限事項] - [実装時の注意点] ``` ### **2. A/Bテスト投稿文(3種類)** #### **🚀 タイプA: 効果重視型** ``` [280バイト以内の最適化投稿文A] 📊 バイト数詳細: - 本文: [X]バイト - 絵文字: [X]バイト - URL: 23バイト - ハッシュタグ: [X]バイト - 合計: [X]/280バイト 💡 特徴: 数値効果重視、劇的改善アピール 🎯 ターゲット: 効率化重視のエンジニア ``` #### **💡 タイプB: 課題共感型** ``` [280バイト以内の最適化投稿文B] 📊 バイト数詳細: - 本文: [X]バイト - 絵文字: [X]バイト - URL: 23バイト - ハッシュタグ: [X]バイト - 合計: [X]/280バイト 💡 特徴: 共感重視、問題解決提案 🎯 ターゲット: 課題を抱えるエンジニア ``` #### **📚 タイプC: 学習促進型** ``` [280バイト以内の最適化投稿文C] 📊 バイト数詳細: - 本文: [X]バイト - 絵文字: [X]バイト - URL: 23バイト - ハッシュタグ: [X]バイト - 合計: [X]/280バイト 💡 特徴: トレンド重視、スキルアップ促進 🎯 ターゲット: 学習意欲の高いエンジニア ``` ### **3. 投稿タイミング戦略** #### **🥇 最優先推奨(記事品質・内容連動)** ``` 推奨投稿タイプ: [A/B/C] 推奨日時: [具体的日時] 理由: [記事品質・対象読者・技術分野に基づく根拠] 期待効果: [エンゲージメント予測] ``` #### **🥈 代替オプション** ``` 代替投稿タイプ: [A/B/C] 代替日時: [具体的日時] 理由: [異なるアプローチでの訴求理由] 期待効果: [別ターゲット層への効果] ``` #### **🥉 実験的選択肢** ``` 実験投稿タイプ: [A/B/C] 実験日時: [具体的日時] 理由: [新しいパターンのテスト目的] 期待効果: [データ収集・学習効果] ``` ### **4. 最適化効果サマリー** ``` 💡 技術用語最適化効果: - 半角英語化: [具体例] → [X]バイト節約 - 半角数値化: [具体例] → [X]バイト節約 - 半角記号化: [具体例] → [X]バイト節約 - 総節約効果: [X]バイト 🎯 A/Bテスト推奨順位: 1位: タイプ[A/B/C] - [選定理由] 2位: タイプ[A/B/C] - [選定理由] 3位: タイプ[A/B/C] - [選定理由] 📈 期待エンゲージメント予測: - タイプA: [予測値・根拠] - タイプB: [予測値・根拠] - タイプC: [予測値・根拠] ``` --- ## 🔧 実行トリガー ### **自動実行条件** ``` 以下の場合に自動的にこの強化版プロセスを実行: 1. URLのみが入力された場合 2. "記事品質評価付きで"等の指示がある場合 3. "A/Bテスト版で"等の指示がある場合 4. "完全版で"等の包括的分析要求がある場合 ``` ### **プロンプト例** ``` 【基本実行】 https://tech-blog.example.com/article-url 【明示的実行】 以下の記事で記事品質評価とA/Bテスト版投稿文を作成してください: https://tech-blog.example.com/article-url 【カスタム実行】 記事品質評価A評価想定、上級者向けでA/Bテスト版作成: https://tech-blog.example.com/article-url ``` --- ## 📋 品質保証チェックリスト(強化版) ### **記事品質評価の正確性** - [ ] 技術的事実の正確性検証 - [ ] 実装レベルの適切な判定 - [ ] 対象読者レベルの妥当性確認 - [ ] 制限事項・注意点の適切な反映 ### **A/Bテスト版の差別化** - [ ] 3つのアプローチが明確に異なる - [ ] 各タイプのターゲット読者が明確 - [ ] バイト数最適化が各版で実現 - [ ] 品質評価結果が適切に反映 ### **全体的最適化** - [ ] ハッシュタグ効果分析の妥当性 - [ ] 投稿タイミング戦略の論理性 - [ ] バイト数計算の正確性 - [ ] エンゲージメント予測の根拠明確性 --- この強化版システムにより、URLひとつで記事の技術的品質から複数の投稿戦略まで包括的に分析・生成できます! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post DevContainerでGemini CLIセットアップ!チーム開発環境差異を解決する実践ガイド first appeared on SIOS Tech. Lab .
アバター
こんにちは、伊藤です。 パスワードハッシュを使用したシステムを開発する際、プログラムが生成したパスワードハッシュが正しいかどうかテストすることがあります。この記事では、そのような場面で役立つ openssl コマンドを使い、手軽にパスワードハッシュを生成・確認する方法を紹介します。 検証環境 検証で使用したOSとOpenSSLのバージョンは以下のとおりです。 OS Rocky Linux OS 9.2 OpenSSLのバージョン $ openssl version OpenSSL 3.0.7 1 Nov 2022 (Library: OpenSSL 3.0.7 1 Nov 2022) パスワードハッシュの出力 使用できるパスワードハッシュアルゴリズムを確認します。 openssl コマンドをそのまま入力すると、helpが表示されます。 $ openssl help: Standard commands asn1parse ca ciphers cmp cms crl crl2pkcs7 dgst dhparam dsa dsaparam ec ecparam enc engine errstr fipsinstall gendsa genpkey genrsa help info kdf list mac nseq ocsp passwd pkcs12 pkcs7 pkcs8 pkey pkeyparam pkeyutl prime rand rehash req rsa rsautl s_client s_server s_time sess_id smime speed spkac srp storeutl ts verify version x509 Message Digest commands (see the `dgst' command for more details) blake2b512 blake2s256 md2 md4 md5 rmd160 sha1 sha224 sha256 sha3-224 sha3-256 sha3-384 sha3-512 sha384 sha512 sha512-224 sha512-256 shake128 shake256 sm3 Cipher commands (see the `enc' command for more details) aes-128-cbc aes-128-ecb aes-192-cbc aes-192-ecb aes-256-cbc aes-256-ecb aria-128-cbc aria-128-cfb aria-128-cfb1 aria-128-cfb8 aria-128-ctr aria-128-ecb aria-128-ofb aria-192-cbc aria-192-cfb aria-192-cfb1 aria-192-cfb8 aria-192-ctr aria-192-ecb aria-192-ofb aria-256-cbc aria-256-cfb aria-256-cfb1 aria-256-cfb8 aria-256-ctr aria-256-ecb aria-256-ofb base64 bf bf-cbc bf-cfb bf-ecb bf-ofb camellia-128-cbc camellia-128-ecb camellia-192-cbc camellia-192-ecb camellia-256-cbc camellia-256-ecb cast cast-cbc cast5-cbc cast5-cfb cast5-ecb cast5-ofb des des-cbc des-cfb des-ecb des-ede des-ede-cbc des-ede-cfb des-ede-ofb des-ede3 des-ede3-cbc des-ede3-cfb des-ede3-ofb des-ofb des3 desx idea idea-cbc idea-cfb idea-ecb idea-ofb rc2 rc2-40-cbc rc2-64-cbc rc2-cbc rc2-cfb rc2-ecb rc2-ofb rc4 rc4-40 rc5 rc5-cbc rc5-cfb rc5-ecb rc5-ofb seed seed-cbc seed-cfb seed-ecb seed-ofb パスワードハッシュアルゴリズムとして、 Message Digest commands に表示されているアルゴリズムが使用できます。 例:MD5 $ echo 'password' | openssl md5 MD5(stdin)= 286755fad04869ca523320acce0dc6a4 ※パスワードハッシュのみを表示したい場合 $ echo 'password' | openssl md5 | awk '{print $2}' 286755fad04869ca523320acce0dc6a4 ソルトを使用するパスワードハッシュの出力 ソルトを使用するパスワードハッシュアルゴリズムを使用したい場合は openssl passwd コマンドを使用します。 openssl passwd で使用できるパスワードハッシュアルゴリズムを確認します。 Cryptographic options の部分が使用できるパスワードハッシュアルゴリズムです。 $ openssl passwd --help Usage: passwd [options] [password] General options: -help Display this summary Input options: -in infile Read passwords from file -noverify Never verify when reading password from terminal -stdin Read passwords from stdin Output options: -quiet No warnings -table Format output as table -reverse Switch table columns Cryptographic options: -salt val Use provided salt -6 SHA512-based password algorithm -5 SHA256-based password algorithm -apr1 MD5-based password algorithm, Apache variant -1 MD5-based password algorithm -aixmd5 AIX MD5-based password algorithm Random state options: -rand val Load the given file(s) into the random number generator -writerand outfile Write random data to the specified file Provider options: -provider-path val Provider load path (must be before 'provider' argument if required) -provider val Provider to load (can be specified multiple times) -propquery val Property query used when fetching algorithms Parameters: password Password text to digest (optional) パスワードハッシュアルゴリズムを使用してみます。 例:MD5Crypt $ openssl passwd -1 -salt 'salt' 'password' $1$salt$qJH7.N4xYta3aEG/dfqo/0 まとめ 今回は openssl コマンドを使い、パスワードハッシュを生成する方法を紹介しました。 開発中のテストや検証で、ぜひ活用してみてください。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post opensslコマンドでパスワードハッシュを出力する first appeared on SIOS Tech. Lab .
アバター