← ハブ

🔬 Miyoki Labs

個人(技術・開発ログ)(Zenn:4 / Qiita:2 / note:1 / X:3)

📅 おすすめ投稿カレンダー(週次・🔬Miyoki Labs)

X7:30告知①ブログ▶ 自己紹介-固定ポストとプロフィール
Zenn7-8時▶ 型がついてるのに実体が違う。AIと開
X8:00告知②Zenn▶ 自己紹介-固定ポストとプロフィール
Qiita7-8時▶ 個人でAIプロダクトを「作って売って
X8:00告知③Qiita▶ 自己紹介-固定ポストとプロフィール
ブログ作業日ブログ公開
note21:00▶ 「作れる人」になりたくて、AIプロダ
X21:15告知④note▶ 自己紹介-固定ポストとプロフィール

時間は標準スケジュールの目安。▶=その枠に次に出す下書き。予約は各媒体の純正機能(X/note=予約・Zenn/Qiita=おすすめ時刻に手動)で。

型がついてるのに実体が違う。AIと開発するとよく騙される——という話をここに書きます

Zennドラフト2026-07-06
Miyoki(作り手の自己紹介=Zenn個人)自己紹介 / 開発ログ / AI協働
2386字
---
title: "型がついてるのに実体が違う。AIと開発するとよく騙される——という話をここに書きます"
emoji: "🔬"
type: "idea"
topics: ["ai", "個人開発", "claude", "typescript", "poem"]
published: false
---

先日、こんなバグを踏みました。

管理画面で作ったキャンペーン割引が、なぜか契約編集の画面に出てこない。DBを見てもデータは正しく入っている。最初は「一覧を取るクエリが悪い」と思って書き直しました。直らない。次にサーバーの応答を疑いました。これも違う。

結局、原因はまったく別の場所にありました。画面を開く処理の中に `started_at.slice(...)` という一行があって、そこで例外が飛んでいた。`started_at` は型定義上 `string` なのに、実体は `Date` オブジェクトだったんです。`Date` に `.slice` は無いので当然エラー。しかもその例外のせいで、**後ろにあった「キャンペーン一覧を取得する処理」に一度も到達していなかった**。だから何をいじっても一覧は空のまま。原因(日付の型)と症状(キャンペーンが出ない)が、画面上まったく別の場所にありました。

型は `string` と言っている。でも実体は `Date`。**型がついているのに、その型が嘘をついている。** 人もAIも「stringって書いてあるからstringだろう」と信じて、まんまと騙される。

……という感じの「詰まり」を残していく場所として、Zennを始めます。はじめまして、Miyoki(みよき)です。

## 何をやってる人か

**Miyoki Labs** という屋号で、AIを使ったプロダクト開発をしています。肩書きは自分で「**AIプロダクトエンジニア**」と名乗ってます。企画から制作、運用、事業化まで、**一人でAIと一緒に通す**のが自分のやりたいことです。

その証明として、いま **Nompass** という小規模事業者向けのAI活用サービスを立ち上げて動かしています。さっきのキャンペーンのバグも、このNompassの開発で踏んだものです。うまくいくかはまだわかりません。でも、収益ラインも撤退ラインも自分で考える当事者になったことで、開発の解像度がかなり上がりました。

開発はほぼ Claude Code と二人三脚です。かなりの部分を書かせてます。ただ「AIが主役で自分はおまけ」ではなくて、**どこをAIに任せて、どこは人間が握るか**を決めるのが自分の仕事、という感覚でやってます。さっきの「型がつく嘘」も、AIに任せていれば防げたわけじゃなくて、**AIも自分も同じように騙された**話です。この線引きの話は、これから何度も書くことになると思います。

## これまで作ってきたもの

