前回の投稿はコチラ:【第1回】AIエージェントと一緒にクラウド日記アプリ開発してみる:環境構築編【AWS Amplify Gen 2 × Cursor × Claude Code】
なお、この記事はAI・AWSを活用したプログラミング初心者以上を対象としており、AWSアカウント作成や基本的なPC操作については解説を省略している箇所もあることをご了承ください。もし実際にご自身のPC・クラウド環境で構築される場合は、AWS利用での思わぬ課金や、公開(デプロイ)による外部からの攻撃リスクについて十分ご注意ください。
第2回の概要
今回は、前回構築したAWSバックエンドと、Next.jsプロジェクトを連携させるところからスタートします。
ここからの手順はソースの確認の意味も含めて一部、Geminiを使用しながらAIエージェントによるコーディングではなく、手作業でコーディングを行っています。ClaudeCodeでももちろん実装は行えます。
使用するツール
- AWS Amplify Gen 2: TypeScriptでバックエンドインフラを定義できるフレームワーク。今回AWSは新規登録者向けの無料利用枠を利用して開発を進めていきます。
- Cursor: AIによるコード補完・編集機能がネイティブ統合された、VS Codeベースのエディタ。
- Claude Code: 2025年に公開された、ターミナル上で動作するAIエージェント。コードの記述、テスト、デバッグを実行できる。今回Claude Pro(有料版)を年額契約しています。年額契約は月額契約より若干お得なのでおすすめです。(2026年4月現在)
- GitHub: AI(CursorやClaude Code)が生成したコードのバージョン管理(履歴保存)や、バックエンド(AWS Amplify)への自動デプロイの起点として活用。
- Gemini: Googleが提供するAI。Cursor/Claude Codeが生成したコードのセカンドオピニオン、「伴走型アドバイザー」として活用。
1. Amplifyライブラリの導入と初期設定
ステップ1:必要なライブラリのインストール
フロントエンドでAWSサービスを操作するためのコアライブラリと、ログイン画面を作れる公式UIキットをインストールします。画面を作るならデザインを考えて……という工程をイメージしてしり込みしますが、このキットなら「とりあえず」でいい感じの画面が作れます。
npm install aws-amplify @aws-amplify/ui-react
ステップ2:Amplifyのクライアントサイド設定
src/app/layout.tsx



以下のように変更します。(このコードはGeminiで出力しています)
"use client"; // Amplifyの初期化にはブラウザの機能が必要なため必須です
import { Amplify } from "aws-amplify";
import outputs from "../amplify_outputs.json";
import "@aws-amplify/ui-react/styles.css"; // ログイン画面の標準デザイン
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
// AWSバックエンドとの接続設定
Amplify.configure(outputs);
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ja">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased min-h-screen flex flex-col`}
>
{children}
</body>
</html>
);
}
Next.jsのApp Routerでは、Amplifyの設定を layout.tsx の最上部で行うのがスムーズなようです。"use client" を忘れずにつけることで、ブラウザ側で正しくバックエンドとの通信が開始されます。
次に、amplify-outputs.d.ts という 「型定義ファイル(Declaration file)」 を作成し、以下のように宣言します。
declare module "*/amplify_outputs.json" {
const value: any;
export default value;
}
TypeScriptにJSONを読み込めるように許可を出している、というような内容のようです。
2. 認証機能(ログイン画面)の実装
通常、ログイン・会員登録・パスワードリセットの機能を自作すると数日かかります(以前、JavaとSpring Bootでソーシャル認証機能のあるWEBサービスを開発する自習をしていたとき、認証機能でハマってとても時間がかかってしまいました。。)。ですが、Amplify UIの Authenticator コンポーネントを使えば、数行で認証画面が完成します。
ステップ1:ログイン画面の実装
トップページ(page.tsx)を書き換えます。一度中身をすべて消して、以下を貼り付けます。(このコードはGeminiで出力しています)
"use client";
import { Authenticator } from "@aws-amplify/ui-react";
export default function Home() {
return (
<Authenticator>
{({ signOut, user }) => (
<main className="flex min-h-screen flex-col items-center justify-center p-24 bg-gray-50">
<div className="bg-white p-10 rounded-xl shadow-lg text-center max-w-md w-full">
<h1 className="text-3xl font-bold mb-6 text-gray-800">
AI Diary App
</h1>
<p className="text-gray-600 mb-8">
こんにちは、<span className="font-semibold">{user?.signInDetails?.loginId}</span> さん!<br />
AWS環境との接続に成功しました。
</p>
<button
onClick={signOut}
className="w-full bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-lg transition duration-200"
>
ログアウト
</button>
</div>
</main>
)}
</Authenticator>
);
} コードを保存したら、ブラウザで http://localhost:3000 を開きます(もし止まっていたら、ターミナルで npm run dev を打って起動してください)。
ログイン画面が表示されました。

