このページについて
最終更新: 2026-09-04 / 現在: v1.0(外部サービスからの自動取り込み)を実装中
転職用のポートフォリオとして、個人でWebアプリ「Hubpin」を作っています。
Zenn・ブログ・X・GitHub に散らばった自分の発信を、1か所に集めて見せるハブサイトです。
2026年8月17日に v0.5(カードの登録・編集・並び替えと、公開ページ)を公開しました。
いまは、外部サービスから記事を自動で取り込む v1.0 を作っています。
設計と判断は自分で行い、実装も原則自分で書いています。AIには手順書・レビュー・調査を任せています。
記事は起きた順に並べています。上から読むと、題材を決めるところから順に追えます。
載せているのは公開済みの記事だけで、書きかけの分は公開のたびに足していきます。
ほかの作品の記録は 個人開発の記録 にまとめています。
- 🔗 公開先: https://hub.mt-tk.com/matsutake
- 🔗 リポジトリ: https://github.com/Matsutake29/hubpin
- 技術構成: Next.js 16(App Router)/ TypeScript / Supabase(PostgreSQL・認証・RLS)/ Tailwind CSS / Vercel
この作品で決めたこと
作る前に、外せない3つを決めました。迷ったらここに戻ります。
1. 公開ページは静的サイト並みの速さを保つ(事前生成する)
2. カードの追加・編集・並び替えができる(認証つき)
3. 外部の発信を自動で取り込む
1. 題材を決めるまで(2026年7月下旬〜8月上旬)
1作目を公開したあと、2作目の題材を決めるのに2週間ほどかかりました。
案を出しては落とし、「自分にしか作れない」と「自分に作れる」が別のことだと分かった時期です。
- ポートフォリオの題材を21案出し直したら、結局最初の案に戻ってきた話
- 発信が5つの媒体に散らばっていたので、まとめるものを作ることにしました
- 参考にしていたサービスと、自分がやろうとしていたことは向きが逆でした
- 「リアルタイム更新」の正体は cron と静的ファイルのコミットだった
- 作りたいものが決まってから、データが手に入らないと分かりました
- 「使ってもらえる人がいる」を強みにしようとして、2回同じ罠に入った
- ポートフォリオの題材を1日で3案とも落として、4案目に決まりました
- 「自分にしか作れない題材」を、作れる形に組み替えました
- 機能は真似ていい。真似ないのはコードとデザインと名前でした
- ポートフォリオで機能を盛る罠——CRUDを4つ作っても示せるのは1つ
- インフラを足しても主張は増えない——ポートフォリオにAWSを入れなかった話
- 仕事は本名、SNSはハンドルネーム。この使い分けに名前がついていました
2. 計画を立てる(8月上旬)
工程表と見積を作った時期です。
「1日に何時間使えるか」を外したところから始まり、AIとの分担の形もここで決めました。
- 「1日何時間やれるか」を自己申告で2回続けて外した
- 「1日に何時間使えるか」を2回外して、工程の見積も逆方向に外しました
- AIセッションを2つ並行させて同じ資産を育てたら、書いたルールが改善されて返ってきた
- AI依存を避けるために外部DBへ、は依存先が移るだけでした
- AIとの対話ログは、作業時間の証拠になりませんでした
3. 設計(8月4日〜6日)
URLの構造・画面のレイアウト・データの形を決めた時期です。
手書きで組んでいたレイアウトが、データを可変にした瞬間に破綻して、作り直しました。
- 選択肢が全部同じ形をしていたら、問題設定のほうを疑います
- ER図が読めなかった原因は、知識不足ではありませんでした
- 要約したら、検討していない選択肢を検討したことになっていました
- 名前を変えたら、半月前に下していた設計判断に説明がついた
4. 実装 v0.5(8月6日〜13日)
作りながら踏んだことを、テーマごとにまとめています。
どれも上から順に読むと、同じ勘違いがどう解けていったかが追えます。
手順書と見積のこと(9本)
実装の前に手順書を書く運用にしています。
書いてあることは速く終わり、書いていないことは見積どおりかかりました。
その手順書自体が罠を仕込んでいたこともあります。
- 「何から書けばいいか分からない」の正体は、完成形を一気に書こうとしていたこと
- AIと開発するとき、速さを選ぶと「説明できないコード」が残る
- 見積に「理解する時間」が入っていなかった
- 罠を消すために書いた手順書が、自分で罠を仕込んでいた
- 手順書に書いてあることは速く、書いていないことは見積どおりかかりました
- 手順書に書いた「こうなったら成功」が、バグを見つけてくれました
- 罠の記録には、2つの使い道がありました
- 分からないことが3つある工程が、1つの工程より速く終わりました
- 「1枚に全部写っている」スクリーンショットのほうが、証拠として弱かった
型のこと(9本)
「型を書いた」と「型が守ってくれる」は別のことでした。
書いた型が誰にも検証されていない状態から始まって、**AIが書いた存在しない値が
すべての検査を通り抜ける**ところまで行きます。
1. 壊れていないコードに出た型エラーが、指していたのは設計の穴でした
2. 「エラーが出ないまま間違っている」を4件並べたら、2種類に分かれました
3. 値がある保証だと思って付けていた「!」は、確認しないと決めた印でした
4. 「item: Item」と書いた型は、誰にも検証されていませんでした
5. DBから型を生成したら、1箇所だけ手書きより弱くなりました
6. AIが書いた存在しない値が、型検査もLintもDBの型も通り抜けました
7. 文字数の上限を決めようとしたら、切っている場所がどこにもありませんでした
8. 1つの数字で足りたのは、2つの基準がたまたま近かっただけでした
9. asが危険かどうかは、書いた場所で決まった
レンダリングとビルド(5本)
'use server' や 'use client' が何を宣言しているのかを、取り違えたまま進めていました。
最後は「静的に焼く」と決めた判断が、ビルドを外部サービスに依存させるところまで繋がります。
1. use server が宣言していたのは、実行場所ではありませんでした
2. Client Component だから動的だと思っていたページが、静的に焼けていました
3. 静的生成にした瞬間、ビルドが外部サービスに依存し始めました
4. 開発サーバーと本番ビルドが、同じ言葉で別のことを言っていました
Next.js そのものについては、教材を選ぶところから詰まりました。
権限まわり(RLS・6本)
データベース側で守ると決めたのに、書いたポリシーが1行も読ませてくれないところから始まり、
逆に見せすぎていたことに4日後に気づくまでの話です。
後半の2本は、守れていることを「証明する」側の話になります。
1. RLSポリシーを7本書いても、データは1行も読めませんでした
2. RLSのポリシーを足すほど、見える範囲が広がりました
3. 権限の検証が通ったのは、そのとき他人がいなかったからでした
4. 二重に防御していたら、片方が壊れていることが見えなくなりました
5. RLSが効いている証明に、でたらめなIDは使えなかった
6. updateは0行でもエラーにならないのに、insertはエラーになる
フォームと並び替え
フォームの共通化・入力チェック・並び替えで踏んだことです。順次公開していきます。
準備中です。
5. 公開する(8月16日〜17日)
hub.mt-tk.com に v0.5 を公開した2日間の話です。
公開前のセキュリティレビュー、本番環境の設定を「予想してから測る」こと、
個人サイトのトップを新しいアプリへ向ける切り替えまで含みます。順次公開していきます。
準備中です。
6. v0.5 の振り返り(8月19日〜27日)
公開したあとに、工程表とのズレ・実績時間・「作れたが説明できないコード」を棚卸しした時期です。
順次公開していきます。
準備中です。
7. v1.0 外部サービスからの自動取り込み(8月24日〜)
Blog・Zenn・GitHub から記事を自動で取り込む部分を作っています。
取り込みの処理・毎日の自動実行・取得ログまでが範囲です。順次公開していきます。
準備中です。
技術的な詳細は Zenn に書いています
このページは「何をどう決めたか」の流れを追うためのものです。
再現できる形の技術メモは Zenn に分けています。上から起きた順です。
- TypeScriptが読めなかったのは型のせいではなく、{} が6種類あったからだった
- WordPress の語彙で Next.js(App Router)を読む
- create-next-app に .gitignore を上書きされて気づいた、git init のタイミングの意味
- CIをわざと壊したら、lintが緑のままbuildだけ赤くなった
- 手書きのBento Gridは、CMS化した瞬間に破綻した
- 仕様書に書かれていない判断が、実物のCSSにだけ残っていた
- height: 100% は親の高さを見る。その親の高さは自分で決まっていた
- next/fontで日本語フォントを使うとき、subsetsにjapaneseは書けない
- リンクは<a>、開閉は<button>。カードの種類で要素を出し分けた話
- matcherに「保護したいパス」を書いたら、1時間後にログアウトされた
