<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
	>

<channel>
	<title>開発技術 | みんたく</title>
	<atom:link href="https://mintaku-blog.net/category/develop/feed/" rel="self" type="application/rss+xml" />
	<link>https://mintaku-blog.net</link>
	<description>みんたくの技術ブログ</description>
	<lastBuildDate>Sun, 11 Jan 2026 08:06:41 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.0.11</generator>

<image>
	<url>https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2018/06/cropped-ipad-820272_640.jpg?fit=32%2C32&#038;ssl=1</url>
	<title>開発技術 | みんたく</title>
	<link>https://mintaku-blog.net</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">144480658</site>	<item>
		<title>個人開発のWebサービスがSQLインジェクション攻撃を受けた話と対策まとめ</title>
		<link>https://mintaku-blog.net/attack-sql-injection/</link>
					<comments>https://mintaku-blog.net/attack-sql-injection/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Sun, 11 Jan 2026 08:06:41 +0000</pubDate>
				<category><![CDATA[まとめ系]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2632</guid>

					<description><![CDATA[<p>個人開発で運営しているWebサービスが攻撃を受けたので、その内容と対策をまとめました。 攻撃を受けた経緯 ある日、サーバーのログを確認していたところ、不審な …</p>
The post <a href="https://mintaku-blog.net/attack-sql-injection/">個人開発のWebサービスがSQLインジェクション攻撃を受けた話と対策まとめ</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>個人開発で運営しているWebサービスが攻撃を受けたので、その内容と対策をまとめました。</p>
<h2>攻撃を受けた経緯</h2>
<p>ある日、サーバーのログを確認していたところ、不審なリクエストを発見しました。</p>
<p>お問い合わせフォームの「姓」フィールドに、以下のような文字列が入力されていたのです。</p>
<p><code></code><code><br />
-1" OR 5*5=25 --<br />
</code><code></code></p>
<p>これはSQLインジェクション攻撃と呼ばれるもので、データベースを不正に操作しようとする攻撃手法です。</p>
<h2>SQLインジェクションとは</h2>
<p>SQLインジェクションは、入力フィールドに悪意のあるSQL文を注入して、データベースを操作しようとする攻撃です。</p>
<p>例えば、ログイン処理で以下のようなSQLを使っていたとします。</p>
<p><code></code><code>sql<br />
SELECT * FROM users WHERE name = '入力値'<br />
</code><code></code></p>
<p>ここに <code>' OR '1'='1</code> のような文字列を入力されると、</p>
<p><code></code><code>sql<br />
SELECT * FROM users WHERE name = '' OR '1'='1'<br />
</code><code></code></p>
<p>となり、条件が常にTRUEになってしまいます。これにより、認証をバイパスされたり、データを不正に取得されたりする可能性があります。</p>
<p>今回の攻撃で使われた <code>-1" OR 5*5=25 --</code> も同様の原理で、<code>5*5=25</code> は常にTRUEになるため、データベースの挙動を変えようとしていたものと思われます。</p>
<h2>攻撃者の情報を分析してみた</h2>
<p>ログに残っていた情報から、攻撃者について分析してみました。</p>
<p>| 項目 | 内容 |<br />
|&#8212;&#8212;|&#8212;&#8212;|<br />
| IPアドレス | VPNプロバイダーの帯域 |<br />
| ブラウザ | Chrome（Windows） |<br />
| メールアドレス | <code>sample@email.tst</code>（明らかにダミー） |<br />
| 入力パターン | 最小限の値 + 攻撃コード |</p>
<h3>攻撃の特徴から分かったこと</h3>
<p>いくつかの特徴から、この攻撃について考察してみました。</p>
<p>1. 汎用的なペイロードが使われていた</p>
<p>使われていた攻撃コードは非常に一般的なもので、特定のサービスを狙ったものではありませんでした。サイトのDB構造を知っているような痕跡もなく、「とりあえず試してみた」という印象です。</p>
<p>2. 入力が機械的だった</p>
<p>名前欄に「e」、メールに「sample@email.tst」など、明らかに自動化ツールで生成したような値が使われていました。SQLMap等の脆弱性スキャンツールの特徴に似ています。</p>
<p>3. VPN経由でアクセスしていた</p>
<p>IPアドレスを調べると、VPNプロバイダーがよく使用する帯域でした。身元を隠しながら多くのサイトをスキャンする際の典型的な手法です。</p>
<h2>結論：無差別スキャンの可能性が高い</h2>
<p>これらの特徴から、特定のサービスを狙った標的型攻撃ではなく、脆弱なサイトを探すボットによる無差別スキャンだったと判断しました。</p>
<p>個人開発の小規模なサービスでも、こういった攻撃は普通に来るんだなと実感しました。</p>
<h2>実施した対策</h2>
<p>今回の攻撃を受けて、以下の対策を実施しました。</p>
<h3>1. IPブラックリストの実装</h3>
<p>攻撃元のIPアドレスを即座にブロックする仕組みを追加しました。</p>
<p><code></code><code>typescript<br />
// IPブラックリスト<br />
const IP_BLACKLIST: Set&lt;string&gt; = new Set([<br />
'178.16.55.xxx', // 攻撃元IP<br />
])</p>
<p>export function checkIpBlacklist(event: H3Event): void {<br />
const ip = getClientIp(event)</p>
<p>if (IP_BLACKLIST.has(ip)) {<br />
console.log(</code>[BLOCKED] Blacklisted IP: ${ip}<code>)<br />
throw createError({<br />
statusCode: 403,<br />
message: 'Access denied'<br />
})<br />
}<br />
}<br />
</code><code></code></p>
<p>シンプルな実装ですが、同じIPからの継続的な攻撃には効果的です。</p>
<h3>2. 既存の対策が機能しているか確認</h3>
<p>改めて既存のセキュリティ対策を確認しました。</p>
<p>| 対策 | 状態 | 説明 |<br />
|&#8212;&#8212;|&#8212;&#8212;|&#8212;&#8212;|<br />
| パラメータ化クエリ | <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> | ORMを使用しており、SQLインジェクション対策済み |<br />
| レート制限 | <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> | 1分間に数回までに制限 |<br />
| reCAPTCHA v3 | <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> | ボット判定を実施 |<br />
| 入力長制限 | <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> | 追加で実装 |</p>
<p>重要なのは、パラメータ化クエリを使用していたため、今回の攻撃は実際には成功していなかったという点です。</p>
<p>Supabaseのクライアントライブラリを使っていたので、入力値は自動的にエスケープされており、SQLインジェクションは成立しませんでした。</p>
<p><code></code><code>typescript<br />
// Supabaseはパラメータ化クエリを使用するため安全<br />
const { error } = await client<br />
.from('inquiries')<br />
.insert({<br />
last_name, // 値は安全にエスケープされる<br />
email,<br />
message,<br />
})<br />
</code><code></code></p>
<h3>3. 入力値の長さ制限を追加</h3>
<p>説明欄などに大量のテキストを送りつけられるのを防ぐため、文字数制限を追加しました。</p>
<p><code></code><code>typescript<br />
const MAX_DESCRIPTION_LENGTH = 1000</p>
<p>if (description &amp;&amp; description.length &gt; MAX_DESCRIPTION_LENGTH) {<br />
throw createError({<br />
statusCode: 400,<br />
message: </code>説明は${MAX_DESCRIPTION_LENGTH}文字以内で入力してください<code><br />
})<br />
}<br />
</code><code></code></p>
<h2>reCAPTCHA v3がボットを検出できる仕組み</h2>
<p>せっかくなので、reCAPTCHA v3がどうやってボットを検出しているのかも調べてみました。</p>
<p>v3は「私はロボットではありません」のチェックボックスがなく、ユーザーの**行動パターン**を分析してスコア（0.0〜1.0）を算出します。</p>
<p>| 分析要素 | 人間の特徴 | ボットの特徴 |<br />
|&#8212;&#8212;&#8212;-|&#8212;&#8212;&#8212;&#8212;|&#8212;&#8212;&#8212;&#8212;&#8211;|<br />
| マウスの動き | 曲線的、不規則 | 直線的、または動きなし |<br />
| キー入力 | タイミングにばらつき | 均一で高速 |<br />
| ページ滞在時間 | 入力に時間がかかる | 瞬時に送信 |<br />
| スクロール | 自然な速度変化 | 一定または無し |</p>
<p>スコアが閾値（例: 0.5）を下回ると、ボットと判定されてブロックされます。自動化ツールはこういった「人間らしさ」を再現するのが難しいようです。</p>
<h2>多層防御の考え方</h2>
<p>今回の経験で改めて実感したのは、多層防御の重要性です。</p>
<p>一つの対策に頼るのではなく、複数の防御層を設けることで、どれか一つが突破されても他の層で防げるようになります。</p>
<p><code></code><code><br />
[攻撃]<br />
→ IPブラックリスト（既知の攻撃元をブロック）<br />
→ レート制限（大量リクエストを防止）<br />
→ reCAPTCHA（ボットを検出）<br />
→ 入力検証（不正な値を拒否）<br />
→ パラメータ化クエリ（SQLインジェクションを無効化）<br />
</code><code></code></p>
<p>今回は最後の砦である「パラメータ化クエリ」で攻撃が無効化されていましたが、他の層も機能していたことで、より安心できる状態でした。</p>
<h2>学んだこと</h2>
<p>今回の攻撃を通じて、いくつかのことを学びました。</p>
<h3>1. 基本的な対策が重要</h3>
<p>ORMやクエリビルダーを正しく使っていれば、SQLインジェクションは基本的に防げます。フレームワークの機能をちゃんと使うことが大切だなと思いました。</p>
<h3>2. ログは必ず残す</h3>
<p>攻撃を検知できたのは、リクエストのログを残していたからです。以下の情報は記録しておくと、分析に役立ちます。</p>
<p>&#8211; IPアドレス<br />
&#8211; User-Agent<br />
&#8211; リクエスト内容（個人情報の取り扱いには注意）<br />
&#8211; タイムスタンプ</p>
<h3>3. 小規模サービスでも攻撃は来る</h3>
<p>「うちは小さいから大丈夫」は通用しないんだなと実感しました。ボットは無差別にスキャンしているので、サービスの規模は関係ありません。</p>
<h3>4. 定期的なログ確認の習慣</h3>
<p>普段からログを確認する習慣をつけておくと、異常に気づきやすくなります。今回も定期的なログ確認で発見できました。</p>
<h2>まとめ</h2>
<p>今回の攻撃は、おそらく脆弱なサイトを探す自動スキャンでした。幸い対策済みだったため実害はありませんでしたが、個人開発でも普通に攻撃が来るんだなという良い経験になりました。</p>
<p>Webサービスを運営している方は、ぜひ以下の点を確認してみてください。</p>
<p>&#8211; SQLインジェクション対策ができているか（パラメータ化クエリの使用）<br />
&#8211; ログを確認する習慣があるか<br />
&#8211; 多層防御を意識した設計になっているか</p>
<p>何かの参考になれば幸いです。</p>The post <a href="https://mintaku-blog.net/attack-sql-injection/">個人開発のWebサービスがSQLインジェクション攻撃を受けた話と対策まとめ</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/attack-sql-injection/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2632</post-id>	</item>
		<item>
		<title>SupabaseのRow Level Security（RLS）について整理する</title>
		<link>https://mintaku-blog.net/supabase-rls/</link>
					<comments>https://mintaku-blog.net/supabase-rls/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Fri, 29 Aug 2025 00:47:54 +0000</pubDate>
				<category><![CDATA[まとめ系]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2626</guid>

					<description><![CDATA[<p>RLSとは何か？ Row Level Security（RLS）は、データベースの各行に対してアクセス権限を細かく制御する仕組みです。 簡単に言うと、「誰が …</p>
The post <a href="https://mintaku-blog.net/supabase-rls/">SupabaseのRow Level Security（RLS）について整理する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<h2>RLSとは何か？</h2>
<p>Row Level Security（RLS）は、データベースの各行に対してアクセス権限を細かく制御する仕組みです。</p>
<p>簡単に言うと、「誰がどのデータを見たり編集したりできるか」をデータベースレベルで決められる機能です。</p>
<p>例えば、SNSアプリを作る場合を想像してください。ユーザーAの投稿はユーザーAだけが編集できて、他のユーザーは閲覧のみ可能にしたいですよね。RLSを使えば、そのようなルールをデータベース側で自動的に適用できるのです。</p>
<h2>従来の方法との違い</h2>
<h3>従来のアプローチの問題点</h3>
<p>これまでのWebアプリケーション開発では、セキュリティ制御をアプリケーションのコード内で行うのが一般的でした。しかし、この方法には以下のような問題がありました。</p>
<h4>1. ヒューマンエラーのリスク</h4>
<p>開発者がセキュリティチェックを書き忘れると、他のユーザーのデータが見えてしまう可能性があります。特に大規模なアプリケーションでは、すべてのエンドポイントで完璧なセキュリティを実装するのは困難です。</p>
<h4>2. 開発・保守の負担</h4>
<p>新しい機能を追加するたびに「このユーザーはこのデータにアクセスできるのか？」を毎回考える必要があり、コードも複雑になりがちです。</p>
<h4>3. 一貫性の欠如</h4>
<p>チーム開発では、開発者によってセキュリティの実装方法が異なる場合があり、アプリケーション全体で一貫したセキュリティポリシーを維持するのが難しくなります。</p>
<h3>RLSの利点</h3>
<p>RLSを使用することで、これらの問題を根本的に解決できます。</p>
<p><strong>データベースレベルでの保護</strong>：アプリケーションコードにバグがあっても、データベース側でアクセスを制限するため、データ漏洩のリスクが大幅に減少します。</p>
<p><strong>開発効率の向上</strong>：一度ポリシーを設定すれば、アプリケーション側でいちいちセキュリティチェックを書く必要がなくなります。</p>
<p><strong>自動適用</strong>：Supabaseクライアントを通じてデータにアクセスする際、RLSポリシーが自動的に適用されるため、開発者が意識しなくても適切なアクセス制御が行われます。</p>
<h2>RLSの仕組み</h2>
<p>RLSは「ポリシー」という仕組みで動作します。ポリシーとは、「特定の条件を満たした場合のみ、データへのアクセスを許可する」というルールのことです。</p>
<h3>ポリシーの基本構造</h3>
<p>ポリシーを作成する際は、以下の要素を定義します。</p>
<ul>
<li>対象テーブル：どのテーブルに適用するか</li>
<li>操作種類：SELECT（読み取り）、INSERT（作成）、UPDATE（更新）、DELETE（削除）のどの操作に適用するか</li>
<li>条件：どのような条件の時にアクセスを許可するか</li>
</ul>
<p>例えば「ユーザーは自分が作成した投稿のみ編集できる」というルールを作りたい場合、「投稿テーブルのUPDATE操作において、現在ログインしているユーザーのIDと投稿の作成者IDが一致する場合のみ許可する」というポリシーを定義します。</p>
<h2>実際の活用例</h2>
<h3>ブログアプリケーションでの活用</h3>
<p>ブログアプリケーションを例に、RLSの実際の活用方法を説明します。</p>
<p>まず、ユーザーテーブルと投稿テーブルがあるとします。投稿テーブルには、タイトル、内容、作成者ID、公開フラグが含まれています。</p>
<p>このようなアプリケーションでは、以下のようなアクセス制御が必要になるでしょう。</p>
<h4>閲覧に関するルール</h4>
<ul>
<li>すべてのユーザーが公開済みの投稿を見ることができる</li>
<li>作成者は自分の投稿（下書き含む）をすべて見ることができる</li>
<li>他のユーザーの下書きは見ることができない</li>
</ul>
<h4>編集に関するルール</h4>
<p>ユーザーは自分の投稿のみ作成・編集・削除できる</p>
<p>他のユーザーの投稿は編集できない</p>
<p>これらのルールをRLSで実現することで、アプリケーション側で複雑なセキュリティロジックを書く必要がなくなります。</p><pre class="crayon-plain-tag">-- 例：誰でも公開された投稿を閲覧可能
CREATE POLICY "Anyone can view published posts" ON posts
FOR SELECT USING (is_published = true);

-- 例：ユーザーは自分の投稿のみ編集可能
CREATE POLICY "Users can update own posts" ON posts
FOR UPDATE USING (auth.uid() = user_id);</pre><p></p>
<h2>組織やチーム単位でのアクセス制御</h2>
<p>個人ユーザーだけでなく、組織やチーム単位でのアクセス制御も可能です。</p>
<p>例えば、プロジェクト管理ツールを作る場合を考えてみましょう。このツールでは</p>
<ul>
<li>同じ組織のメンバーは組織内のプロジェクトを閲覧できる</li>
<li>プロジェクトの作成は組織の管理者のみが可能</li>
<li>プロジェクトの削除は組織のオーナーのみが可能</li>
</ul>
<p>といったルールが必要になります。</p>
<p>RLSを使えば、これらの複雑なアクセス制御もデータベースレベルで実現できます。ユーザーと組織の関係、ユーザーの役割（メンバー、管理者、オーナー）などの情報を基に、適切なポリシーを設定することで、安全で柔軟なアクセス制御システムを構築できます。</p>
<h2>パフォーマンスとベストプラクティス</h2>
<h3>パフォーマンスへの配慮</h3>
<p>RLSを使用する際は、パフォーマンスも考慮する必要があります。ポリシーの条件が複雑になると、データベースの処理が重くなる可能性があります。</p>
<h4>インデックスの活用</h4>
<p>ポリシーで使用する条件に対して適切なインデックスを設定することで、パフォーマンスの向上が期待できます。例えば、ユーザーIDで絞り込む条件が多い場合は、user_idカラムにインデックスを設定します。</p>
<h4>効率的なポリシー設計</h4>
<p>よく使用される条件を先に評価するようにポリシーを設計することで、不要な処理を減らすことができます。</p>
<h3>開発時のベストプラクティス</h3>
<h4>明確な命名規則</h4>
<p>ポリシーには分かりやすい名前を付けることが重要です。「users_select_own_posts」のように、誰が何に対してどの操作を行えるかが名前から分かるようにしましょう。</p>
<h4>段階的な実装</h4>
<p>すべてのテーブルに一度にRLSを適用するのではなく、重要なテーブルから段階的に導入することをお勧めします。これにより、問題が発生した際の影響範囲を限定できます。</p>
<h4>十分なテスト</h4>
<p>RLSポリシーは、異なる権限を持つユーザーでテストすることが重要です。管理者、一般ユーザー、ゲストユーザーなど、さまざまな立場からアクセスを試して、期待通りの動作をするか確認しましょう。</p>
<h2>まとめ</h2>
<p>SupabaseのRLSは、アプリケーションのセキュリティを根本的に向上させる強力な機能です。従来のアプリケーションレベルでのセキュリティ制御と比較して、より安全で保守性の高いシステムを構築できます。</p>
<p>適切に設計・実装されたRLSポリシーは、開発者の負担を軽減し、データ漏洩のリスクを最小限に抑えます。また、複雑な組織構造やロールベースのアクセス制御にも対応できる柔軟性を持っています。</p>
<p>RLSを活用して、ユーザーが安心して使える、セキュアなアプリケーションを構築していきましょう。</p>The post <a href="https://mintaku-blog.net/supabase-rls/">SupabaseのRow Level Security（RLS）について整理する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/supabase-rls/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2626</post-id>	</item>
		<item>
		<title>Claude AIを活用したWebアプリ開発事例まとめ</title>
		<link>https://mintaku-blog.net/claude-ai/</link>
					<comments>https://mintaku-blog.net/claude-ai/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Fri, 25 Jul 2025 04:42:35 +0000</pubDate>
				<category><![CDATA[まとめ系]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2621</guid>

					<description><![CDATA[<p>最近、Claude AIを使った開発の話をよく聞くようになり、実際に事例を調べてみると、想像以上に実用的な使われ方をしていることがわかりました。 今回は具体 …</p>
The post <a href="https://mintaku-blog.net/claude-ai/">Claude AIを活用したWebアプリ開発事例まとめ</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>最近、Claude AIを使った開発の話をよく聞くようになり、実際に事例を調べてみると、想像以上に実用的な使われ方をしていることがわかりました。</p>
<p>今回は具体的な事例を通して、現在のClaude AI活用の実態を整理してみます。</p>
<h2>Claude 4で何が変わったのか</h2>
<p>2025年5月にリリースされたClaude 4は、従来のバージョンから大きく進化しています。特に注目すべきは並列でのツール使用と、推論中にリアルタイムでWeb検索ができるようになった点です。</p>
<p>これまでのAIチャットツールは「聞かれたことに答える」だけでしたが、Claude 4は「考えながら必要な情報を取りに行く」ということができるようになりました。開発中に「このライブラリの最新の使い方がわからない」となった時、自分で調べて最新情報を踏まえた回答をしてくれるのは、かなり実用的です。</p>
<h3>Web検索機能</h3>
<p>3月に追加されたWeb検索機能も地味に便利です。従来は「GPTに聞く→Googleで調べる→また戻ってGPTに聞く」みたいな往復が必要でしたが、Claudeは一つの会話の中で最新情報を調べて回答してくれます。</p>
<p>ただし、検索結果をそのまま返すのではなく、Claudeが解釈して会話形式で返してくれるのが特徴的です。情報の信頼性については相変わらず注意が必要ですが、開発中のちょっとした調べ物には重宝しそうです。</p>
<h3>Claude Artifacts</h3>
<p>個人的に面白いと思ったのがArtifacts機能です。15行以上のコードや構造化されたコンテンツを独立して管理し、リアルタイムでプレビューできます。</p>
<p>従来だとVSCodeで作って、ローカルサーバー立てて、ブラウザで確認して&#8230;という手順が必要でしたが、すべてClaude上で完結できるのは確かに便利です。</p>
<p>何より、「ちょっとしたツールが欲しい」と思った時のハードルが劇的に下がります。社内の小さな業務効率化ツールとかには、かなり使えそうな印象です。</p>
<h2>Claude Codeという新しいアプローチ</h2>
<p>Claude Codeはターミナルベースで動作し、プロジェクト全体のコンテキストを理解してくれるという点が新しいです。従来のAIコーディングツールは「このファイルの中でこの機能を実装して」みたいな局所的な支援でしたが、Claude Codeはプロジェクト全体を見渡した提案ができるようです。</p>
<p>事例ではダッシュボードを数時間で作り上げていて、リアルタイム統計表示、ドキュメント管理、WebSocket対応、レスポンシブデザイン、ダークモード対応まで含んでいます。従来なら数週間かかる作業を数時間でやってのけるのは、確かにインパクトがあります。</p>
<p>ただ、こういう「すごく早い」系の事例を見る時は、要件定義や設計の時間が含まれているのか、品質はどの程度なのか、という点は気になります。</p>
<p>&nbsp;</p>
<h2>現在の制限事項</h2>
<p>調べてみると、現在のClaude Artifactsにはいくつかの制限があります：</p>
<p>&#8211; 外部APIとの連携ができない<br />
&#8211; 永続的なストレージがない<br />
&#8211; テキストベースの完了APIのみ</p>
<p>つまり、実際のWebサービスで必要な「データベースとの連携」「外部サービスとの認証」「ファイルアップロード」みたいな機能は、まだ自分で実装する必要があります。現状は「プロトタイプやモックアップの作成」「簡単なツールの開発」あたりが現実的な用途でしょう。</p>
<p>&nbsp;</p>
<h3>コスト面</h3>
<p>開発速度の向上は確実にコストメリットがあります。特にプロトタイプ作成やMVP開発では、大幅な時間短縮が期待できます。</p>
<p>一方で、生成されたコードのレビューや品質向上に追加工数がかかることも考慮する必要があります。結果的には「初期開発は早くなるが、品質担保に別の時間がかかる」という構造になりそうです。</p>
<p>&nbsp;</p>
<h3>スキルセットの変化</h3>
<p>Claude AIが普及すると、開発者に求められるスキルも変わってきそうです。純粋な「コードを書く能力」よりも、「問題を整理する能力」「AI生成コードの品質を評価する能力」「プロダクト全体を設計する能力」の重要性が高まりそうです。</p>
<p>特に、プロンプトエンジニアリングのスキルは今後重要になるでしょう。AIに適切な指示を出して、期待する結果を得る技術は、従来のプログラミングスキルとは異なる新しい能力です。</p>
<h2>開発プロセスの変化</h2>
<p>従来の「要件定義→設計→実装→テスト」という流れが、「アイデア→プロトタイプ→反復改善→本格実装」という形に変わる可能性があります。</p>
<p>特に初期段階でのアイデア検証が格段に早くなるため、より多くの選択肢を試行錯誤できるようになります。これは、プロダクト開発全体にとってプラスの変化だと思います。</p>
<h3>新しい役割の出現</h3>
<p>AIとペアプログラミングをする新しい働き方や、AI生成コードの品質を担保する専門職みたいな役割が生まれるかもしれません。また、技術的な実装よりもビジネス価値の創出により重点を置いた開発者の役割も増えそうです。</p>
<h2>まとめ</h2>
<p>Claude AIを使ったWebアプリ開発は、確実に実用的なレベルに達していると感じました。特にプロトタイプ作成や小規模ツール開発では、大幅な効率化が期待できます。</p>
<p>一方で、本格的なプロダクション環境で使うには、まだ課題も多く残っています。セキュリティ、パフォーマンス、保守性といった観点では、引き続き人間の開発者の役割が重要です。</p>
<p>重要なのは、AIを「開発者の代替」として見るのではなく、「開発プロセスを加速するツール」として捉えることだと思います。適切に活用すれば、より創造的で価値の高い仕事に集中できるようになるはずです。</p>
<p>今後もClaude AIの進化は続くでしょうし、開発現場での活用事例も増えてくると思います。早めにキャッチアップして、自分なりの活用方法を見つけていくことが大切になりそうです。</p>The post <a href="https://mintaku-blog.net/claude-ai/">Claude AIを活用したWebアプリ開発事例まとめ</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/claude-ai/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2621</post-id>	</item>
		<item>
		<title>Gemini API の新機能「URL Context」でWebページ情報抽出を実装してみた</title>
		<link>https://mintaku-blog.net/gemini-url-context/</link>
					<comments>https://mintaku-blog.net/gemini-url-context/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Sat, 07 Jun 2025 02:12:28 +0000</pubDate>
				<category><![CDATA[まとめ系]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2614</guid>

					<description><![CDATA[<p>2025年のGoogle I/Oで発表されたGemini APIの新機能「URL Context」を使って、Webサイトから自動で情報を抽出するシステムを構 …</p>
The post <a href="https://mintaku-blog.net/gemini-url-context/">Gemini API の新機能「URL Context」でWebページ情報抽出を実装してみた</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>2025年のGoogle I/Oで発表されたGemini APIの新機能「URL Context」を使って、Webサイトから自動で情報を抽出するシステムを構築しました。</p>
<p>「URLを投げるだけで中身をAIが理解してくれる」という魔法のような機能に見えましたが、実際に実装してみると様々な課題に直面しました。最終的に安定したシステムを構築するまでの試行錯誤をまとめます。</p>
<h2>URL Context機能とは</h2>
<p>従来のWebスクレイピングでは、HTMLを取得してパースし、CSSセレクターやXPathで特定の要素を抽出する必要がありました。</p><pre class="crayon-plain-tag">// 従来のスクレイピング
const response = await fetch(url);
const html = await response.text();
const $ = cheerio.load(html);
const title = $('.page-title').text();
const content = $('.main-content').text();
// ... 各要素を個別に抽出</pre><p>URL Context機能を使えば、これらの作業をAIに丸投げできます。</p><pre class="crayon-plain-tag">// URL Context使用
const response = await fetch('https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent', {
  body: JSON.stringify({
    contents: [{
      parts: [{ text: `${url}から情報をJSON形式で抽出して` }]
    }],
    tools: [{ "url_context": {} }]  // これが重要
  })
});</pre><p>&nbsp;</p>
<h2>最初の実装と失敗</h2>
<p>当初、以下のようなシンプルな実装から始めました。</p><pre class="crayon-plain-tag">export async function POST(request: NextRequest) {
    const { url } = await request.json();
    
    const response = await fetch(geminiApiUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            contents: [{
                parts: [{ text: `${url}の情報を抽出して` }]
            }]
        })
    });
}</pre><p></p>
<h3>発生した問題</h3>
<h4>間違った情報が返ってくる</h4>
<ul>
<li>入力: とあるWebサイトのページURL</li>
<li>出力: 全く関係ない別のサイトの情報</li>
</ul>
<h4>動作が不安定</h4>
<ul>
<li>同じURLでも結果が変わる</li>
<li>明らかに学習データから推測した内容</li>
</ul>
<h2>URL Context機能が動作していなかった</h2>
<p>最大の原因はtools設定の不備でした。URL Context機能を使うには明示的に指定する必要があります。</p><pre class="crayon-plain-tag">// 間違った実装
body: JSON.stringify({
    contents: [{ parts: [{ text: `URL: ${url}` }] }]
    // tools設定なし → 通常のテキスト生成として処理される
})</pre><p></p><pre class="crayon-plain-tag">// 正しい実装
body: JSON.stringify({
    contents: [{ parts: [{ text: `URL: ${url}` }] }],
    tools: [{ "url_context": {} }],                    // 重要！
    model: "gemini-2.5-flash-preview-05-20"           // 対応モデル
})</pre><p>また、URL Context機能は新しい機能のため、対応モデルが限定されていました。</p>
<h3>原因分析</h3>
<p>Webページが膨大だったのが大きな要因でした。</p><pre class="crayon-plain-tag">元のHTML: 208,922文字
↓
Gemini内部処理: 6,377トークン
↓
制限オーバー: 4,096トークン制限を超過</pre><p>一般的なWebサイトには以下の要素が大量に含まれていました。</p>
<ul>
<li>JavaScript（数万行）</li>
<li>CSS（複雑なスタイル定義）</li>
<li>広告・関連コンテンツ・ナビゲーション</li>
<li>アナリティクス用のdata属性</li>
</ul>
<p>&nbsp;</p>
<h2>解決策：ハイブリッドアプローチ</h2>
<p>最終的に、従来のスクレイピング + AI分析のハイブリッドアプローチを採用しました。</p>
<h3>実装フロー</h3>
<ol>
<li>自前でHTMLを取得</li>
<li>不要要素を徹底的に削除</li>
<li>プレーンテキスト化</li>
<li>Geminiで構造化データに変換</li>
</ol>
<h3>HTML軽量化の実装</h3>
<p></p><pre class="crayon-plain-tag">async function preprocessHTML(url) {
    // 1. HTMLを取得
    const response = await fetch(url, {
        headers: {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        }
    });
    const html = await response.text();
    
    // 2. 不要要素を削除
    let cleanHtml = html
        .replace(/&lt;script[^&gt;]*&gt;[\s\S]*?&lt;\/script&gt;/gi, '') // JavaScript
        .replace(/&lt;style[^&gt;]*&gt;[\s\S]*?&lt;\/style&gt;/gi, '')   // CSS
        .replace(/&lt;!--[\s\S]*?--&gt;/g, '')                  // コメント
        .replace(/class="[^"]*"/gi, '')                   // class属性
        .replace(/id="[^"]*"/gi, '')                      // id属性
        .replace(/style="[^"]*"/gi, '')                   // style属性
        .replace(/data-[^=]*="[^"]*"/gi, '');             // data属性
    
    // 3. HTMLタグを完全除去
    cleanHtml = cleanHtml
        .replace(/&lt;[^&gt;]+&gt;/g, ' ')                         // 全タグ削除
        .replace(/\s+/g, ' ')                             // 空白正規化
        .trim();
    
    // 4. 文字数制限
    return cleanHtml.substring(0, 8000);
}</pre><p>&nbsp;</p>
<h3>Gemini APIへの送信</h3>
<p></p><pre class="crayon-plain-tag">const response = await fetch(geminiApiUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        contents: [{
            parts: [{
                text: `以下のWebページテキストから必要な情報をJSON形式で抽出：

${preprocessedText}

出力形式:
{
  "title": "タイトル",
  "category": "カテゴリ",
  "description": "説明",
  "details": "詳細情報"
}`
            }]
        }],
        // URL Contextは使わない（前処理済みテキストのため）
        generationConfig: {
            temperature: 0.1,
            maxOutputTokens: 4096
        }
    })
});</pre><p>&nbsp;</p>
<h2>学んだこと</h2>
<p>「URLを渡すだけ」という説明に惑わされ、tools設定を見落としており、公式ドキュメントをよく読むべきでした。</p>
<p>また、基本的にWebページは非常に重く、軽量化なしではAI APIの制限にすぐ引っかかるので考慮する必要がありそうです。</p>
<p>「AIですべて解決」ではなく、現状は従来技術と組み合わせることで、より確実で効率的なシステムを作ることを目指すのが良さそうです。</p>
<p>現在、一部の項目で「N/A」が出力される場合があり、サイト構造の分析と、より適切な文字数制限の設定が今後必要になりそうです。</p>The post <a href="https://mintaku-blog.net/gemini-url-context/">Gemini API の新機能「URL Context」でWebページ情報抽出を実装してみた</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/gemini-url-context/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2614</post-id>	</item>
		<item>
		<title>Model Context Protocol (MCP) を理解してみる</title>
		<link>https://mintaku-blog.net/about-mcp/</link>
					<comments>https://mintaku-blog.net/about-mcp/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Tue, 20 May 2025 09:18:23 +0000</pubDate>
				<category><![CDATA[まとめ系]]></category>
		<category><![CDATA[その他]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2610</guid>

					<description><![CDATA[<p>最近よく聞くMCPについてよくわかっていないので、どんなものなのか理解するために整理してみました。 MCPって何？ Model Context Protoc …</p>
The post <a href="https://mintaku-blog.net/about-mcp/">Model Context Protocol (MCP) を理解してみる</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>最近よく聞くMCPについてよくわかっていないので、どんなものなのか理解するために整理してみました。</p>
<h2>MCPって何？</h2>
<p>Model Context Protocol（MCP）は、簡単に言うとAIモデルと外部ツールが連携するときの標準的なルールを定めたものです。</p>
<p>今まで、GPTやClaude等のAIモデルにWebスクレイピングやデータベース操作などの機能を追加しようとすると、それぞれ独自の方法で実装する必要がありましたが、MCPはこの問題を解決します。</p>
<p>要するにAIと外部ツールが話し合うための「共通言語」のようなものだと思います。</p>
<p>今まで、AIに「Webで検索して」とか「データベースから情報を取得して」とお願いするとき、それぞれ別々の方法で実装する必要がありましたが、MCPであれば一度覚えた方法でいろんなツールとAIを連携させることができるようになります。</p>
<h2>技術的にはどういう仕組みなのか</h2>
<p>MCPは、WebSocketやgRPCなどの通信プロトコルの上で動く、メッセージベースのシステムのようです。JSON形式でメッセージをやり取りして、AIとツールが情報を交換します。</p>
<p>特に重要だと思うのは「コンテキスト」の管理です。普通のAPIだと、一回一回のリクエストは独立していますが、MCPでは会話の流れやこれまでの処理結果を適切に管理してくれるようです。</p>
<p>これにより、AIが複数のツールを連続して使ったり、前回の結果を踏まえて次の行動を決めたりすることができるのではないでしょうか。</p>
<h2>実際の開発現場でどう使えそうか</h2>
<p>現在のAI開発で困っていることを考えると、MCPの恩恵は大きそうです。</p>
<h3>チャットボット開発での活用</h3>
<p>例えば、顧客サポートのチャットボットを作る場合</p>
<ul>
<li>CRMシステムから顧客情報を取得</li>
<li>注文履歴をデータベースから検索</li>
<li>在庫確認システムで商品状況をチェック</li>
<li>メール送信システムで返答を送信</li>
</ul>
<p>これらを全て統一的な方法で連携できれば、開発がかなり楽になります。</p>
<h3>開発者向けAIアシスタント</h3>
<p>コーディング支援AIの場合</p>
<ul>
<li>GitHubから最新のコードを取得</li>
<li>静的解析ツールでコード品質をチェック</li>
<li>テスト実行環境でコードを実行</li>
<li>ドキュメントを自動生成</li>
</ul>
<p>これらのツールを組み合わせたワークフローが、MCPで簡潔に記述できるかもしれません。</p>
<h2>実装の難しさと課題</h2>
<p>MCPを実際に使う際の課題もあるかと思います。</p>
<h3>学習コスト</h3>
<p>新しいプロトコルを覚える必要があります。特に、コンテキスト管理やメッセージ形式など、従来のAPI開発とは異なる概念を理解する必要があります。</p>
<h3>デバッグの困難さ</h3>
<p>AIとツールの連携が複雑になると、どこで問題が発生しているかを特定するのが難しくなりそうです。適切なログ機能やデバッグツールが重要になるでしょう。</p>
<h3>パフォーマンスの懸念</h3>
<p>抽象化レイヤーが増えることで、多少のオーバーヘッドは必要になりそうです。高負荷なシステムでは、この影響を考慮する必要がありそうです。</p>
<h2>まとめ</h2>
<p>Model Context Protocolは、AI開発における「配管工事」の部分を大幅に簡略化してくれそうです。標準化により、AI開発者はより創造的で付加価値の高い部分に時間を使えるようになるでしょう。</p>
<p>まだ新しい技術なので、全てが確定しているわけではありませんが、AI開発に携わる者として注目していくべき技術だと思います。</p>
<p>実際のプロジェクトで使えるレベルになるまでには、もう少し時間がかかるかもしれませんが、早めにキャッチアップしておけば、将来的に大きなアドバンテージを得られるかもしれないです。</p>The post <a href="https://mintaku-blog.net/about-mcp/">Model Context Protocol (MCP) を理解してみる</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/about-mcp/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2610</post-id>	</item>
		<item>
		<title>ウォーターフォールプロジェクトにアジャイルを導入する</title>
		<link>https://mintaku-blog.net/agile-introduction/</link>
					<comments>https://mintaku-blog.net/agile-introduction/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Tue, 08 Apr 2025 05:52:12 +0000</pubDate>
				<category><![CDATA[その他]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2603</guid>

					<description><![CDATA[<p>最近、名目上はアジャイル開発を採用しているものの、実態はウォーターフォールに近い進め方をしている新規プロジェクトに参加することになりました。 チームの一員と …</p>
The post <a href="https://mintaku-blog.net/agile-introduction/">ウォーターフォールプロジェクトにアジャイルを導入する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>最近、名目上はアジャイル開発を採用しているものの、実態はウォーターフォールに近い進め方をしている新規プロジェクトに参加することになりました。</p>
<p>チームの一員として関わる中で、このギャップが将来的な問題につながる可能性を感じ、アジャイル開発手法への段階的な移行を内部で提案してみた内容をまとめました。</p>
<h2>課題感</h2>
<p>新しいプロジェクトに参加してすぐに感じたのは、以下のような課題でした。</p>
<p>1. 曖昧なタスク定義: 作業内容がざっくりと定義されているため、正確な工数見積もりができない<br />
2. 頻繁な要件変更: 週次ミーティングでクライアントから次々と新しい要望が出てくる<br />
3. 優先順位の不明確さ: 何を先に実装すべきかの基準がない<br />
4. プロジェクト全体の見通しの不透明さ: 「いつまでに何ができるのか」がわからない</p>
<p>これらは「ウォーターフォール的アジャイル」とも呼べる状況の典型的な特徴です。</p>
<p>名目上はアジャイルを採用しているものの、実態はウォーターフォールに近い進め方をしており、アジャイルの利点を十分に活かせていない状態でした。</p>
<h2>予想されるリスク</h2>
<p>このまま進めた場合、以下のようなリスクが予想されました。</p>
<p>1. スケジュール管理の難しさ</p>
<p>タスクが明確に定義されていないため、今後の工数見積もりが困難になり、納期に間に合わない恐れがあります。</p>
<p>2. 要件変更への対応の限界</p>
<p>週次定例でクライアントから新たな要望や改善点が出され続け、それらを既存の計画にどう組み込むかの仕組みがないため、混乱が生じる可能性があります。</p>
<p>3. チームの疲弊</p>
<p>優先度が明確でないまま、次々と要件が追加されると、チームは常に「潜在的な納期遅れ」のプレッシャーにさらされ、疲弊してしまいます。</p>
<p>4. プロジェクトの失敗リスク</p>
<p>このままあいまいな状態で進めると、納期や品質に影響が出る可能性が高くなります。</p>
<h2>アジャイル開発手法の導入プラン</h2>
<p>これらの課題を解決するため、以下のようなアジャイル開発導入計画を立てました。</p>
<p>1. カンバンボードの実装</p>
<ul>
<li>タスクの可視化を進め、「今何が進行中で、何が待機中か」を明確にする</li>
<li>ボトルネックを早期に発見できるようにする</li>
</ul>
<p>2. ユーザーストーリーの整備</p>
<ul>
<li>曖昧な要件を明確なユーザーストーリーとして再定義する</li>
<li>各ストーリーにポイント（難易度・工数の指標）を付与し、定量的な管理を可能にする</li>
</ul>
<p>3. スプリント計画の導入</p>
<ul>
<li>チームの消化可能なポイント数（ベロシティ）を把握し、現実的な計画を立てる</li>
<li>2週間単位のスプリントを設定し、その期間内で達成可能な目標を設定する</li>
</ul>
<h2>期待される効果</h2>
<p>アジャイル開発を正しく導入することで、以下のような効果が期待できます。</p>
<p>1. クライアントとのコミュニケーション改善</p>
<ul>
<li>「今週は○○ポイントまで消化可能であり、これらの機能を実装できます」と具体的に説明できるようになる</li>
<li>追加要望があった場合、「この要望を優先すると、他のこの機能は次スプリントに持ち越しになります」と明確に提示できる</li>
<li>プロジェクトの状況が「見える化」されることで、より焦点を絞った建設的な対話が可能になる</li>
</ul>
<p>2. プロジェクト管理の透明性向上</p>
<ul>
<li>進捗状況が可視化され、問題の早期発見が可能になる</li>
<li>変更による影響範囲を定量的に評価できるようになる</li>
</ul>
<p>3. チームの持続可能な開発ペース確立</p>
<ul>
<li>達成可能な目標設定により、チームは無理なく効率的に開発を進められる</li>
<li>過剰な負荷を防止し、品質を維持できる</li>
</ul>
<p>4. リスクの低減と早期対応</p>
<ul>
<li>技術的な課題や実現可能性の問題を早期に発見できるため、プロジェクト後半での大きな問題発生を防止できる</li>
<li>問題が発生しても影響範囲が限定的で、素早い対応が可能になる</li>
</ul>
<h2>クライアントとの共有ポイント</h2>
<p>アジャイル開発への移行を成功させるためには、クライアントの理解と協力が不可欠です。以下の点をクライアントと早期に共有することにしました。</p>
<p>1. 優先度の明確化</p>
<ul>
<li>機能の優先順位を一緒に決定することの重要性を説明する</li>
<li>「すべてが最優先」は不可能であり、限られたリソースで最大の価値を生み出すための選択が必要であることを理解してもらう</li>
</ul>
<p>2. 変更管理プロセスの確立</p>
<ul>
<li>新たな要望はプロダクトバックログに追加し、優先度に応じて取り込むプロセスを確立する</li>
<li>変更が及ぼす影響を可視化し、適切な意思決定を促す</li>
</ul>
<p>3. 定例会議の活用方法</p>
<ul>
<li>スプリントレビューでは、完成した機能のデモを行い、フィードバックを得る</li>
<li>スプリント計画では、次のスプリントで取り組む内容を明確にする</li>
</ul>
<p>4. 追加要望と工数の関係</p>
<ul>
<li>追加要望には追加の工数が必要であり、既存タスクとの優先度調整が必要なことを理解してもらう</li>
<li>「スコープ・時間・品質」のトレードオフ関係を明確に伝える</li>
</ul>
<h2>実施に向けた具体的なステップ</h2>
<p>アジャイル移行の第一歩として、以下のようなステップで進める計画を立てました。</p>
<p>1. 現状のプロジェクト要件をユーザーストーリーとして整理</p>
<ul>
<li>既存の要件をユーザーストーリー形式に書き換える</li>
<li>各ストーリーにポイントを割り当てる</li>
</ul>
<p>2. プロダクトバックログの作成</p>
<ul>
<li>すべてのユーザーストーリーを優先順位順に並べたバックログを作成する</li>
<li>クライアントと一緒にレビューし、優先順位を合意する</li>
</ul>
<p>3. 初回スプリント計画</p>
<ul>
<li>2週間のスプリントで取り組むストーリーを選定する</li>
<li>チームのベロシティを保守的に見積もり、無理のない計画を立てる</li>
</ul>
<p>4. カンバンボードの運用開始</p>
<ul>
<li>Notion等のツールを活用し、タスクの進捗を可視化する</li>
<li>日次のスタンドアップミーティングを短時間で実施し、進捗を共有する</li>
</ul>
<p>5. 定例での振り返りと改善</p>
<ul>
<li>スプリントごとに振り返りを行い、プロセスの改善点を見つける</li>
<li>実績ベロシティを計測し、次回スプリントの計画に反映する</li>
</ul>
<h2>まとめ</h2>
<p>アジャイル開発への移行は一朝一夕にはいきません。特に「アジャイルと言いながらウォーターフォール」の状態からの脱却は、チームだけでなくクライアントの考え方の変化も必要とします。</p>
<p>重要なのは、すべてを一度に変えようとするのではなく、小さな成功体験を積み重ねていくことです。</p>
<p>今回のケースでは、まずはユーザーストーリーの整備とカンバンボードの導入から始め、チームとクライアントの双方に「見える化」のメリットを実感してもらうことから始めました。</p>
<p>アジャイル開発の本質は、不確実性を前提とした上で、効率的にプロジェクトを進めていく柔軟性にあると思います。プロジェクトの成功確率を高め、クライアントとの良好な関係構築にもつながると考えています。</p>The post <a href="https://mintaku-blog.net/agile-introduction/">ウォーターフォールプロジェクトにアジャイルを導入する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/agile-introduction/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2603</post-id>	</item>
		<item>
		<title>Refreshコンポーネントとrefreshメソッドの違いを理解する</title>
		<link>https://mintaku-blog.net/react-server-refresh/</link>
					<comments>https://mintaku-blog.net/react-server-refresh/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Tue, 04 Mar 2025 09:00:52 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2593</guid>

					<description><![CDATA[<p>react-serverでは、ページやコンテンツを更新するための方法が2つ提供されています。 宣言的なアプローチの&#38;lt;Refresh&#38;g …</p>
The post <a href="https://mintaku-blog.net/react-server-refresh/">Refreshコンポーネントとrefreshメソッドの違いを理解する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p><a href="https://react-server.dev/">react-server</a>では、ページやコンテンツを更新するための方法が2つ提供されています。</p>
<p>宣言的なアプローチの&lt;Refresh&gt;コンポーネントと、命令的なアプローチのrefresh()関数です。</p>
<p>一見似ているように見えるこの2つの更新方法ですが、実際どのような違いがありのかまとめてみました。</p>
<h2>2つの更新方法</h2>
<p>まず、それぞれの基本的な使い方を見てみましょう：</p><pre class="crayon-plain-tag">// 方法1: Refreshコンポーネントを使用
import { Refresh } from "@lazarv/react-server/navigation";

function RefreshButtonUsingComponent() {
  return (
    &lt;div&gt;
      &lt;Refresh&gt;更新する&lt;/Refresh&gt;
    &lt;/div&gt;
  );
}

// 方法2: refresh関数を使用
import { useClient } from "@lazarv/react-server/client";
import { startTransition } from "react";

function RefreshButtonUsingFunction() {
  const { refresh } = useClient();
  
  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; startTransition(async () =&gt; refresh())}&gt;
        更新する
      &lt;/button&gt;
    &lt;/div&gt;
  );
}</pre><p>一見すると似たような機能に見えますが、実際には重要な違いがあります。</p>
<h3>1. コンポーネント vs 関数</h3>
<p>最も明確な違いは、一方がReactコンポーネントであり、もう一方が関数であるという点です。</p>
<ul>
<li>&lt;Refresh&gt;：ユーザーインターフェースを構成するReactコンポーネント。宣言的なアプローチ</li>
<li>refresh()：プログラムから呼び出す関数。命令的なアプローチ</li>
</ul>
<h3>2. 使用方法と柔軟性</h3>
<p></p><pre class="crayon-plain-tag">// Refreshコンポーネント - シンプルに使える
&lt;Refresh&gt;クリックして更新&lt;/Refresh&gt;

// refresh関数 - より多くのコントロールが可能
const { refresh } = useClient();

// 条件付き更新
const conditionalRefresh = () =&gt; {
  if (hasChanges) {
    startTransition(async () =&gt; refresh());
  }
};

// 特定のアウトレットのみ更新
const refreshDashboard = () =&gt; {
  startTransition(async () =&gt; refresh("dashboardOutlet"));
};

// 他の処理と組み合わせる
const saveAndRefresh = async () =&gt; {
  await saveData();
  startTransition(async () =&gt; refresh());
  showNotification("保存しました");
};</pre><p>refresh()関数は柔軟性が高く、より複雑なシナリオに対応できます。</p>
<h3>3. トランジションの扱い</h3>
<p>&lt;Refresh&gt;コンポーネントは内部でReactのトランジションを自動的に処理しますが、refresh()関数を使用する場合は、通常自分でstartTransitionを使ってトランジションを開始する必要があります。</p><pre class="crayon-plain-tag">// Refreshコンポーネント - トランジションは内部で処理される
&lt;Refresh&gt;更新&lt;/Refresh&gt;

// refresh関数 - 自分でトランジションを開始する
&lt;button onClick={() =&gt; startTransition(async () =&gt; refresh())}&gt;
  更新
&lt;/button&gt;</pre><p>これにより、refresh()関数を使用する場合、トランジションのタイミングをより細かく制御できます。</p>
<h2>ユースケース：どちらを選ぶべき？</h2>
<p>それぞれの方法が適している状況を考えてみます。</p>
<h3>&lt;Refresh&gt;コンポーネントが適している場合</h3>
<ul>
<li>シンプルな「更新ボタン」が欲しい場合</li>
<li>マークアップが中心で、複雑なロジックが不要な場合</li>
<li>React Serverのデフォルトの動作で十分な場合</li>
<li>JSXの中で直感的に使いたい場合</li>
</ul>
<p></p><pre class="crayon-plain-tag">function SimplePage() {
  return (
    &lt;div className="page-container"&gt;
      &lt;header&gt;
        &lt;h1&gt;マイダッシュボード&lt;/h1&gt;
        &lt;Refresh&gt;更新&lt;/Refresh&gt;
      &lt;/header&gt;
      &lt;main&gt;
        {/* ページコンテンツ */}
      &lt;/main&gt;
    &lt;/div&gt;
  );
}</pre><p></p>
<h3>refresh()関数が適している場合</h3>
<ul>
<li>他のロジックと組み合わせる必要がある場合</li>
<li>条件付きで更新したい場合</li>
<li>特定のアウトレットだけを更新したい場合</li>
<li>カスタムUIと組み合わせたい場合</li>
<li>プログラムで更新のタイミングを制御したい場合</li>
</ul>
<p></p><pre class="crayon-plain-tag">function ComplexDashboard() {
  const { refresh } = useClient();
  const [isLoading, setIsLoading] = useState(false);
  
  const handleRefresh = async () =&gt; {
    setIsLoading(true);
    // 先にバックエンドAPIからデータを取得
    await fetchLatestData();
    // その後ページを更新
    startTransition(async () =&gt; {
      await refresh();
      setIsLoading(false);
    });
    // 分析データを送信
    trackEvent("dashboard_refreshed");
  };
  
  return (
    &lt;div className="dashboard"&gt;
      &lt;button 
        onClick={handleRefresh}
        disabled={isLoading}
        className={isLoading ? "btn-loading" : "btn-normal"}
      &gt;
        {isLoading ? "更新中..." : "最新データに更新"}
      &lt;/button&gt;
      {/* ダッシュボードコンテンツ */}
    &lt;/div&gt;
  );
}</pre><p></p>
<h2>まとめ</h2>
<p>React Serverにおける2つの更新方法は、同じ機能をベースとしながらも、異なるユースケースに合わせて最適化されています。</p>
<ul>
<li>&lt;Refresh&gt;コンポーネント：シンプルさと宣言的なアプローチを重視</li>
<li>refresh()関数：柔軟性と制御性を重視</li>
</ul>
<p>より高度なパフォーマンス最適化やユーザーエクスペリエンス向上のためには、refresh()関数とstartTransitionを組み合わせた方法が多くの場合に適していそうです。</p>
<p>しかし、シンプルなUI要素としては&lt;Refresh&gt;コンポーネントの直感的に使いやすいです。</p>The post <a href="https://mintaku-blog.net/react-server-refresh/">Refreshコンポーネントとrefreshメソッドの違いを理解する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/react-server-refresh/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2593</post-id>	</item>
		<item>
		<title>【実体験】個人開発でサービス運営を始める前に知っておくべきこと</title>
		<link>https://mintaku-blog.net/before-personal-dev/</link>
					<comments>https://mintaku-blog.net/before-personal-dev/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Sun, 02 Feb 2025 04:51:21 +0000</pubDate>
				<category><![CDATA[まとめ系]]></category>
		<category><![CDATA[サイト運営]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2573</guid>

					<description><![CDATA[<p>個人開発において「技術的な学習が目的の個人開発」と「実際にユーザーに使ってもらうことを目指す個人開発」では、考えるべきことが大きく違います。 今回は実際にユ …</p>
The post <a href="https://mintaku-blog.net/before-personal-dev/">【実体験】個人開発でサービス運営を始める前に知っておくべきこと</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>個人開発において「技術的な学習が目的の個人開発」と「実際にユーザーに使ってもらうことを目指す個人開発」では、考えるべきことが大きく違います。</p>
<p>今回は実際にユーザーに使われるサービスを作りたい方に向けて、大学生の頃から現在まで10個以上の個人開発のサービスを作ってきた経験から得た知見をお伝えします。</p>
<p><strong>なお、単純に技術力向上が目的の場合は、この記事で紹介する「集客」や「マネタイズ」といった観点は気にする必要はありません。</strong></p>
<p>まずは自分の目的を明確にすることから始めましょう！</p>
<h2>個人開発の目的を明確にする</h2>
<p>個人開発には大きく分けて3つの目的があります。</p>
<h3>1. 技術的な学習</h3>
<ul>
<li>新しい言語やフレームワークの習得</li>
<li>アーキテクチャの理解</li>
<li>技術的チャレンジ</li>
</ul>
<p>→ この場合は以降で説明する「ユーザー獲得」や「マネタイズ」は気にする必要なし</p>
<h3>2. ポートフォリオ作成</h3>
<ul>
<li>転職や就職の際のアピール</li>
<li>技術力の可視化</li>
<li>コーディングスキルの証明</li>
</ul>
<p>→ この場合も「収益化」よりも「技術の見せ方」を重視</p>
<h3>3. サービス運営・収益化</h3>
<ul>
<li>実際のユーザーに使ってもらう</li>
<li>継続的な収益の獲得</li>
<li>サービス運営の経験を得る</li>
</ul>
<p>→ この場合は以降で説明する内容が重要</p>
<p>&nbsp;</p>
<h2>サービス運営から得られる学び</h2>
<p>実際にユーザーに使われるサービスを作ると、会社員エンジニアとして働いているだけでは得られない多くの学びがあります。</p>
<p>これらの経験は、エンジニアとしての視野を大きく広げてくれます。</p>
<h3>1. 収益の仕組みの理解</h3>
<p>広告収入の仕組みについて、実際に運用してみて初めて細かい部分が見えてきました。</p>
<ul>
<li>インプレッション単価は季節や時間帯でかなり変動する</li>
<li>クリック単価は業界によって大きく違う（金融系は高め、エンタメ系は低めなど）</li>
<li>成果報酬型広告は条件が合えば収益性が高い</li>
<li>純広告は安定収入になるけど、営業力が必要</li>
<li>動画配信型広告など新しい媒体が出てくる</li>
</ul>
<p>正直、これらは実際にやってみないと分からないことばかりでした。</p>
<h3>2. 運用コストの現実</h3>
<p>サービスの運用には意外と多くのコストがかかることを、実際に運営してみて初めて実感します。</p>
<p>特に自分の場合、最初は「無料枠でなんとかなるだろう」と思っていたのですが、ユーザーが増えるにつれて様々なコストが発生してきました。</p>
<p>サーバー費用はアクセス数と比例して増加し、最初は無料枠で収まっていても、徐々にコストが発生してきました。</p>
<p>他にもドメイン代、データベースやストレージなどの費用が発生します。</p>
<p>急なアクセス増に対応するための余裕を持たせる必要がある場合は、それに伴うコストも考慮が必要です。例えば、SNSで拡散された時の一時的なアクセス増にも耐えられる設計が必要です。</p>
<h3>3. ユーザー対応の重要性</h3>
<p>サービスを運営する上で、ユーザーとの関係構築は重要です。</p>
<p>サービスを運営していくと問い合わせが発生してきます。ユーザーからのバグ報告はしっかりと受け止め、迅速に対応する必要があります。</p>
<p>お問い合わせに対して真摯に対応していくことでユーザー価値を届けることの大切さを学ぶことができます。</p>
<p>また、同じような質問が多いため、FAQ作成など効率化が必要になります。</p>
<p>機能要望について全ての要望に応えることはできませんが、優先順位をつけて計画的に対応することが重要です。</p>
<h2>アイデアの見つけ方</h2>
<p>自分の経験から言えば、最も取っ掛かりやすいのは自分自身が本当に必要としているものです。</p>
<p>自分が実際に困っている問題など、毎日の生活や仕事で「これ、面倒だな」と感じることをメモしておくと良いです。また、同僚や友人の愚痴や相談の中にも、サービスのタネは隠れています。</p>
<p>他にもよく使うサービスの「ここが使いづらい」という点も、新しいサービスのヒントになります。</p>
<p>アイデアが思いついたら、実際に需要があるか確認します。XなどのSNS検索で「〜できない」「〜めんどくさい」などのネガティブなキーワードで検索すると、潜在的なニーズが見つかります。</p>
<h2>アイデアの検証ステップ</h2>
<p>アイデアが固まったら、本格的な開発に入る前に必ず検証を行います。ここでの検証が甘いと、後々大きな手戻りの原因となります。</p>
<h3>1. 競合分析</h3>
<p>競合サービスの機能を一覧表にまとめ、どの機能が本当に必要とされているのかを見極めます。</p>
<p>例えば、私の場合は競合を全てスプレッドシートにまとめ、本当に使われている機能を洗い出しました。</p>
<p>差別化ポイントとして「より良い」ではなく「異なる」アプローチを見つけることが重要です。例えば、競合が機能の豊富さを売りにしているなら、あえてシンプルさを極めるという選択肢もあります。</p>
<h3>2. ユーザーヒアリング</h3>
<p>実際のユーザーの声を聞くことで、思い込みに気づくことができます。</p>
<p>知人や同僚など最初は身近な人に協力してもらいましょう。「このサービスがあったら使う？」ではなく、「現在どうやってこの問題を解決しているか」を聞くのがポイントです。</p>
<p>サブスクや買い切りなどの場合は「いくらなら払えるか」を具体的に聞きます。この時、「月額いくら？」ではなく「この問題を解決するためにいくらまでなら払えるか」という聞き方をすると、より正確な答えが得られます。</p>
<p>また「いつ」「どこで」「どんな時に」使うのかを具体的にイメージしてもらいます。これにより、見落としていた機能要件が見つかることもあります。</p>
<h2>MVP（最小機能）の設計</h2>
<p>ここからが実際の開発フェーズです。MVPは「必要最小限の機能を持つ製品」という意味ですが、重要なのは「最小限」であることです。</p>
<p>例として、以下の3つの質問全てに「YES」と答えられる機能のみを実装します。</p>
<h3>1. 必要最低限の機能か？</h3>
<p>「あったら便利」と「なければ困る」は明確に区別します。</p>
<p>例えばタスク管理アプリなら、タスクの追加・編集・削除は必須ですが、タグ付けは必ずしも最初から必要ではないです。</p>
<p>なくても代替可能な機能は後回し、例えば、カレンダー連携は便利ですが、最初は手動で日付を入力してもらうことで代替できます。</p>
<p>ヒアリングで得た情報をもとに、ユーザーが本当に必要な機能を見極めます。</p>
<h3>2. 2週間以内に実装可能か？</h3>
<p>開発期間が長くなると、モチベーション維持が難しくなります。</p>
<p>未経験の技術を使う場合は、学習時間も含めて見積もります。機能ごとに必要な作業を細かく分解し、現実的な工数を見積もります。</p>
<p>特に外部サービスとの連携や、初めて使う技術については、予期せぬ問題が発生する可能性を考慮します。</p>
<h3>3. 検証可能な価値を提供できるか？</h3>
<p>特に最初はユーザーからフィードバックが得られる機能を考慮します。</p>
<p>ユーザーの課題をどの程度解決できるのか、具体的に説明できる必要があります。</p>
<p>また、ユーザーの行動や反応を測定できる機能であることが重要です。この辺りはGoogle AnalyticsやSearch Console、Clarityなどを導入すると良いかと思います。</p>
<p>フィードバックをもとに、どのように改善できるかが具体的にイメージできるとなお良いです。</p>
<h2>運用コストを考える</h2>
<p>個人開発では、運用コストの管理が特に重要です。</p>
<p>最初は無料枠の範囲内で始められるサービスを選ぶと良いと思います。例えば、Vercel + Supabase、Firebase、Herokuなど。</p>
<p>また自動化できる部分は最大限自動化し、運用の手間を減らします。</p>
<h2>マネタイズプランの検討</h2>
<p>個人開発でのサービス運営でよく見かける失敗の一つが、マネタイズプランを後回しにすることです。なぜなら運用コストが発生すると赤字になり続けるので、サービス運営をすることが困難になるからです。</p>
<p>最低でも運営コストを稼げるくらいのマネタイズを考えておくと良いでしょう。</p>
<p>個人開発の開始前から以下の点を検討しておく必要があります。</p>
<h3>1. 収益モデルの選択</h3>
<p>サービスの性質に合わせて、適切な収益モデルを選択します。例えば以下などがあります。</p>
<h4>広告収入</h4>
<ul>
<li>メリット：ユーザーの金銭的負担がなく、導入が容易</li>
<li>デメリット：安定した収益を得るには大量のPVが必要</li>
<li>具体例：月間10万PVで1-2万円程度の収入が目安</li>
<li>向いているサービス：情報提供系、メディア系</li>
</ul>
<h4>サブスクリプション（月額課金）</h4>
<ul>
<li>メリット：安定した収益が見込める、売上予測がしやすい</li>
<li>デメリット：継続的な価値提供が必要、解約率の管理が重要</li>
<li>具体例：月額500円×継続ユーザー100人で月5万円の収入</li>
<li>向いているサービス：ツール系、サービス系</li>
</ul>
<h4>プラットフォーム手数料</h4>
<ul>
<li>メリット：取引規模に応じて収益が増加、ユーザー同士の取引から収益化</li>
<li>デメリット：両面市場の運営が必要、初期の流動性確保が課題</li>
<li>具体例：取引額の3-15%程度を手数料として徴収</li>
<li>向いているサービス：マッチングサービス、マーケットプレイス</li>
<li>補足：出品手数料、決済手数料、プレミアム掲載料など複数の収益源を組み合わせることも</li>
</ul>
<h4>ライセンス販売</h4>
<ul>
<li>メリット：一度の開発で複数回販売可能</li>
<li>デメリット：継続的なアップデートやサポートが必要</li>
<li>具体例：永続ライセンスを1-5万円で販売</li>
<li>向いているサービス：デスクトップアプリ、専門的なツール</li>
</ul>
<h4>フリーミアム</h4>
<ul>
<li>メリット：導入障壁が低く、優良ユーザーの囲い込みが可能</li>
<li>デメリット：無料ユーザーのコスト負担、機能分けの難しさ</li>
<li>具体例：基本機能は無料、高度な機能は月額課金</li>
<li>向いているサービス：SaaS、モバイルアプリ</li>
<li>補足：無料プランと有料プランの機能差を明確に</li>
</ul>
<h4>アフィリエイト/紹介料</h4>
<ul>
<li>メリット：初期投資が少なく、成果に応じた収益</li>
<li>デメリット：提携先の選定や関係維持が重要</li>
<li>具体例：商品紹介による成果報酬、ユーザー紹介料</li>
<li>向いているサービス：情報メディア、比較サイト</li>
<li>補足：主要な収益源として、または副収入として活用可能</li>
</ul>
<p>個人開発の場合、最初は1つのモデルからスタートして、サービスの成長に合わせて他のモデルを追加していくのがおすすめです。</p>
<p>例えば、最初は広告収入からスタートし、ユーザーが増えてきたらプレミアム機能を追加してサブスクリプションを導入する、といった具合です。</p>
<p>個人開発では、全てを完璧にする必要はないです。まずは最小限の体制を整え、サービスの成長に合わせて徐々に改善していきましょう。</p>
<p>特に初期は、自動化できる部分は積極的に自動化し、人的リソースはサービスの改善や新機能の開発に集中することをおすすめします。</p>
<p>&nbsp;</p>
<h2>結果はそんな重要じゃない</h2>
<p>色々書きましたが、これらをやるのはぶっちゃけめんどくさいです。ここまでしてユーザーが集まらなかったらとか1円も稼げなかったらどうしようとかやってて思いがちですが、個人的にあまり意識しすぎない方が良いと思ってます。</p>
<p>これらに挑戦したこと自体に価値があり、それぞれ実践してアウトプットすることで確実に自分の経験となり、キャリアップや想定外のところで活きてきます。</p>
<p>つまり、やること自体、自分にプラスになっていると意識すると良いと思ってます。結果的にダメでも学びや気づきがあるので、過程を楽しむと良いと思います。</p>
<h2>まずはやってみよう！</h2>
<p>この記事に書いたことは、あくまでも私の経験からの「こうじゃないかな」という考えで、人によって正解は違うと思います。<br />
正直、最初から全部完璧にやろうとする必要はありません。</p>
<p>この記事に書いてあることを頭の片隅に置きつつ、まずは自分で何かアクションを起こしてみることをおすすめします。</p>
<p>例えば、気になる技術でとりあえず何か作ってみる、小さなツールでもいいので公開してみる、広告を入れてみて収益の仕組みを体験するなど実際に動いてみると、必ず何かしらの気づきがあります。</p>
<p>私も最初は全然うまくいきませんでしたが、少しずつ試行錯誤する中で、だんだんコツが掴めてきました。</p>
<p>大事なのは、完璧を目指すことではなく、実際に動いて学びを得ること。この記事が、皆さんの最初の一歩を踏み出すきっかけになれば嬉しいです！</p>The post <a href="https://mintaku-blog.net/before-personal-dev/">【実体験】個人開発でサービス運営を始める前に知っておくべきこと</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/before-personal-dev/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2573</post-id>	</item>
		<item>
		<title>Intersection Observer APIを使用したGTM/GA4イベントトラッキングの実装</title>
		<link>https://mintaku-blog.net/io-api-ga4/</link>
					<comments>https://mintaku-blog.net/io-api-ga4/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Sun, 05 Jan 2025 01:50:27 +0000</pubDate>
				<category><![CDATA[Vue.js]]></category>
		<category><![CDATA[Nuxt.js]]></category>
		<category><![CDATA[サイト運営]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2565</guid>

					<description><![CDATA[<p>Webサイトでユーザーの行動を分析する際、要素が画面に表示されたタイミングを検知したいと思い、Vue.jsのコンポーネントで Intersection Ob …</p>
The post <a href="https://mintaku-blog.net/io-api-ga4/">Intersection Observer APIを使用したGTM/GA4イベントトラッキングの実装</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>Webサイトでユーザーの行動を分析する際、要素が画面に表示されたタイミングを検知したいと思い、Vue.jsのコンポーネントで Intersection Observer API を使用して要素の表示を検知し、Google Analytics 4（GA4）でトラッキングする実装方法を紹介します。</p>
<h2>目的</h2>
<ul>
<li>カードコンポーネントの画面表示を検知</li>
<li>GTM経由でGA4にイベントを送信</li>
<li>表示データの分析</li>
</ul>
<h2>実装フロー</h2>
<ol>
<li>Vueコンポーネントで要素の表示を検知</li>
<li>dataLayerにイベントをプッシュ</li>
<li>GTMでイベントを捕捉</li>
<li>GA4にデータを送信</li>
</ol>
<h2>Vueコンポーネントの実装</h2>
<p></p><pre class="crayon-plain-tag">import { ref, onMounted } from 'vue'

interface Props {
  items: Array&lt;{
    id: string
    title: string
  }&gt;
}

const props = defineProps&lt;Props&gt;()
const itemRefs = ref&lt;any[]&gt;([])

onMounted(() =&gt; {
  itemRefs.value.forEach((elementRef, index) =&gt; {
    if (!elementRef) return

    const observer = new IntersectionObserver((entries) =&gt; {
      if (entries[0].isIntersecting) {
        const item = props.items[index]
        // dataLayerにイベントをプッシュ
        window.dataLayer?.push({
          event: 'item_view',
          item_id: item.id,
          item_title: item.title,
          eventModel: {
            send_to: 'G-XXXXXXXX'
          }
        })
        observer.disconnect()
      }
    })

    observer.observe(elementRef.$el || elementRef)
  })
})
&lt;/script&gt;

&lt;template&gt;
  &lt;div&gt;
    &lt;div
      v-for="(item, index) in items"
      :key="item.id"
      :ref="el =&gt; itemRefs[index] = el"
    &gt;
      &lt;!-- カードの内容 --&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;</pre><p></p>
<h3>ref配列の使用</h3>
<p>itemRefs配列を使って、複数のカードコンポーネントへの参照を管理しています。Vue3のref属性の動的な割り当てを活用しています。</p>
<h3>Intersection Observer APIの設定</h3>
<p>各カードごとに個別のObserverを作成しています。一度表示を検知したらdisconnect()で監視を停止し、重複計測を防止します。</p>
<h3>dataLayerへのデータ送信</h3>
<p>カードのIDとタイトルを送信し、eventModelを使って送信先のGA4を指定しています。</p>
<h2>GTMの設定</h2>
<p>続いて、Google Tag Managerの設定手順です。</p>
<h3>1. 変数の設定</h3>
<p>まずは、dataLayerから送信されたデータを受け取るための変数を設定します。</p>
<p>GTMの変数設定で以下の2つを作成します。</p><pre class="crayon-plain-tag">【Item ID変数の設定】
- 変数名: Item ID
- 種類: データレイヤー変数
- データレイヤー変数名: item_id

【Item Title変数の設定】
- 変数名: Item Title
- 種類: データレイヤー変数
- データレイヤー変数名: item_title</pre><p>これらの変数は、後ほどGA4にデータを送信する際のパラメータとして使用します。</p>
<h3>2. トリガーの設定</h3>
<p>次に、イベントを検知するためのトリガーを設定します。</p><pre class="crayon-plain-tag">【トリガーの設定】
- トリガー名: Item View
- トリガータイプ: カスタムイベント
- イベント名: item_view</pre><p>このトリガーは、dataLayerにプッシュされたitem_viewイベントを検知します。</p>
<h3>3. タグの設定</h3>
<p>最後に、GA4にデータを送信するためのタグを設定します。</p><pre class="crayon-plain-tag">【タグの設定】
- タグ名: GA4 - Item View
- タグタイプ: Google タグ
- 測定ID: G-XXXXXXXX

イベントパラメータ:
- item_id: {{Item ID}}
- item_title: {{Item Title}}</pre><p></p>
<h2>GA4での設定と分析</h2>
<h3>カスタムディメンションの設定</h3>
<p>GA4でデータを分析するために、まずカスタムディメンションを設定する必要があります。</p>
<p>管理画面から以下のディメンションを作成します。</p>
<ol>
<li>Item ID（イベントスコープ）</li>
<li>Item Title（イベントスコープ）</li>
</ol>
<h3>レポートの作成</h3>
<p>探索レポートを使って、以下のように分析が可能です。</p>
<ul>
<li>ディメンション：Item Title</li>
<li>指標：イベントカウント</li>
<li>フィルター：イベント名 = item_view</li>
</ul>
<p>これにより、各カードの表示回数を確認できます。</p>
<h2>デバッグのポイント</h2>
<h3>1. GTMのプレビューモード</h3>
<p>GTMのプレビューモードでは以下を確認します。</p>
<ul>
<li>dataLayerにイベントが正しく送信されているか</li>
<li>変数が期待通りの値を取得できているか</li>
<li>タグが正しいタイミングで発火しているか</li>
</ul>
<h3>2. GA4のデバッグビュー</h3>
<p>GA4のデバッグビューでは</p>
<ul>
<li>イベントが正しく送信されているか</li>
<li>パラメータが正しく設定されているか</li>
</ul>
<p>を確認できます。</p>
<h2>よくあるトラブルと解決方法</h2>
<h3>イベントが発火しない</h3>
<ul>
<li>dataLayerの実装を確認</li>
<li>コンソールでエラーがないか確認</li>
</ul>
<h3>パラメータが取得できない</h3>
<ul>
<li>GTMの変数名が正しいか確認</li>
<li>dataLayerの構造を確認</li>
</ul>
<h3>GA4にデータが反映されない</h3>
<ul>
<li>測定IDが正しいか確認</li>
<li>最大48時間の遅延を考慮</li>
</ul>
<p>→ 実際に24時間くらいデータが反映されず、実装が間違っているのかと見直していましたが時間が経てば反映されてました</p>
<h2>参考リンク</h2>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API">交差オブザーバー API - Web API | MDN</a>
			</p>
							<div class="ys-blog-card__dscr">
					交差オブザーバー API (Intersection Observer API)&hellip;				</div>
										<div class="ys-blog-card__domain">developer.mozilla.org</div>
					</div>
	</div>
</div>


<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img src="https://i0.wp.com/www.gstatic.com/devrel-devsite/prod/v80d9a52eefe4eccd9a262cfbc94473a846b10e2b3115e0674427719fa6f74364/developers/images/opengraph/white.png?w=800&#038;ssl=1" alt="" data-recalc-dims="1">			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://developers.google.com/analytics/devguides/collection/ga4">Google Analytics for developers &nbsp;|&nbsp; Google for Developers</a>
			</p>
										<div class="ys-blog-card__domain">developers.google.com</div>
					</div>
	</div>
</div>


<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img src="https://i0.wp.com/www.gstatic.com/devrel-devsite/prod/v80d9a52eefe4eccd9a262cfbc94473a846b10e2b3115e0674427719fa6f74364/developers/images/opengraph/white.png?w=800&#038;ssl=1" alt="" data-recalc-dims="1">			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://developers.google.com/analytics/devguides/collection/ga4?hl=ja">デベロッパー向け Google アナリティクス &nbsp;|&nbsp; Google Analytics &nbsp;|&nbsp; Google for Developers</a>
			</p>
										<div class="ys-blog-card__domain">developers.google.com</div>
					</div>
	</div>
</div>The post <a href="https://mintaku-blog.net/io-api-ga4/">Intersection Observer APIを使用したGTM/GA4イベントトラッキングの実装</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/io-api-ga4/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2565</post-id>	</item>
		<item>
		<title>AWS ECS Execを使用したコンテナへの直接アクセス方法</title>
		<link>https://mintaku-blog.net/aws-ecs-exec/</link>
					<comments>https://mintaku-blog.net/aws-ecs-exec/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Thu, 26 Dec 2024 00:12:53 +0000</pubDate>
				<category><![CDATA[その他]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2557</guid>

					<description><![CDATA[<p>AWS ECS（Elastic Container Service）でコンテナを運用している場合、デバッグやトラブルシューティングのためにコンテナ内部にアク …</p>
The post <a href="https://mintaku-blog.net/aws-ecs-exec/">AWS ECS Execを使用したコンテナへの直接アクセス方法</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>AWS ECS（Elastic Container Service）でコンテナを運用している場合、デバッグやトラブルシューティングのためにコンテナ内部にアクセスする必要が出てきます。</p>
<p>ECS Execを使用してコンテナに直接アクセスする方法を解説します。</p>
<h2>ECS Execとは</h2>
<p>ECS Execは、AWS ECSの実行中のコンテナに直接SSHのような形でアクセスできる機能です。この機能により、以下のようなことが可能になります。</p>
<ul>
<li>コンテナ内でのコマンド実行</li>
<li>ログファイルの直接確認</li>
<li>プロセスの状態確認</li>
<li>ファイルシステムの調査</li>
</ul>
<h2>前提条件</h2>
<ul>
<li>AWS CLIがインストールされていること</li>
<li>AWSプロファイルが設定されていること</li>
<li>対象のECSタスクでECS Execが有効になっていること</li>
<li>適切なIAM権限が付与されていること</li>
</ul>
<p>&nbsp;</p>
<h2>手順</h2>
<h3>1. AWS認証情報の設定</h3>
<p>開発環境用のプロファイルを設定します：</p><pre class="crayon-plain-tag">$ export AWS_PROFILE=${profile}</pre><p></p>
<h3>2. 必要な情報の確認</h3>
<p>ECS Execを使用するために必要な情報は以下の3つです：</p>
<ol>
<li>クラスター名</li>
<li>タスクID</li>
<li>コンテナ名</li>
</ol>
<p>これらの情報は、AWS Management ConsoleのECSダッシュボードから確認できます。</p><pre class="crayon-plain-tag">https://ap-northeast-1.console.aws.amazon.com/ecs/v2/clusters/[クラスター名]/services</pre><p>&nbsp;</p>
<h3>3. ECS Execの実行</h3>
<p>以下のコマンドを使用してコンテナにアクセスします：</p><pre class="crayon-plain-tag">aws ecs execute-command \
--region ap-northeast-1 \
--cluster [クラスター名] \
--task [タスクID] \
--container [コンテナ名] \
--interactive \
--command "/bin/sh"</pre><p>&nbsp;</p>
<h3>4. シェルの変更</h3>
<p>コンテナに接続後、より使いやすいbashシェルに切り替えます。</p><pre class="crayon-plain-tag">bash</pre><p>&nbsp;</p>
<h2>接続エラーが発生する場合</h2>
<p></p><pre class="crayon-plain-tag">An error occurred (InvalidParameterException) when calling the ExecuteCommand operation:
The execute command failed because execute command was not enabled on the task.</pre><p></p>
<ul>
<li>タスク定義でECS Execが有効になっているか確認</li>
<li>IAM権限が適切に設定されているか確認</li>
</ul>The post <a href="https://mintaku-blog.net/aws-ecs-exec/">AWS ECS Execを使用したコンテナへの直接アクセス方法</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/aws-ecs-exec/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2557</post-id>	</item>
	</channel>
</rss>