新規登録[Create Account]をしてみます。

指定したメールアドレスに認証コードが届きます。


登録完了しました。
ここまでで、ブラウザで http://localhost:3000 を開くと、すでにAWS Cognitoと連携したログイン画面が表示されるようになりました。
ステップ2:認証UI画面の日本語化
AuthenticatorのUI部品は英語で表示されていますが、日本語化することも可能です。
Amplifyの設定を行っている layout.tsx を開き、以下のコードを追加します。(Geminiによるコード生成)
"use client";
import { Amplify } from "aws-amplify";
import { I18n } from "aws-amplify/utils"; // 追加
import { translations } from "@aws-amplify/ui-react"; // 追加
import outputs from "../amplify_outputs.json";
import "@aws-amplify/ui-react/styles.css";
// ...(他のインポート)
// 日本語リソースをセット
I18n.putVocabularies(translations);
I18n.setLanguage("ja");
Amplify.configure(outputs);
// ...(以降のコード)

ここまでで、以下を実装することができました。
- 認証基盤: AWS Cognitoによるセキュアなユーザー管理。
- 自動ログイン制御: 未ログインユーザーを自動でログイン画面へリダイレクト。
- 接続の自動化:
amplify_outputs.jsonを読み込むだけで、APIエンドポイントやAuth設定がすべて完了。
3. データ操作機能(CRUD)とリアルタイム同期の実装
次は、認証バックエンドに定義したデータモデル(Schema)を使い、フロントエンドから実際にデータを保存し、リアルタイムに画面を更新する仕組みを作ります。
ステップ1:データ操作機能(CRUD)の実装
page.tsx を以下の通り変更します。(Geminiで生成)※今回は「C(作成)」と「D(削除)」、そして「R(読み取り)」のみとなります。
"use client";
import { useState, useEffect } from "react";
import { Authenticator } from "@aws-amplify/ui-react";
import { generateClient } from "aws-amplify/data";
import type { Schema } from "../amplify/data/resource"; // パスを ../ に修正
const client = generateClient<Schema>();
export default function Home() {
// 1. モデル名に合わせて Diary に変更
const [diaries, setDiaries] = useState<Array<Schema["Diary"]["type"]>>([]);
useEffect(() => {
// 2. client.models.Diary を使用
const sub = client.models.Diary.observeQuery().subscribe({
next: ({ items }) => setDiaries([...items]),
});
return () => sub.unsubscribe();
}, []);
async function createDiary() {
const title = window.prompt("日記のタイトルを入力してください");
const content = window.prompt("内容を入力してください");
if (title && content) {
// 3. 設計図で定義した title, content, date に合わせて保存
await client.models.Diary.create({
title: title,
content: content,
date: new Date().toISOString().split('T')[0], // YYYY-MM-DD 形式
});
}
}
function deleteDiary(id: string) {
client.models.Diary.delete({ id });
}
return (
<Authenticator>
{({ signOut, user }) => (
<main className="flex min-h-screen flex-col items-center p-8 bg-gray-50">
<div className="bg-white p-8 rounded-xl shadow-lg w-full max-w-lg">
<h1 className="text-3xl font-bold mb-6 text-gray-800 text-center">AI Diary</h1>
<button
onClick={createDiary}
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg mb-8 transition shadow-md"
>
+ 新しい日記を書く
</button>
<ul className="space-y-4">
{diaries.map((diary) => (
<li key={diary.id} className="p-4 border border-gray-100 rounded-lg bg-gray-50 flex justify-between items-center group">
<div>
<h2 className="font-bold text-gray-900">{diary.title}</h2>
<p className="text-gray-700">{diary.content}</p>
<small className="text-gray-400">{diary.date}</small>
</div>
<button onClick={() => deleteDiary(diary.id)} className="text-red-300 hover:text-red-500 opacity-0 group-hover:opacity-100 transition">
削除
</button>
</li>
))}
</ul>
<div className="mt-12 pt-6 border-t text-center">
<button onClick={signOut} className="text-gray-400 hover:text-red-500 text-sm">
ログアウト
</button>
</div>
</div>
</main>
)}
</Authenticator>
);
}実装のポイント解説
① 型安全なデータの定義とクライアント
import type { Schema } from "../amplify/data/resource";
const client = generateClient<Schema>();
- 詳細:
resource.tsで定義したDiaryモデルの構造をフロントエンドにインポートしています。 - メリット: 入力時に定義にない項目を入力しようとするとエディタがエラーを出してくれます。これで「タイポ(打ち間違い)」によるバグが全滅します。
② observeQuery によるリアルタイム同期
通常の list() メソッドではなく、observeQuery() が使われています。
- 動作: ページ読み込み時にデータを取得するだけでなく、データベースに変更があった瞬間にブラウザに通知が飛び、自動的に表示が更新されます。リロード不要でチャットアプリのような滑らかな動きになります。
③ Authenticator による認可(Authorization)の制御
resource.ts で .authorization((allow) => [allow.owner()]) と設定したことにより、「自分が書いた日記は自分にしか見えない」 セキュリティが自動でかかっています。
④ データの作成(Create)と削除(Delete)
複雑なSQL文やURL(/api/diaries など)を一切意識せず、ローカルの関数を呼ぶ感覚でクラウドのDBを操作できます。
4. 動作確認とクラウドへのデプロイ
ステップ1:ローカルでの動作確認