ポートフォリオ( https://miyoki-labs.com )にまとめていますが、ざっくり挙げるとこんな感じです。

- **説明できるRAG(経理・税務アシスタント)** — 回答だけじゃなく「どのチャンクを根拠にしたか」をUIで見せる。AIに勝手に判断させない設計を突き詰めた一本
- **ブログ自動化パイプライン** — キーワード→リサーチ→構成→本文→AIによる審査→Markdown出力、の6ステップで記事下書きを半自動生成するツール
- **提案文ジェネレーター / 執筆補助ツール** — システムプロンプトで文体を固定して量産する系
- **テレアポ切り返しデモ / 数値分析カウンター** など

どれも「AIで何をどこまで任せられるか」を、実際に手を動かして確かめた結果です。

## これからZennに書くこと

Zennは自分にとって**開発ログと失敗談を残す場所**にします。うまくいった話より、**詰まった話・見当違いした話**を中心に書くつもりです。理由は単純で、自分が調べ物をするとき一番助かったのが、そういう「同じ所でハマった人の記録」だったからです。

いま下書きにあるネタだと、たとえばこんなのを予定してます。

- 今日の冒頭で書いた「**型がつく嘘**」——ORM×RSCで `timestamp` が `Date` になり、`.slice` が時限爆弾になっていた話
- middlewareを「正しい位置」に直したら**無限リダイレクトループ**が始まった話(動いてなかったコードを直すと、隠れてたバグが動き出す)
- `wrangler pages deploy` が「成功」したのに本番が404だった話(git無しはプレビュー扱いになる)
- Cloudflare Pages で `next/image` が効かなくて、画像を実測273KB→34KBまで削った話
- AIの審査ゲートが「**構造的に絶対通らない**」設計になっていて、点数が70点に張り付いていた話

技術記事というより「その時どう間違えて、どう気づいたか」の記録に近いです。コードそのものより、判断の経緯を残したい。

## というわけで

同じような実装や、同じハマりを踏んでいる人に、ここの記事がショートカットになったら嬉しいです。

各媒体のハンドルは全部そろえてあるので、**「@miyoki_labs で探して」** もらえれば見つかります。受託(AI業務自動化・ツール開発)の相談も、ポートフォリオ( https://miyoki-labs.com )から受けてます。

次の記事から、さっそく最初の失敗談——「型がつく嘘」を書きます。よろしくお願いします。
補足・事実確認
- **改稿の意図**:Zennは"技術記事・開発ログ"志向の場なので、純粋な自己紹介ではなく**具体的な詰まり(型がつく嘘)から入る「開発ログ初回」**の形に変更。voice-kit の固定ルール「各媒体の理想構成・文字数を目指す」を適用(本文 約2,200字=Zenn目標2,000〜5,000字内)。
- **出典**:`計画\02_ポジショニング.md`(人格キャノン)+ `計画\プロジェクト棚卸し.md`(実績)+ 冒頭バグ=`miyoki-blog`/`Nompass`の実ネタ(seeds-miyoki・log.md「キャンペーンが出ない真犯人は無関係な.slice例外」)。
- **ブランド文脈**:Miyoki(作り手の自己紹介)→ **Zenn個人**。
- **事実確認ポイント**:
  - 冒頭のキャンペーン/`.slice`バグは実在(log.md 2026-07-05・portal PR #15〜#17)。数字・PR番号を本文に出すなら refs 要確認。
  - 「これから書くネタ」5件は全て `log.md`/`seeds-miyoki.md` にドラフトor記録済みの実ネタ(架空でない)。
- **CTA**:中盤(実績→ポートフォリオ)+末尾(受託相談)。Zenn文化に合わせアフィリ・過剰宣伝なし。
- **公開手段**:`published: false` のまま Zenn個人で手動投稿(将来は Phase2 の投稿ボタン化候補)。

「再生成理由」がAIに一度も伝わっていなかった——保存されるだけのフィードバックという罠

Zennドラフト2026-07-05
2083字
---
title: "「再生成理由」がAIに一度も伝わっていなかった——保存されるだけのフィードバックという罠"
emoji: "🕳️"
type: "tech"
topics: ["ai", "llm", "nextjs", "typescript", "個人開発"]
published: false
---

<!-- 出典: nompass-knowledge-log log.md 2026-07-04「再生成理由がAIに一度も伝わっていなかった」/core.md 2026-07-04(続き7) -->
<!-- ⚠️ これは原案。公開前に事実・数字・実績表記の人間チェック必須 -->

> **原案メモ(公開時は削除)**
> - タイトル別案: 「UIが揃っている=機能している、ではなかった」「AIへのフィードバックが『保存』止まりだった話」
> - ネタ度★★★の根拠: AIプロダクト全般で頻発する構造的な罠+実例と修正差分がある+発見の経緯に物語性
> - 読者: LLMを組み込んだプロダクトを作っている個人開発者・小規模チーム

## 何を作ったか

小規模事業者向けのAI事業診断サービス(Nompass Map)に、「診断結果への顧客フィードバックを再生成に反映する」仕組みを入れた話。……のはずが、**既存の「再生成理由」機能が、実はAIに一度も伝わっていなかった**ことが発覚した。その顛末。

### 書き出し案

「再生成の理由を書いてください」——管理画面にそういう入力欄があって、ちゃんとDBに保存されて、履歴にも表示される。誰がどう見ても機能している。でも、AIはその理由を一度も読んでいませんでした。

## なぜ気づけたか(ここが本題の入口)

- 顧客から「診断結果の営業エリアが実際と違う」系の指摘を拾う仕組みを設計していたとき、依頼主から出たひとこと:**「現状のFB体制だとプロンプトの高品質化につなげられるかがわからない」**
- 調査した結果:`regen_reason` は
  - ✅ 入力UIがある(プルダウン+自由記述)
  - ✅ DBに保存される(`business_info.regen_reason`)
  - ✅ 診断履歴に「更新理由: ○○」と表示される
  - ❌ **生成プロンプトには一切含まれていない**
- つまり「エリアが違うから直して」と書いて再生成しても、AIはその指摘を知らないまま**同じ入力で作り直すだけ**。たまたま変わることはあっても、指摘が反映される保証はゼロだった

### 引用する実データ(原文確認済み)

- 修正前: `generate.ts` のプロンプト組み立てに `regen_reason` への参照が存在しない(grepで0件)
- 修正後(1ブロック追加のみ):

```typescript
【再生成の理由・修正指示(前回の診断に対する指摘。該当箇所を最優先で修正すること)】
${business_info.regen_reason}
```

- refs: `portal@2c5256c`(修正コミット)/`portal/src/lib/map/generate.ts`

## なぜこうなったか(一般化パート・記事の核)

- フィードバックループは4段階ある: **収集 → 保存 → 表示 → 活用**
- 前半3つはUIを作れば「できた感」が出る。テストも通る。デモでも見せられる
- でも価値が生まれるのは4つ目の「活用」だけ。そして活用だけは**画面に映らない**ので、抜けていても誰も気づかない
- 「UIが揃っている=機能している」という思い込み。データフローは**終点(実際に使われる場所)まで**追わないと確認したことにならない

## 結果・学び

- 修正自体はプロンプトへの注入1ブロック。発見から修正まで数分
- 学び①: LLM連携機能は「入力がプロンプトに到達しているか」をgrepで機械的に確認できる。実装後チェックリストに入れるべき
- 学び②: 非エンジニアの「なんかつながってない気がする」という直感は、データフローの断絶を言い当てていることがある。UIの完成度と機能の実在は別物
- 学び③: 同時に作った顧客向けのセクション別コメント機能も、最初から「コメント→管理者確認→再生成理由に取り込むボタン→プロンプト注入」まで**終点込みで設計**した

<!-- 末尾CTA: 作ったものはこちら → https://miyoki-labs.com -->

> **公開前チェック(原案メモ・公開時削除)**
> - [ ] 「依頼主」の表現(自事業なので実際は自分=運営者。記事上の立て付けを決める)
> - [ ] コード引用が最新のgenerate.tsと一致しているか
> - [ ] Nompassサービス名を出すか伏せるか(出すならPublicationに紐付け)

Cloudflare Pagesでnext/imageが効かない問題と、sharpで273KB→34KBにした話

Zennドラフト2026-07-01
2645字
---
title: "Cloudflare Pagesでnext/imageが効かない問題と、sharpで273KB→34KBにした話"
emoji: "🖼️"
type: "tech"
topics: ["nextjs", "cloudflare", "sharp", "パフォーマンス", "個人開発"]
published: false
---

## この記事は何

Next.js の `next/image` を使ってるのに、**Cloudflare Pages にデプロイしたら画像が最適化されてなかった**、という話です。原因の切り分けと、`sharp` で事前にWebP化して **273KB → 34KB** にした対策までまとめます。

「`next/image` 使ってるから最適化は勝手にやってくれてるはず」と思ってる人(=過去の自分)に刺さるかなって思ってます。

一言でいうと、**`next/image` の最適化は“動く環境”が前提で、Cloudflare Pages の静的配信ではその前提が崩れる**、という話です。

---

## 症状:next/image なのに画像がやたら重い

ローカルでは気にならなかったんですが、公開後に PageSpeed 的なものを見たら画像が重い。DevTools の Network で該当画像を確認すると、こんな感じでした。

- リクエスト先は `/_next/image?url=...&w=...&q=...`(ちゃんと next/image 経由)
- なのに **レスポンスの `content-type` が元画像のまま**(`image/png`)
- **サイズも原寸のまま**(縮んでない・WebPにもなってない)

つまり `next/image` のURLは通ってるのに、**中身は原寸画像がそのまま返ってきてる**。最適化が素通り(passthrough)してる状態でした。

---

## 原因:CF Pages では画像最適化エンドポイントが動かない

`next/image` の最適化(リサイズ・WebP変換)は、**リクエスト時にサーバー側の画像処理が走る**ことで成立します。この処理には Node のランタイムや専用の画像最適化ローダーが要る。

ところが Cloudflare Pages に**静的エクスポート(`output: "export"`)**で載せると、成果物はただの静的ファイル。画像を動的に変換してくれる層がいないので、`/_next/image` は**変換せずに元画像をそのまま返す**だけになります。

なので Next 側も、静的エクスポート時は最初から諦める設定を推奨してきます。

```js
// next.config.mjs
const nextConfig = {
  output: "export",
  images: { unoptimized: true }, // ← CFでは最適化が効かないので明示的にオフ
};
export default nextConfig;
```

`unoptimized: true` にすると `/_next/image` を経由せず、画像は素の `<img src>` で配信されます。**「最適化されてるつもり」をやめて、自分で最適化する**方針に切り替えるわけです。

---

## 対策:ビルド前に sharp でWebP化+リサイズ

実行時に最適化できないなら、**ビルド前に済ませておけばいい**。`sharp` で「表示サイズに合わせてリサイズ+WebP変換」を事前にかけます。

```js
// scripts/optimize-images.mjs(ビルド前に一度回す)
import sharp from "sharp";

await sharp("brand/logo-src.png")
  .resize({ width: 512 })        // 実際に表示する最大幅に合わせる
  .webp({ quality: 82 })         // WebPで十分軽くなる
  .toFile("public/logo.webp");
```

あとは `<Image src="/logo.webp" .../>`(`unoptimized` なのでそのまま配信)で参照するだけ。

実測では、ロゴ画像が **273KB(原寸PNG)→ 34KB(適正サイズWebP)** になりました。約8分の1です。見た目の劣化はほぼ分からないレベル。

> ポイントは「**表示サイズに合わせてリサイズ**」も一緒にやること。WebP変換だけでなく、原寸で配って `width` 属性で縮めてた分の無駄が一番効きます。

---

## なぜ最初にハマるのか

`next/image` は「使えば最適化される」体験が強いので、**環境が変わっても同じだと思い込む**んですよね。でも最適化の実体は“変換してくれる何か”が動いているからで、静的ホスティングだとそれがいない。

Vercel だと透過的に効くので気づきにくい。**Cloudflare Pages のような静的配信に移した瞬間だけ**この前提が外れる、というのがハマりの正体でした。

---

## まとめ

- Cloudflare Pages(静的エクスポート)では `next/image` の最適化は**効かない**。`/_next/image` は原寸を passthrough する。
- 潔く `images.unoptimized: true` にして、**ビルド前に sharp で WebP化+リサイズ**する。
- 「リサイズ」も忘れずに。原寸配信をやめるだけで一番効く(実測 273KB→34KB)。

「フレームワークが勝手にやってくれてる」と思ってる所ほど、環境を変えたときに前提が崩れがちです。画像みたいな“効いてるつもり”は、一度 Network で実測してみると発見があるかもしれません。

(ポートフォリオも Cloudflare Pages で公開してます → https://miyoki-portfolio.pages.dev )

AIと一緒にポートフォリオを作ってCloudflare Pagesに公開するまでの全つまづき

Zennドラフト2026-07-01
5791字
---
title: "AIと一緒にポートフォリオを作ってCloudflare Pagesに公開するまでの全つまづき"
emoji: "🛠️"
type: "tech"
topics: ["nextjs", "cloudflare", "個人開発", "ai", "typescript"]
published: false
---

## この記事は何

AI(Claude)と一緒に、自分のポートフォリオサイトを Next.js + Cloudflare Pages で作って公開した記録です。完成品の自慢じゃなくて、**途中でハマったところを全部書く**スタイルでいきます。

同じように「AIに手伝ってもらって個人開発してる人」「Next.js を Cloudflare に載せようとしてる人」に刺さるかなって思ってます。正直、きれいに一発で動いた話より、詰まって直した話のほうが役に立つので。

作ったもの:AIプロダクトエンジニアとしての実績を見せるポートフォリオ(受託の入口)→ https://miyoki-portfolio.pages.dev 。技術構成は Next.js 15 / Tailwind / Cloudflare Pages です。

---

## なぜ作ったか

端的に言うと、**営業資料がほしかった**からです。「AIで何を作れるのか」を口で説明するより、実績ページを見せたほうが早い。あと、作る過程そのものが発信ネタになる(=この記事)ので一石二鳥だなと。

なので最初から「まず動かして公開、完璧はあとで」の方針でMVPを作りました。ここから先が本題のつまづき集です。

---

## つまづき①:`@cloudflare/next-on-pages` が Windows で動かない

Cloudflare Pages に Next.js を載せる定番は `@cloudflare/next-on-pages` です。これを `npm run pages:build` で叩いたら、こうなりました。

```
⚡️ Warning: It seems like you're on a Windows system, the Vercel CLI ... seems not to work reliably on Windows
SHELLAC COMMAND FAILED!
Executing: npm --version
⚡️ Unexpected error: {"cmd":"npm --version", ...}
```

内部で Vercel CLI を呼ぶんですが、それが Windows で不安定らしく、`npm --version` すら通らずにコケる。WSL に逃げる手もあったんですが、ここで一回立ち止まって考えました。

**「このサイト、全ページ静的じゃない?」**

ポートフォリオなのでサーバー機能(API ルートや動的レンダリング)は使ってない。だったら next-on-pages を経由せず、素直に**静的エクスポート**して、その成果物を直接アップすればいい。

`next.config.mjs` をこう変えました。

```js
const nextConfig = {
  output: "export",          // out/ に静的書き出し
  images: { unoptimized: true }, // Cloudflareでnext/imageは効かないので最初から未最適化
};
export default nextConfig;
```

ただ、これだけだと `sitemap.ts` と `robots.ts` でビルドが落ちます。

```
Error: export const dynamic = "force-static"/export const revalidate not configured
on route "/robots.txt" with "output: export".
```

メタデータルートに `dynamic = "force-static"` を足せば解決でした。

```ts
// app/sitemap.ts / app/robots.ts の頭に
export const dynamic = "force-static";
```

デプロイは wrangler で直接アップ。`package.json` の `deploy` をこうしておくと一発です。

```json
"deploy": "next build && wrangler pages deploy out --project-name miyoki-portfolio --branch main --commit-dirty=true"
```

**学び**:ツールの定番ルートが詰まったとき、「そもそもこの機能いる?」と一段戻ると近道が見える。全部静的なら next-on-pages を無理に通さなくていい。

---

## つまづき②:日本語が単語の途中で改行される

最初、本文の折り返しをきれいにしようと `word-break: keep-all` を入れていました(日本語の禁則っぽくしたくて)。そしたらモバイルで**横スクロールが発生**。長い日本語が折り返せずに画面外へはみ出してたんです。keep-all は CJK の改行を全面的に止めるので、こうなりがち。

で、横スクロールを直そうと keep-all を外したら、今度は**単語の途中で改行**するようになりました。「ではな/く」みたいに割れる。あちらを立てればこちらが立たず、です。

正解は `auto-phrase` でした。

```css
body {
  word-break: normal;       /* フォールバック: 文字単位 */
  word-break: auto-phrase;  /* Chrome 119+ / Safari 17.4+: 文節単位で改行 */
  overflow-wrap: break-word;
}
```

`auto-phrase` はブラウザが**文節を意識して**折り返してくれるので、「制作だけ・分析だけ、/ではなく。」のように自然に切れます。対応してないブラウザは前段の `normal` にフォールバック。

実際に 300px 幅で折り返しを計測してみたら、ちゃんと「ではなく。」が固まりで残ってました。

**学び**:日本語Webの改行は `keep-all` じゃなく `auto-phrase`。keep-all は「折り返せずにはみ出す」副作用があるので、レスポンシブだと地雷になりやすい。

---

## つまづき③:OG画像はAI生成より「実ロゴ+コード合成」が早かった

SNS共有用の OG 画像(1200×630)を、最初は画像生成にお願いしました。が、出てくるのが英語になったり、プレースホルダのダミーURLが入ったり、レイアウトが崩れたりで安定しない。デザインのコントロールが効かないんですよね。

結局、**手持ちの実ロゴ+ブランド色で、コードで合成**するのが一番早くて確実でした。ロゴが黒背景だったので、`mix-blend` の screen 合成で黒を抜いて、グラデの発光だけ残す。日本語テキストも sharp(SVG経由)で普通に描けました。

```js
// 黒背景のロゴを screen 合成で「黒だけ透過」して、ブランド色の背景に乗せる
await sharp(Buffer.from(bgSvg))
  .composite([{ input: logoPng, left: 802, top: 161, blend: "screen" }])
  .png()
  .toFile("public/og.png");
```

日本語フォントも、SVG の `font-family` に入っていれば普通にレンダリングされます(手元では Noto Sans JP / Yu Gothic で確認)。

**学び**:「AIで全部作る」がいつも最短とは限らない。**既にあるアセット+少しのコード**のほうが、ブランドの一貫性も精度も出る。適材適所。

---

## つまづき④:ページ遷移するとコンテンツが消える

スクロールで要素をふわっと出す演出(`.fade-in` を IntersectionObserver で監視)を入れていたんですが、トップから実績ページへ**リンク遷移すると、リロードするまで何も表示されない**バグが出ました。

原因は、監視役のコンポーネントが**初回マウント時に1回だけ** `.fade-in` を拾っていたこと。SPA 遷移だとレイアウトは再マウントされないので、新しいページの要素が `opacity: 0` のまま取り残される。

`usePathname` を依存に入れて、**遷移ごとに再監視**するようにして解決しました。

```tsx
const pathname = usePathname();
useEffect(() => {
  // pathname が変わるたびに .fade-in を拾い直して監視
  const targets = document.querySelectorAll(".fade-in:not(.is-visible)");
  // ...IntersectionObserver で observe...
}, [pathname]);
```

ついでに、遷移直後に発火しないケース用のフォールバック(少し後にビューポート内の未表示要素を一括で出す)も足しました。

**学び**:SPA のスクロール演出は「初回だけ」だと遷移で死ぬ。`usePathname` を依存に入れて遷移ごとに張り直すのが定石。

> おまけの失敗:`npm run dev` を動かしたまま別で `npm run build` を叩いて、`.next` のキャッシュが壊れて `__webpack_modules__[moduleId] is not a function` を食らいました。dev とビルドを同時に走らせないこと、これ大事です。

---

## つまづき⑤(重要):公開前に秘密情報をスキャンする

ポートフォリオに各プロジェクトの GitHub リンクを足すとき、リポジトリを public にする前に**秘密情報スキャン**をかけました。これがやっておいてよかった案件で。

別のアプリのリポジトリの履歴に、**Stripe の秘密鍵がコミットされていた**のを発見。public にしてたら鍵が丸見えでした(しかもコミットメッセージが "Initial commit for public release" だったのが地味に怖い)。

やったことはシンプルで、

- `.env` 系ファイルが git 管理に入っていないか(`git ls-files` で確認)
- ソースに直書きの鍵(`sk-ant-` / `AIza...` / `PRIVATE KEY` / `client_secret` など)がないか
- 新規 public 化するリポジトリは、`.gitignore` で `.env*.local` を除外したうえで、**push 前に「秘密ファイルが混入してないか」をガード**

```powershell
# push 前のガード例:tracked な .env(.example以外)があれば中断
$leak = git ls-files | Select-String '\.env' | Where-Object { $_ -notmatch '\.example$' }
if ($leak) { "ABORT: env混入" } else { # commit & push }
```

**学び**:public 化は「コードを見せる」だけじゃなく「**過去の全履歴を見せる**」こと。鍵が一度でも入っていたら、現在のコードがきれいでも履歴から漏れる。公開前スキャンは儀式にする価値あり。

---

## 結果と、判断の軸

無事に公開できました。今回ハマって学んだことを「次から迷わないための軸」にまとめると、こんな感じです。

- **全ページ静的なら、素直に `output: export`**。アダプタ層で消耗しない。
- **日本語Webの改行は `auto-phrase`**。`keep-all` ははみ出しの地雷。
- **ブランドアセットがあるなら、AI生成より実物+コード合成**。精度と一貫性が出る。
- **SPAのスクロール演出は遷移ごとに張り直す**(`usePathname`)。
- **public 化の前に秘密情報スキャン**。履歴まで見られる前提で。

完璧じゃないけど、まず動いて公開できた。ここから実績を足しながら育てていきます。

## おわりに

「AIに任せれば一瞬」みたいな話より、こうやって**詰まって直した過程**のほうがリアルだし役に立つかなと思って書きました。

次は問い合わせフォームの追加と、ブログ自動化ツールの仕上げをやる予定です。同じように Cloudflare Pages で個人開発してる方、`auto-phrase` と公開前スキャン、ぜひ試してみてください。

作ったポートフォリオはこちらです 👉 https://miyoki-portfolio.pages.dev

RAG・業務自動化・AIアプリ開発の受託もやっています。感想やツッコミ、お仕事の相談ももらえると喜びます。

個人でAIプロダクトを「作って売って運用まで」やるときの技術スタックと、AIへの任せ方

Qiitaドラフト2026-07-06
Miyoki(作り手の技術知見=Qiita個人)AI協働 / 個人開発 / スタック
2289字
---
title: 個人でAIプロダクトを「作って売って運用まで」やるときの技術スタックと、AIへの任せ方
tags:
  - AI
  - 個人開発
  - Claude
  - TypeScript
  - 生成AI
private: false
---

## はじめに

はじめまして、Miyoki(**Miyoki Labs**)です。AIプロダクトエンジニアとして、個人でAIを使ったプロダクトを作っています。

Qiitaでは技術解説を中心に書いていくので、初回は自己紹介を兼ねて **「いま自分が使っている技術スタック」と「AIにどこまで任せているか」** を具体的に共有します。同じように一人でAIプロダクトを回そうとしている人の、構成の参考になればと思います。

## いま使っているスタック

一言でいうと「**個人1人でも運用が破綻しない構成**」を優先して選んでます。無料枠と静的ホスティングを軸に、撤退リスクを低く保つのが方針です。

| 領域 | 使っているもの |
|------|----------------|
| フロント | React / TypeScript / Tailwind CSS(ブログは Astro) |
| バックエンド | Node.js / Python |
| AI | Claude API(生成・診断)/OpenAI API(画像系) |
| DB | Neon(PostgreSQL)+ Drizzle ORM |
| ホスティング | Cloudflare Pages(静的・商用可・無料)/一部 Render |
| 認証 | Better Auth |

派手な技術は入れてません。**「一人で保守できるか」を最優先**にすると、だいたいこの辺に落ち着きました。

## AIにどこまで任せているか

開発はほぼ Claude Code と二人三脚です。ただ丸投げはしていません。自分の中の線引きはシンプルで、こうしてます。

- **AIに任せる**:実装、リファクタ、調査、下書き生成(自然文・コード)
- **人間が握る**:何を作るかの判断、構造化フィールドの確定、公開のGO判断

とくに大事にしているのが3つ目です。たとえばAIに日付やIDのような**フォーマットが決まった値を生成させない**。ここを守らないと痛い目を見ます。

```typescript
// NG: period(期間)までAIに生成させる → "2026年6月" / "2026年06月" が混在して重複が出る
const report = await ai.generate('period も含めて JSON で返して')

// OK: period はサーバー側で確定、AIは本文だけ書かせる
const content = await ai.generate('レポート本文のみ生成して')
const report  = { period, ...content }
```

「AIが主役、人は確認と判断に集中する」——これは自分のサービス(Nompass)の設計思想でもあり、開発そのもののやり方でもあります。

## AIも自分も同じように騙される、という前提

線引きの話をもう一歩だけ具体的に。AIに任せる/握るを分けても、**両方が同時に騙される**ことはあります。最近踏んだのがこれです。

```typescript
// 型定義上は string。でも Neon の timestamp は RSC 経由で Date になって届く
started_at.slice(0, 10)  // ← Date に .slice は無い → TypeError で処理が止まる
```

型は `string` と言っているのに、実体は `Date`。**型がついているのに、その型が嘘をついている。** こういうとき、人もAIも「stringって書いてあるからstringだろう」と信じて、まんまと通してしまいます。しかもこの例外がイベントハンドラ内で握りつぶされ、UIは「無言で何も起きない」ように見える。原因(日付の型)と症状(別の機能が動かない)が、画面上まったく別の場所に出ました。

教訓はシンプルで、**「UIが黙っているときこそ、まずconsole」**。値を疑うより先に観測を疑う。AIと組んでいても、いや組んでいるからこそ、この順番を人間側が握っておくのが効くなと思ってます。

## 作ってきたもの

上のスタックで、こういうものを作ってきました。

- **説明可能なRAG**:回答の根拠チャンクをUIで見せる。AIに判断させず、人が確認できる形にする
- **ブログ自動化パイプライン**:キーワードから記事下書きまで6ステップで生成
- **提案文ジェネレーター / 執筆補助ツール**:システムプロンプトで文体を固定する系
- **Nompass**:小規模事業者向けのAI活用サービス(自分で立ち上げた実事業)

## これから書くこと

Qiitaでは、上みたいな「AIと開発するときの具体的な作法」や、ハマったエラーの解決手順を書いていきます。検索で同じ問題に当たった人がショートカットできるように、というのを意識するつもりです。

各媒体そろえてあるので **「@miyoki_labs で探して」** もらえれば見つかります。よろしくお願いします。
補足・事実確認
- **出典**:`計画\02_ポジショニング.md`(人格キャノン)+ グローバル/Nompass CLAUDE.md の技術スタック・AI出力信頼性ルール(`period` の例はNompass portal CLAUDE.mdの実ルールが原文)。
- **ブランド文脈**:Miyoki(作り手の技術知見)→ **Qiita個人**へ投稿(Organization「Nompass」ではない)。
- **事実確認ポイント**:
  - スタック表・Better Auth/Neon/Drizzle/Cloudflare Pages はNompass実装で実使用(STATUS/CLAUDE.md準拠)。個人プロジェクトはCloudflare Pages中心。**Renderは主にNompass portal**なので「一部」と表現。
  - `period` のコード例はNompass portalの実装ルールの引用。個人名義記事としては「自分の設計思想」として書いてOKだが、単独でNompassの内部実装を細かく出す方向にはしない。
- **CTA**:末尾のポートフォリオ導線+ハンドル。Qiita文化に合わせ受託CTAは軽め。
- **公開手段**:`private: false` だが手動投稿前提。Qiita個人アカウント。

wrangler pages deploy が本番に反映されない(404)——git無しはプレビュー扱いになる罠

Qiitaドラフト2026-07-06
Miyoki(作り手の技術Tips=Qiita個人)インフラ / Cloudflare Pages
2486字
---
title: wrangler pages deploy が本番に反映されない(404)——git無しはプレビュー扱いになる罠
tags:
  - CloudflarePages
  - wrangler
  - Cloudflare
  - デプロイ
  - Astro
private: false
---

## 概要

`wrangler pages deploy` を実行して「Success!」と出たのに、**本番URLを開くと404**。ビルドもデプロイも成功しているように見えるのに、公開サイトだけ更新されない——という状況にハマりました。

原因は **デプロイ対象フォルダがGitリポジトリでないと、wrangler が本番ではなく「プレビュー」としてデプロイしてしまう** ことでした。結論だけ先に書くと、`--branch main` を明示すれば直ります。

同じく「デプロイ成功したのに反映されない」で困っている人向けに、原因と対策、そして再発防止(npmスクリプトへの焼き込み)まで書きます。

## 前提・環境

- Cloudflare Pages に **Direct Upload**(`wrangler pages deploy` でローカルのビルド成果物を直接アップロード)
- ビルド出力を静的に吐くサイト(今回は Astro の `dist`)
- **デプロイするフォルダが Git 管理下ではない**(ここが今回の肝)

## 何が起きたか

やったことはシンプルで、これだけです。

```bash
wrangler pages deploy dist
```

コンソールには成功メッセージとデプロイ先URLが表示されます。ところが、本番のエイリアス(`<project>.pages.dev` や割り当てた独自ドメイン)を開くと **404**。生成されたプレビューURLの方は中身が見えている。つまり「アップロード自体は成功しているが、本番に紐づいていない」状態でした。

正直なところ、「Success!」と出ているので最初はDNSやキャッシュを疑いました。デプロイ成功のメッセージほど、当てにならないものはないなと思った瞬間です。

## 原因:git無し → ブランチを推定できず → プレビュー扱い

Cloudflare Pages は「**本番ブランチ(Production branch)にデプロイされたものだけ**」を本番エイリアスに反映します。

`wrangler pages deploy` は、実行フォルダがGitリポジトリなら現在のブランチ名を見て「これは本番ブランチか?」を判定します。ところが**フォルダがGit管理下でないと、ブランチを推定できません**。この場合、本番ブランチとして扱われず、**プレビューデプロイ**になります。だから本番エイリアスは古いまま(初回なら未作成で404)だったわけです。

「成功したのに反映されない」の正体は、**成功したのはプレビューへのデプロイだった**、というオチでした。

## 解決:--branch を明示する

本番ブランチ名を明示的に渡します。プロジェクトの本番ブランチが `main` なら、こうです。

```bash
wrangler pages deploy dist --branch main
```

これで本番エイリアスに反映され、独自ドメインでも中身が見えるようになりました。

ポイントは、**Gitに依存させず、コマンド側で本番ブランチを指定してしまう**こと。CIやGit連携を組んでいないローカルからの直接デプロイでは、この明示がないと毎回プレビューに落ちます。

## 再発防止:npmスクリプトに焼き込む

毎回 `--branch main` を手打ちすると、いつか忘れて404を再発させます。なので `package.json` の deploy スクリプトに最初から入れておきました。

```json
{
  "scripts": {
    "deploy": "astro build && wrangler pages deploy dist --branch main"
  }
}
```

これで `npm run deploy` 一発で、確実に本番へ反映されます。「手順で気をつける」より「コマンドに埋めて気をつけなくてよくする」方が、この手の事故には効きます。

## おまけ:CJK文字に挟まれた `**` は強調にならない

同じ作業中にもう一件踏んだので共有します。Markdownで

```md
その率は**26%**です
```

のように、**日本語(CJK文字)に直接挟まれた `**` は強調になりません**。CommonMarkの強調記号は前後の文字種(空白・句読点かどうか)で有効/無効が決まる仕様(left/right-flanking のルール)で、CJK文字にピッタリ挟まれると条件を満たさず、`**` がそのまま表示されてしまいます。

前後に半角スペースを入れると解消します。

```md
その率は **26%** です
```

AIに本文を書かせて日本語記事を量産していると、地味に踏みやすい罠です。

## まとめ

- `wrangler pages deploy` で「成功したのに本番404」は、**git無しフォルダがプレビュー扱いになっている**可能性が高い
- `--branch main` を明示すれば本番反映される
- 手打ちを忘れないよう **npmスクリプトに焼き込む**のが確実
- おまけ:CJKに挟まれた `**` は強調にならないので半角スペースを添える

「Success!」の表示を鵜呑みにせず、**本番エイリアスが実際に更新されたか**まで見るのが、この手のデプロイでは大事だなと思いました。
補足・事実確認
- **出典/refs**:`miyoki-blog/knowledge-log/log.md` #44 / `history/main.md` 2026-07-05(コマンドと解消手順)/ 本日 `package.json` に `--branch main` を焼き込み(2026-07-06)。
- **ブランド文脈**:Miyoki(作り手のインフラTips)→ **Qiita個人**。
- **事実確認ポイント**:
  - 「git無し→プレビュー扱い」はwrangler pages deployのブランチ推定挙動に基づく説明。**公開前に一次証拠(実際の404スクショ or wrangler出力ログ)を1点添える**と信頼度が上がる(`[📸証拠:デプロイ成功メッセージと本番404のスクショ]`)。
  - CJK `**` の挙動はCommonMark仕様(left/right-flanking)。実際に踏んだ事象(history/main.md記載)。
- **CTA**:技術Tipsのため受託CTAは入れず(Qiita文化)。末尾でハンドル導線は任意。
- **公開手段**:`private: false` だが手動投稿前提・Qiita個人。
- **キュー処理**:公開したら記事キュー #44 を削除→公開台帳へ。今回はドラフトなのでステータスのみ更新。

「作れる人」になりたくて、AIプロダクトエンジニアを名乗ることにした

noteドラフト2026-07-06
Miyoki(作り手のビジョン=note個人)自己紹介 / 生き方 / キャリア
1669字
# 「作れる人」になりたくて、AIプロダクトエンジニアを名乗ることにした

はじめまして、Miyoki(みよき)です。この記事は自己紹介です。とくに「なぜいまAIでプロダクトを作ることに賭けているのか」を、正直なところから書いてみます。

独学でキャリアチェンジを目指している人、副業や個人開発で何かを形にしたい人に、少しでも重なるものがあれば嬉しいです。

## いまの立ち位置を隠さずに言うと

まず前提を正直に書いておきます。僕はいま、本業はエンジニア職ではありません。別の仕事をしながら、夜と週末に個人でアプリを作っています。独立、あるいは転職を目指して独学している途中です。

「エンジニアです」と胸を張れる肩書きが、まだ手元にない状態。ここからのスタートです。

## それでも「作れる人」になりたかった

思い返すと、僕がずっと憧れていたのは「作れる人」でした。

アイデアを思いついて終わりじゃなくて、実際に動くものにして、人に使ってもらって、お金をもらうところまで持っていける人。企画だけの人でも、制作だけの人でもなく、**最初から最後まで一人で通せる人**。

正直、これまでの自分には遠い話でした。技術も中途半端、事業のことなんて何もわからない。でも、AIが出てきて景色が変わりました。

一人では手が回らなかった実装や調査を、AIがかなりの部分で肩代わりしてくれる。そのぶん自分は「何を作るか」「どこは人間が握るか」という判断に集中できる。これなら、**一人でも"作って売って運用まで"を通せるかもしれない**。そう思えたんです。

## 名乗りを先に決めた

だから、実績が完全に揃う前に、名乗りを先に決めました。**AIプロダクトエンジニア**。企画から制作、運用まで、AIでプロダクトを形にする人、という意味を込めています。

肩書きって、あとから追いつくものかなって思ってます。先に旗を立てて、そこに自分を寄せていく。

その証明として、いま **Nompass** という小規模事業者向けのAI活用サービスを自分で立ち上げて動かしています。うまくいくかはまだわかりません。でも、収益ラインも撤退ラインも自分で考える当事者になったことで、見える景色がまったく変わりました。

## 夜と週末しかない、それでも進める

正直に言うと、開発に使える時間はほとんどありません。平日は本業があるので、触れるのは夜の21時以降。あとは土日。まとまった時間なんて、ほぼ取れません。

以前の自分なら、それを「だから無理」の理由にしていたと思います。でも今は、細切れの時間でも前に進められるようになりました。理由の大きな部分はAIです。夜に「ここを直したい」と方針だけ決めて渡しておけば、実装はかなりの部分が進む。自分は判断と確認に集中する。翌朝スマホで結果を見て、また方針を返す。そういう回し方ができるようになって、「時間がないから作れない」という言い訳が、少しずつ効かなくなってきました。

もちろん、うまくいかない日もあります。方針を間違えて丸ごとやり直したり、AIと一緒に見当違いの方向に突っ走ったり。でも、その失敗も含めて「作れる人」に近づいている実感があります。完璧じゃなくても、動くものを積み上げていく。その繰り返しでしか、たぶん辿り着けない。

## これからnoteに書くこと

noteでは、こういう「生き方・働き方」寄りの話を書いていきます。技術の細かい話はZennやQiitaに置いて、こっちには**迷いや判断、失敗も含めたリアルな過程**を残すつもりです。うまくいった自慢話より、そっちのほうが誰かの役に立つ気がしています。

最後に一つだけ聞かせてください。あなたがいま「作れる人」になるために、一番ネックになっていることは何ですか。よかったらコメントで教えてください。次に書くテーマの参考にします。

(各媒体そろえてあります。「@miyoki_labs で探して」もらえれば見つかります)
補足・事実確認
- **出典**:`計画\02_ポジショニング.md`(人格キャノン・差別化「一気通貫」「事業を自分で作った当事者」)+ グローバルCLAUDE.mdプロフィール(現職非エンジニア・独学中・独立/転職志向)。
- **ブランド文脈**:Miyoki(作り手のビジョン)→ **note個人**。
- **事実確認ポイント**:
  - 「本業はエンジニア職ではない/夜と週末に個人開発/独立・転職を目指し独学中」はプロフィールの事実そのまま。誇張なし。
  - Nompassは「立ち上げて動かしている」=事実(初顧客フェーズ)。「成功した」とは書かない(未証明なので断定回避)。
- **CTA**:末尾の問いかけ+ハンドル導線(note文化に合わせCTAは1箇所に集約)。
- **字数**:本文 約1,500字(note適正 1,000〜2,000字内)。
- **公開手段**:note個人アカウントで手動投稿。

自己紹介-固定ポストとプロフィール

Xドラフト2026-07-06
Miyoki(作り手の自己紹介=X個人 @miyoki_labs)自己紹介 / X固定ポスト
126字
AIプロダクトエンジニア🔬 企画→制作→運用までAIでプロダクトを作る人。小規模事業者向けAI活用サービス @nompass を運営中。開発ログ=Zenn / 技術=Qiita / 生き方=note で発信。作ったもの→ miyoki-labs.com
109字
1/
AIで「作って・売って・運用まで」を一人でやってます。
Miyoki Labs(@miyoki_labs)です🔬
自己紹介を固定しておきます。同じように個人でAIプロダクトを回そうとしてる人に届いたら嬉しいです。
111字
2/
何をやってる人かというと、企画から制作・運用・事業化までをAIと二人三脚で通す人です。
その証明として、小規模事業者向けのAI活用サービス「Nompass」を自分で立ち上げて動かしてます。理論じゃなく、当事者として。
116字
3/
これまで作ってきたもの:
・根拠を見せる「説明できるRAG」
・記事下書きを6ステップで作る自動化パイプライン
・提案文/執筆の生成ツール
・数値分析アプリ 等
どれも「AIにどこまで任せるか」を手を動かして確かめた結果です。
120字
4/
発信はこう分けてます:
🛠 開発ログ・失敗談 → Zenn
📘 技術解説 → Qiita
🌱 生き方・働き方 → note
全部 @miyoki_labs でそろえてます。「@miyoki_labs で探して」もらえれば見つかります。
100字
5/
作ったものはポートフォリオにまとめてます → miyoki-labs.com
AI業務自動化・ツール開発の相談もここから受けてます。
これからよろしくお願いします。次から失敗談を出していきます🔧
11字
#個人開発 #AI活用
補足・事実確認
- **出典**:`計画\02_ポジショニング.md`(人格キャノン:ハンドル統一・媒体分担・合言葉「@miyoki_labs で探して」)。
- **各ツイート字数**:全角で概ね100〜120字(上限280内)。投稿前に軽く見て詰まっていれば改行調整。
- **事実確認ポイント**:
  - `@nompass` は確保済みアカウント(STATUS「Instagram/X @nompass 確保済み」)。X本アカウントの正式ハンドルは投稿前に確認(bioの相互リンク先)。
  - 実績列挙は棚卸しの範囲内・誇張なし。「Nompassが成功」とは書かない(=「立ち上げて動かしてる」止まり)。
- **CTA**:3で実績、5でポートフォリオ+受託相談(中盤・末尾の2箇所)。
- **プロフィール整備**:bio差し替え+固定ポスト設定は本人手動(アイコン=人物・表示名=Miyoki Labsは既存設定を維持)。

x-domain-koshinryo-hikaku-kokuchi

Xドラフト2026-07-05
495字
**1/4**
「取得0円」のドメイン、更新の請求額を見てゾッとしたことありませんか?

私は事業用と個人用で2つのレジストラを併用していて、同じ.comでも実質支払額に年900円以上の差があると知りました。

**2/4**
たとえばお名前.comは表示価格とは別に「サービス維持調整費26%」が乗って、.com更新は実質約1,622円。

一方Cloudflare Registrarは原価販売で$10.46(約1,570円)。この差、知らないと毎年払い続けます。

**3/4**
ただしCloudflareも一筋縄ではなくて、最初は決済が全滅(Google Pay・PayPal・カード全部NG)。

国内レジストラへの迂回まで検討した後、後日再試行したらあっさり通って拍子抜けしました。

**4/4**
そんな実体験ベースで、主要7サービスの更新料を「実質支払額」で比較した記事を書きました。10年の総コスト試算と失敗しない移管手順つき。更新月が来る前にどうぞ👇
https://blog.miyoki-labs.com/blog/domain-koshinryo-hikaku
107字
#ドメイン #個人開発(4ツイート目の末尾 or リンクの前に1〜2個。多用しない)

## 画像プロンプト

なし(4ツイート目はURLのOGPカードが画像代わり。ブログ側のOGPが出るか投稿前にプレビュー確認)
補足・事実確認
- **出典**:ブログ記事 `個人\miyoki-blog\src\content\blog\domain-koshinryo-hikaku.md`(2026-07-05公開)+ seeds-miyoki「ドメイン取得、結局どこが得?」(2026-07-04・一部使用済み)
- **事実確認ポイント**:①維持調整費26%と実質約1,622円 ②Cloudflare $10.46(約1,570円) ③決済全滅→再試行で通過 — いずれも記事本文と一致済み。数字は記事とズレないこと
- **予約**:月曜 7:30(X純正予約)。リレー方式の告知①(正=`voice-kit\03_posting_workflow.md`)
- **公開後にやること**:`ideas\公開台帳.md` に X告知の行を1行追記(クロスポスト追跡)

X 告知スレッド(Zenn初投稿の公開時に使う)

Xドラフト2026-07-01
135字
AIと一緒に自分のポートフォリオを作って公開しました🛠

でも「AIで一瞬」みたいな話より、"詰まって直した過程"のほうが役に立つと思うので、ハマったところを全部Zennに書きました。

Cloudflare Pages × Next.js で個人開発してる人向けです👇
149字
ハマりどころ抜粋:

・next-on-pages が Windowsで動かない → 全ページ静的なら output:"export" で回避
・日本語が単語の途中で改行される → keep-all じゃなく auto-phrase
・OG画像はAI生成より「実ロゴ+sharpで合成」が早くて確実
132字
さらに、

・SPA遷移で fade-in 要素が消える → usePathname で監視を張り直し
・public化の前に秘密情報スキャン → 別リポジトリ履歴の Stripe鍵混入を発見して冷や汗

"完璧じゃないけど、まず動かして公開" でやった記録です。
160字
記事はこちら👇
[ZENN_URL]

ポートフォリオ本体:https://miyoki-portfolio.pages.dev

同じく Cloudflare Pages で個人開発してる方、auto-phrase と公開前スキャンだけでも試す価値あります。

#個人開発 #駆け出しエンジニア #Cloudflare