ブラウザで「日記を書く」ボタンを押し、適当な文字を入力してOKを押します。


画面に即座に表示されます。

カーソルを合わせて「削除」をクリックすれば削除も可能です。


ステップ2:Claude CodeによるGitHubへのアップロード
ターミナルからClaude Codeを使ってGit管理を開始し、GitHubへプッシュします。git config(git初期設定)が完了していない状態の場合も、Claudeが設定を手伝ってくれます。
詳しい方法については、GitHubの公式ドキュメントをご参照ください。
git init/git add ./git commitを実行。git config(メアド・名前)の設定もClaudeに任せられます。






GitHubへコードを送る際、『Git Credential Manager』による認可を行います。













ステップ3:アクセス制限の設定
Basic認証(パスワードによる認証)で、予期せぬアクセスを防ぎます。
AWSでデプロイすることにより、URLを知っている人なら誰でもアクセス可能な状態となっています。予期せぬアクセスで請求が増大することがないように、Basic認証で簡単にパスワードによるアクセス制御をしていきます。
ちなみに、WAF(ファイアウォール)によりIPアドレスでのアクセス制御も可能ですが、今回はBasic認証で簡易的に行いました。Basic認証も完全ではないので、使い終わったらアプリ自体を削除するか、ブランチ接続解除する必要があると思います。ブランチ接続解除については後述します。
このように、日記アプリへのアクセスにユーザー名とパスワードが要求されるようになります。


5.トラブルシューティング:ビルドエラーと認証エラーの解消
ステップ1:ビルドエラー(package-lock.jsonの不整合)
Amplifyでデプロイを行う際、package.json と package-lock.json の内容が食い違っているとエラーが発生します。エラー内容も、AWSに記載のエラーのスタックトレースをGeminiに貼り付けて相談して解明しました。(エラー内容を見ると、なんとなく対処方法が分かるようにもなっていますね)
エラーログ例:
npm error Missing: fast-xml-parser@5.2.5 from lock file
...
npm error Clean install a project
npm error npm ci

対応方法: ローカルで npm install を行い、package-lock.json を最新化して再度GitHubにプッシュします。



ステップ2:Basic認証の動作
動作するようになりました。Basic認証もアクセス直後に表示されています。






ステップ3: 認証情報の迷子(NoSignedUser)エラー
デプロイ後、投稿したデータが画面に出てこない問題が発生。デベロッパーツール(F12)を確認するとエラーが出ていました。
NoSignedUser: No current userデータを確認すると入力したはずの内容は存在しました。

原因と対策: Geminiに現象を相談したところ、ページを開いた瞬間にデータ取得が走り、ログイン処理と競合している可能性があるということで、Claudeに修正を依頼しました。
「page.tsxの中身を見て。Authenticatorの中で、ユーザーが確実にログインしてからデータの監視(observeQuery)が始まるようになっているか確認して。修正が必要なら直してpushして。」


修正のキモは、「認証されるまで、データの蛇口(observeQuery)を開かない」構造にすること。DiaryApp という部品に切り分け、Authenticator がログインを確認した後に表示されるようにしました。


6.セキュリティとコスト管理
ステップ1:ブランチ接続解除による公開停止
確認作業を終える際、不要なアクセス遮断とコスト防止のためにホスティングを動的に停止します。Amplifyコンソールの「ホスティング > ブランチ設定」より、main ブランチの接続解除(Disconnect)を選択します。
これにより、リソース(DynamoDBやCognito)を保持したまま、フロントエンドの公開のみを停止できます。



7.画像投稿機能の実装(S3連携 + クライアント圧縮)
日記への画像添付機能を実装します。
ステップ1:画像リサイズの設計
高解像度の写真をそのまま上げると非効率なため、browser-image-compression ライブラリを使用してブラウザ側でリサイズ(最大1MB以下 / 横幅1024px)を行います。

(Gemini生成の構成図)
ステップ2:Storageリソース(S3)の定義
Claude Code に以下のように指示します。
「amplify/storage/resource.ts を作成して。バケット名は diary-photos にして、ログインユーザー(auth)が自分のファイルを「アップロード・表示・削除」できるように権限設定(allow.owner)を追加して。最後に amplify/backend.ts でこのストレージを有効にして。」






エージェントが自律的に調査し、「Storageの場合は allow.entity('identity') と書くのが正解だ」と気づき、最適なコードを作成してくれました。
ステップ3:画像リサイズとアップロードのフロントエンド実装
Claude Code に以下のプロンプトを投げます。
「フロントエンドの実装をして。browser-image-compression をインストールして。page.tsx を修正して、日記作成時に画像を選択できるようにして。画像はアップロード前に『最大1MB / 横幅1024px』にリサイズし、Amplifyの uploadData で S3(diary-photos)に保存して。保存した imageKey を Diary モデルに登録し、日記一覧でその画像を表示できるようにして(getUrl を使用)。」







エージェントはライブラリのインストールから型チェック(npx tsc --noEmit)まで自律的に実行します。

8.最終確認:サンドボックスと実働テスト
ステップ1:サンドボックスでの確認
ターミナルで npx ampx sandbox を実行し、AWS上にテスト用のS3バケット等を作成します。


次に npm run dev でローカルサーバーを起動します。






ステップ2:画像投稿とリサイズ効果の検証
画像を投稿し、正しく表示されるか確認します。



次に、デベロッパーツールの「Network」タブで Content-Length(ファイルサイズ)を確認します。


圧縮効果の検証結果
| 項目 | ファイルサイズ | 値取得方法 |
|---|---|---|
| 元のダミー画像 | 5,018 KB | エクスプローラー表示の値 |
| S3送信時のサイズ | 576 KB | content-lengthの値 |
削減率:約 88.5% カットできています。
まとめと次回
第2回では、フロントエンドの認証画面実装、投稿画面と画像投稿機能実装までを完了させました。
今回、Claudeが単なる「作業代行」ではなく、「技術アドバイザー」として動いてくれました。外部ライブラリ導入の影響確認も自律的に実行してくれるのは心強いです。
また、sandbox のおかげで「ローカルで動いたけどAWSに載せたら動かない」という状況を避けられました。ここまでの総作業時間は約2日間程度ですが、その半分は概要学習やコマンドの確認時間であり、開発スピードはこれまでとは桁違いです。本当に驚きです。
調査などもAI(Gemini)と相談しながら行いました。基本的にとてもスムーズにできました
一方で注意が必要だと思うのは、AIは「もっともらしい嘘」(ハルシネーション)をつくことがあり、記事に書いた内容についても後から自分で調べ直してみると「ん?違うかも?」と感じた部分もあります。特に、私(インプットする側)がAIに対して「○○は▲▲である。これは正しい?」と聞いたときに「その通りです!」と答えた内容が違っているということが何度かあり、人間が監査する意識が必要だと感じました。
第3回: 次は、より完成度を高めるべく、完成したコードのコードレビュー、テスト、修正とプルリクエストに挑戦していきたいと思います。
























