フロントエンド中心キャリアでの技術スタック変遷

フロントエンド中心キャリアでの
技術スタック変遷

マネージドサービスと共に
(できるだけ手抜きで)
歩んできた軌跡とこれから

YasunoriMATSUOKA

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

自己紹介

  • YasunoriMATSUOKA
  • 新潟拠点・フルスタックエンジニア
  • キャリアの軸はフロントエンド
  • バックエンド・インフラは コスパよくマネージドなサービスを使い倒してきた スタンス
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

今日のアジェンダ

  1. キャリアで触れてきた技術スタックの変遷(時系列)
  2. 各フェーズでの学びと、つらみ
  3. フロントエンドFW / ホスティング / インフラ / バックエンドの変遷整理
  4. 最近気になっている組み合わせ
  5. まとめ — 最近思うこと

※ 個人の体験・主観です。各技術への dis ではなく、当時の自分から見えた風景としてお聞きください 🙏

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

Part 1

キャリアで触れてきたスタックの変遷

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

① 個人開発時代 (1/3) — スタック概観

Vue.js Cordova JavaScript Monaca Onsen UI
Stack: JavaScript / Vue.js / Monaca(Cordova) / Onsen UI
Backend: 全部ブロックチェーンに任せる
  • ブロックチェーン関連の ウォレットアプリ を個人で作っていた
  • Monaca + Cordova でクロスプラットフォーム化
  • リリースには至らなかったが、ここで出会ったブロックチェーン系スタートアップに転職
  • フロントエンドエンジニアとしてのキャリアがここからスタート 🚀
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

① 個人開発時代 (2/3) — 構成

mermaid diagram

バックエンドサーバーは持たず、署名済みトランザクションをパブリックチェーンに直接投げる構成。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

① 個人開発時代 (3/3) — コード(送金 UI の素朴版)

// MetaMask / ウォレットプロバイダ経由の送金。Vue 側から呼び出してビューに bind
async function send(to: string, amountEth: string) {
  const provider = new ethers.providers.Web3Provider(window.ethereum)
  await provider.send('eth_requestAccounts', [])
  const signer = provider.getSigner()
  const tx = await signer.sendTransaction({
    to,
    value: ethers.utils.parseEther(amountEth),
  })
  return tx.wait()
}
個人開発が次のキャリアの扉を開く。技術選定よりも「動くものを出して人に見せる」ことと「リアルな場でそれを発信する」ことが大事だった。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

② SES 1社目 — Nuxt.js (1/3)

JavaScript Nuxt Vue.js Bulma VeeValidate
Stack: JavaScript / Nuxt.js (Vue.js) / Bulma / VeeValidate
  • 多言語対応が必要な 大規模 SPA
  • 1フォームに要素100個超、i18n キー無数、バリデーションロジックがカオス
  • TypeScript ではなく JS なので型支援ゼロ
  • あらゆるエッジケースで表示・ロジックのバグが出まくり、矛盾点と戦う日々
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

② SES 1社目 (2/3) — 構成

mermaid diagram

バリデーション・条件分岐・i18n キーがすべてフロント側に集中、しかも JS で型ナシ。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

② SES 1社目 (3/3) — VeeValidate 当時のフィーリング

// VeeValidate v2 当時。フィールドが 100 個あると、こういう shape が
// 100 倍に膨れ上がる。型がないので「どこに何が入るか」をひたすら覚える
export default {
  data: () => ({
    form: { name: '', email: '', addr: '', /* ...×100 */ },
  }),
  computed: {
    rules() {
      return {
        name:  'required|min:1|max:100',
        email: 'required|email',
        addr:  this.form.country === 'JP' ? 'required|jp_postal' : 'required',
        /* ...×100 */
      }
    },
  },
}
今 TS に慣れた状態だと、たぶんもう同じ仕事はできない…
でも、ここで「型がないこと」のしんどさを身体で覚えた。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

③ SES 2社目 — jQuery × Spring Boot (1/3)

JavaScript jQuery Kotlin Spring MySQL
Front: JavaScript / jQuery
Back: Kotlin / Spring Boot / MySQL
  • カオスな jQuery 実装に苦しみながらフロントエンド開発
  • 一部はバックエンド(Kotlin / Spring Boot)のバグ修正にも踏み込む
  • DOM を直接いじるアーキテクチャの限界 を実感
  • 一方で、Spring Boot の堅牢さ と Kotlin の書き味は印象的だった
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

③ SES 2社目 (2/3) — 構成

mermaid diagram

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

③ SES 2社目 (3/3) — フロント側 vs バック側

// jQuery: DOM を直接書き換える。複雑な分岐が増えると依存が見えなくなる
$('#btn').on('click', function () {
  $('#total').text('計算中...')
  $.post('/api/calc', { x: $('#x').val(), y: $('#y').val() }, function (res) {
    $('#total').text(res.value)
    if (res.warning) $('#warn').show().text(res.warning)
  })
})
// Kotlin + Spring Boot: 入力 DTO 〜 Service までが型で繋がる気持ちよさ
@RestController
class CalcController(private val service: CalcService) {
  @PostMapping("/api/calc")
  fun calc(@RequestBody req: CalcReq): CalcRes = service.run(req)
}
モダンFW のありがたさは、レガシーを触ったときに初めて分かる。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

④ SES 3社目 — E2E × 画像処理 (1/3)

Python OpenCV scikit-learn Selenium Appium
Stack: Python (OpenCV / scikit-learn) / Selenium / Appium
  • Web & アプリ向け E2E テスト基盤の PoC 開発
  • 高精度スクリーンショット + 画像処理で 自動デグレード検出
  • Selenium / Appium のクセ、待機制御、Flaky テストとの戦い
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

④ SES 3社目 (2/3) — パイプライン

mermaid diagram

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

④ SES 3社目 (3/3) — 差分検出(核となる発想)

# 同じ画面で 2 バージョンのスクショを比較し、変化量で回帰検知
import cv2
import numpy as np

def diff_ratio(a_path: str, b_path: str) -> float:
    a = cv2.imread(a_path, cv2.IMREAD_GRAYSCALE)
    b = cv2.imread(b_path, cv2.IMREAD_GRAYSCALE)
    if a.shape != b.shape:
        b = cv2.resize(b, (a.shape[1], a.shape[0]))
    diff = cv2.absdiff(a, b)
    return float(np.count_nonzero(diff > 10)) / diff.size
E2Eテストの「ちゃんと動かし続ける」ことの難しさを痛感。
ビジュアルリグレッションは銀の弾丸じゃない。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑤ ブロックチェーンSPの自社開発 (1/3) — スタック

TypeScript Angular RxJS Tailwind Firebase Algolia GCP Stripe GitHub Actions Go Docker Vultr SendGrid BigQuery Cosmos SDK
Front: TypeScript / Angular / RxJS / Tailwind CSS
BaaS: Firebase (Hosting / Functions / Firestore / Storage) / Algolia
GCP: BigQuery / Secret Manager
SaaS: Stripe / SendGrid / GitHub Actions
Chain: Go / Cosmos SDK / Docker / VPS (Vultr)
  • コスパよくスケールする SaaS をゼロから立ち上げ
  • 検索 / 決済 / メールを マネージドで組む ノウハウ蓄積
  • 今の自分の礎になった現場
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑤ ブロックチェーンSPの自社開発 (2/3) — 構成

mermaid diagram

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑤ ブロックチェーンSPの自社開発 (3/3) — Firestoreトリガーの勘所

// onCreate トリガーで集計を更新。重複起動でカウントが二重に進むのを防ぐ
import { onDocumentCreated } from 'firebase-functions/v2/firestore'
export const onPostCreate = onDocumentCreated('posts/{id}', async (event) => {
  const id = event.params.id
  await db.runTransaction(async (tx) => {
    const seenRef = db.doc(`stats/global/seen/${id}`)
    const seen = await tx.get(seenRef)
    if (seen.exists) return                                       // 冪等ガード
    tx.set(seenRef, { at: FieldValue.serverTimestamp() })
    tx.update(db.doc('stats/global'), { count: FieldValue.increment(1) })
  })
})
トリガー連鎖と冪等性の規律が無いと、簡単にデータが壊れる。
シミュレータ重い・セキュリティルール厳しい・クエリも貧弱…
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑥ NPO法人の自社開発 — 初React (1/3)

TypeScript React Firebase
Stack: TypeScript / React / Firebase 系
  • それまで Vue.js / Angular を中心に書いてきた身として
  • React 特有のクセ に翻弄されまくる
    • 関数コンポーネントの再レンダリング、クロージャ、useEffect 依存配列…
    • 「Reactの気持ち」を考えないと書けない
  • Vue / Angular の宣言的さに慣れた身には、衝撃の連続
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑥ NPO法人 (2/3) — 構成

mermaid diagram

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑥ NPO法人 (3/3) — useEffect の罠

function Counter({ id }: { id: string }) {
  const [count, setCount] = useState(0)

  // ❌ id が変わっても fetch が再実行されない
  useEffect(() => {
    void fetchCount(id).then(setCount)
  }, [])

  // ✅ 依存配列に id を入れる。これを忘れ続けるとデータが古いまま固まる
  // useEffect(() => { void fetchCount(id).then(setCount) }, [id])

  return <div>{count}</div>
}
React は「文法」より「メンタルモデル」のFW。
JS / TS の知識だけでは戦えない。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑦ Svelte をちょっと触る (1/2)

Svelte
Stack: Svelte(ブロックチェーン関連サービス)
  • ブロックチェーン関連の案件で少しだけ Svelte に触れた
  • 印象は Vue.js に近い、リアクティビティが言語レベルに溶けていて書きやすい
  • ボイラープレートが少なく、楽しい書き味
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑦ Svelte (2/2) — リアクティブの感覚

mermaid diagram

<script>
  let count = 0
  // 自動で再評価される。React の useMemo 相当が文法に溶けている
  $: doubled = count * 2
</script>

<button on:click={() => count++}>+1</button>
<p>{count} × 2 = {doubled}</p>
コンポーネントFWの「書き味」には、ちゃんと差がある。Svelte は静かに良い。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑧ フリーランス — Next.js × Vercel × Supabase (1/3)

TypeScript Next.js Vercel Supabase Terraform GCP
Stack: TypeScript / Next.js / Vercel / Supabase
必要に応じて: Terraform / GCP (Cloud Run など)
  • Next.js / Vercel / Supabase の組み合わせに本格的に入る
  • Supabase は PostgreSQL ベース → Firestore からの移行で…
    • SQL クエリの柔軟さに感動
    • DB Schema マイグレーションの堅牢さに感動
  • このあたりから AI に書かせる比重が増えてくる(Cursor 等)
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑧ Next + Vercel + Supabase (2/3) — 構成

mermaid diagram

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑧ Next + Vercel + Supabase (3/3) — Supabase + RLS で読む

// auth.uid() を前提にした RLS が設定済みなら、クライアントから直接 select で OK
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(url, anonKey)

const { data, error } = await supabase
  .from('posts')
  .select('id, title, author:profiles(name)')
  .eq('is_published', true)
  .order('created_at', { ascending: false })
  .limit(20)
RDB の「当たり前」が、こんなにありがたいとは。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑨ 友人 SaaS — Vercel × Supabase (1/3)

TypeScript Next.js Vercel Supabase Stripe
Stack: TypeScript / Next.js / Vercel / Supabase
AI: Cursor → Cline → Claude Code / Codex / Gemini

管理が楽な恩恵は大きい、でも…

  • Vercel の固定コストの重さがじわじわ効いてくる
  • Supabase 認証が DB Schema に与える暗黙の影響が大きく、ORM 連携でつらみ
  • Row Level Security の運用が想像以上にしんどい
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑨ 友人 SaaS (2/3) — 構成

mermaid diagram

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑨ 友人 SaaS (3/3) — RLS ポリシーの書き味

-- profiles テーブル: 自分の行だけ select / update できる
create policy "own row select"
  on public.profiles for select
  using (id = auth.uid());

create policy "own row update"
  on public.profiles for update
  using (id = auth.uid())
  with check (id = auth.uid());

-- これを join 先 / 集計 / 隣接テーブルに全部書き続ける運用がしんどい
「楽」と「コントロールできる」は別物。マネージドの粒度はちゃんと選ぶ必要がある。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑩ 友人 SaaS — Cloudflare Workers × Supabase (1/3)

TypeScript Next.js Cloudflare Workers Supabase
Stack: TypeScript / Next.js / Cloudflare Workers / Supabase
  • Cloudflare Workers の コスパに驚愕 ⚡
  • 一方で、Supabase の コスト感の重さ が相対的に目立つように
  • 「フロント+エッジ+DB」のバランスを再考するきっかけに
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑩ CF Workers × Supabase (2/3) — 構成

mermaid diagram

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑩ CF Workers × Supabase (3/3) — Hono on Workers

import { Hono } from 'hono'
import { createClient } from '@supabase/supabase-js'

type Env = { Bindings: { SUPABASE_URL: string; SUPABASE_ANON: string } }
const app = new Hono<Env>()

app.get('/posts', async (c) => {
  const supabase = createClient(c.env.SUPABASE_URL, c.env.SUPABASE_ANON)
  const { data } = await supabase.from('posts').select('*').limit(20)
  return c.json(data)
})

export default app
Cloudflare のエッジファーストは、思っていた以上に強い。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑪ 友人 SaaS の移行 — PHP / Laravel / React (1/3)

PHP Laravel React Inertia
Stack: PHP / Laravel / React (+ Inertia)
  • 友人の SaaS の移行作業を手伝う
  • Laravel の成熟したバックエンドフレームワークとしての便利さに感動
    • 認証・ORM・マイグレーション・キュー・ジョブ…全部揃ってる
    • 規約による設計の収束力
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑪ Laravel × React (2/3) — 構成

mermaid diagram

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑪ Laravel × React (3/3) — Controller + Inertia

// Inertia::render で props を返すだけで、React 側に型付きでデータが届く
class PostController extends Controller {
  public function index() {
    return Inertia::render('Posts/Index', [
      'posts' => Post::query()
        ->where('is_published', true)
        ->latest()
        ->take(20)
        ->get(),
    ]);
  }
}
TS エコシステムにはまだ無い、Laravel / Rails 級の統合された成熟がある。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑫ レガシー RN を Expo 化 / モダナイズ (1/3)

React Native Expo
Stack: React Native / Expo / EAS
  • 久々のアプリ開発、特に iOS の本格デバッグはほぼ初体験
  • EAS Build / Update の世界観は思ったより快適
  • コーディング自体は楽しかった
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑫ RN × Expo (2/3) — 構成

mermaid diagram

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑫ RN × Expo (3/3) — チャネル設定

// eas.json: channel と環境を結びつけて OTA 配信を制御
{
  "build": {
    "production": { "channel": "production" },
    "staging":    { "channel": "staging" },
    "preview":    { "distribution": "internal", "channel": "preview" }
  },
  "submit":   { "production": { "ios": { "appleId": "..." } } }
}
…が、開発チームの外側の事情に振り回される時間が長く、
技術以外のところで疲弊した案件でもあった。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑬ 今ココ — Cloudflare ネイティブ (1/3)

Next.js Hono Cloudflare Workers Cloudflare Turso Resend Stripe D1 R2 Better Auth
Front: Next.js / Hono on Cloudflare Workers
Storage: R2
DB: D1(容量が一定)/ Turso(そうでない場合)
Auth: Better Auth
Mail: Cloudflare 完結 / 無理なら Resend
Payment: Stripe
  • シンプルに Cloudflare に乗せる構成を基本線に
  • 「システムコストを極限まで削る」前提のアーキテクチャ選定
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑬ 今ココ (2/3) — 構成

mermaid diagram

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

⑬ 今ココ (3/3) — Hono + D1 + Better Auth

import { Hono } from 'hono'
import { betterAuth } from 'better-auth'

type Env = { Bindings: { DB: D1Database; AUTH_SECRET: string } }
const app = new Hono<Env>()

const auth = betterAuth({ /* D1 アダプタ + 自前のセッション戦略 */ })
app.on(['GET', 'POST'], '/api/auth/*', (c) => auth.handler(c.req.raw))

app.get('/api/posts', async (c) => {
  const { results } = await c.env.DB
    .prepare('select id, title from posts where published = 1 order by id desc limit 20')
    .all()
  return c.json(results)
})

export default app
認証は Better Auth、データは D1 / Turso、配信はエッジ。コスト最適と主導権を両立する構成。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

Part 2

大事にしてきたこと

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

CI/CD パイプライン全体像

mermaid diagram

直列: security check → install。並列: 各 job が install キャッシュ(node_modules)を共有して同時実行。全 pass で merge gate を通過。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

.npmrc — サプライチェーン汚染の入口を塞ぐ

# .npmrc (リポジトリ直下に commit)
ignore-scripts=true                    # postinstall を実行させない(最大の攻撃面)
audit-signatures=true                  # 公式 npm 署名を検証
save-exact=true                        # ^/~ を付けずに固定版で書き込む
engine-strict=true                     # engines.node の範囲外でエラー
package-lock=true                      # lockfile を必須に
fund=false                             # ノイズを抑制
loglevel=warn

# 公開直後の悪意ある version を踏まないよう cooldown を設定(npm v11.10.0+)
min-release-age=7                      # 単位は「日数」(Number)。公開から 7 日経っていない version は install しない

# 組織でプロキシ/ミラーを運用しているなら:
# registry=https://registry.internal.example.com/
postinstall 実行 と 公開直後の悪意ある version が npm 系インシデントの 2 大入口。ignore-scripts=truemin-release-age で両方塞ぐ。min-release-agenpm v11.10.0+ (2026-02)、単位は 日数の整数、個別除外設定は無い。Renovate 側にも cooldown を効かせると二重防御(後述)。
pnpm はもう一歩楽: v10.26+ から strictDepBuilds=true がデフォルトで、install scripts は全 deny。必要な package だけ allowBuilds に列挙する アローリスト運用がフレームワーク標準で組み込み済み。
ただし pnpm の minimum-release-age は単位が 「分」なので要注意 (7 日 = 10080)。個別除外は minimum-release-age-exclude で可能。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

GitHub Actions: Lint

# .github/workflows/lint.yml
name: Lint
on: { pull_request: { branches: [main] } }
concurrency:
  group: lint-${{ github.ref }}
  cancel-in-progress: true
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'npm' }
      - run: npm ci --ignore-scripts
      - run: npm run lint
  • ツール: ESLint / Biome / oxlint から選ぶ(最近は Biome / oxlint の高速さが魅力)
  • npm ci --ignore-scriptsCI でも postinstall を抑止
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

GitHub Actions: Format

# .github/workflows/format.yml
name: Format
on: { pull_request: { branches: [main] } }
jobs:
  format:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'npm' }
      - run: npm ci --ignore-scripts
      - run: npm run format:check     # prettier --check . or biome format .
  • ツール: Prettier / Biome
  • ローカルでも lint-staged / lefthook の pre-commit hook で整形を強制すると CI 失敗が激減
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

GitHub Actions: TypeCheck

# .github/workflows/typecheck.yml
name: TypeCheck
on: { pull_request: { branches: [main] } }
jobs:
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'npm' }
      - run: npm ci --ignore-scripts
      - run: npx tsc --noEmit         # or `npm run typecheck`
  • tsc --noEmit型エラーゼロを merge 条件
  • Monorepo は tsc -b でプロジェクト参照を一括チェック
  • Next.js なら next build 内でも型検査されるが、別ジョブで先に弾く方が速い
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

GitHub Actions: Build

# .github/workflows/build.yml
name: Build
on: { pull_request: { branches: [main] } }
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'npm' }
      - run: npm ci --ignore-scripts
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with: { name: build-output, path: dist, retention-days: 7 }
  • 本番ビルドが通る = デプロイ可能性の保証
  • artifact を upload しておけば後続の E2E ジョブで実 build を流せる
  • Cloudflare Workers なら wrangler deploy --dry-run で本番に近いビルドを検証可能
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

GitHub Actions: Unit Test

# .github/workflows/unit-test.yml
name: Unit Test
on: { pull_request: { branches: [main] } }
jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'npm' }
      - run: npm ci --ignore-scripts
      - run: npm run test -- --coverage
      - uses: codecov/codecov-action@v5
        if: success()
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
  • ツール: Vitest / Jest / node:test
  • coverage は merge ブロック条件にせず、レポート提示で十分(数値だけ追っかけても品質は上がらない)
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

GitHub Actions: Component Catalog

# .github/workflows/storybook.yml
name: Storybook
on: { pull_request: { branches: [main] } }
jobs:
  storybook:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'npm' }
      - run: npm ci --ignore-scripts
      - run: npm run build-storybook
      - uses: chromaui/action@latest
        with:
          projectToken: ${{ secrets.CHROMATIC_TOKEN }}
          exitOnceUploaded: true
  • Storybook + Chromatic で UI スナップショット差分を自動検知
  • PR コメントに review URL が貼られ、レビュー前に UI 変化が一目 で分かる
  • 代替: Playwright の screenshot diff を component catalog 代わりに使う構成も
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

GitHub Actions: E2E Test

# .github/workflows/e2e.yml
name: E2E
on: { pull_request: { branches: [main] } }
jobs:
  e2e:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'npm' }
      - run: npm ci --ignore-scripts
      - run: npx playwright install --with-deps chromium
      - run: npm run test:e2e
      - uses: actions/upload-artifact@v4
        if: failure()
        with: { name: playwright-report, path: playwright-report }
…と偉そうに言いつつ、まともにテストを書けるようになったのは AI 支援前提のコーディングができるようになってからかもしれない 🤖
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

Deploy — 生の API Key を GHA Secrets に置かない

# ❌ 悪い例: 長期 credential を Secrets に置きっぱなし
- run: firebase deploy --token=${{ secrets.FIREBASE_TOKEN }}
- run: aws s3 sync ./dist s3://bucket
  env:
    AWS_ACCESS_KEY_ID:     ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  • GHA Secrets は便利だが、ログ書き出し事故 / fork PR / 改ざんされた action / 第三者 reusable workflow … 「漏れる前提」 で考える時代
  • Workload Identity Federation (WIF) / OIDC 連携短命トークン に置き換えるのが現代の正解
  • 流れ: GHA が OIDC で身元証明 (JWT) → クラウド側で検証 → ジョブ実行中だけ有効な credential を払い出し
「秘密を持たない」が最強の漏洩対策。長期 API Key そのものを廃止できないか、まず検討する。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

OIDC 連携の具体例 (1/2) — GCP / Firebase

# .github/workflows/deploy.yml
on: { push: { branches: [main] } }
permissions:
  id-token: write          # OIDC token 発行を許可(使う job だけに絞る)
  contents: read
jobs:
  deploy-gcp:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: google-github-actions/auth@v2     # GCP / Firebase 共通
        with:
          workload_identity_provider: projects/123/locations/global/workloadIdentityPools/gh/providers/gh
          service_account: deployer@my-prj.iam.gserviceaccount.com
      - run: gcloud run deploy api --source=. --region=asia-northeast1
  • GCP に WIF Pool + Provider を作り、Service Account に impersonate 権限を付与
  • Firebase も同じ ADC を読むので firebase deploy を同じ仕組みで動かせる
  • Provider 側で sub == repo:org/repo:ref:refs/heads/main 形式で repo / branch を限定
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

OIDC 連携の具体例 (2/2) — AWS / Cloudflare

# 同じ workflow の続き
  deploy-aws:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::111122223333:role/gh-deployer
          aws-region: ap-northeast-1
      - run: aws s3 sync ./dist s3://bucket
  • AWS: IAM Role の trust policy で token.actions.githubusercontent.com を許可、sub で repo / branch / environment を限定
  • Cloudflare: 現状 OIDC 未対応 → API Token を 最小権限 + 短命 + IP 制限、漏洩時の即時 rotate 手順を用意しておく
  • どのクラウドでも permissions: id-token: write使う job だけ に絞ることが最重要
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

継続的な依存更新 — Dependabot / Renovate

放置すると依存はあっという間に古び、脆弱性パッチが当たらない…

  • Dependabot: GitHub 純正。.github/dependabot.yml で設定。シンプル運用に最適
  • Renovate: 高機能。グルーピング・スケジュール・自動 merge・preset 流用が強力。エンタープライズなら定番
  • CI が全部通った PR だけ自動 merge が現実解(lint / typecheck / unit / e2e の全 gate を信用する)
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule: { interval: weekly }
    open-pull-requests-limit: 10
    groups:
      minor-and-patch:
        update-types: [minor, patch]
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

Dependabot / Renovate のサプライチェーン対策

最近の npm 系インシデントは 公開直後の悪意あるバージョン が起点。「速く追従しない」勇気が要る。

  • 新リリース直後は cooldown — Renovate の minimumReleaseAge で 3〜7 日待機。悪意ある version は数日で発覚 / yank されることが多い
  • メジャーは必ず人間レビュー / マイナー・パッチは自動 merge
  • グルーピング — 同種パッケージをまとめて 1 PR に。レビュー認知負荷を下げ、見落としを減らす
  • 並行して socket.dev / Snyk / GitHub Advisory Database で外部シグナルを取り込む
// renovate.json
{
  "extends": ["config:recommended"],
  "minimumReleaseAge": "3 days",
  "packageRules": [
    { "matchUpdateTypes": ["minor", "patch"], "automerge": true },
    { "matchDepTypes": ["devDependencies"], "minimumReleaseAge": "1 day" }
  ],
  "vulnerabilityAlerts": { "enabled": true, "minimumReleaseAge": "0 days" }
}
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

Part 3

フレームワーク・インフラの変遷を整理する

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

フロントエンドFW比較 — Angular / Svelte / Nuxt / Next

FW 印象
Angular ❤️ 好き。ng update2バージョンまで1コマンド対応。半年に1回のメジャーアップデート、地に足のついた改善
Svelte 書き味が楽。Vue.js に近い印象。良かった
Nuxt (Vue.js) 昔触って以来。また触りたい
Next.js (React) 文法は少ないが「Reactの気持ち」を考え続ける必要あり…(個人の感想)
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

Angular について(推し)

  • ng update というメジャーバージョンアップ用コマンドが公式で提供されている
  • 2バージョンアップまで1コマンドで対応可能
  • 半年に1回の定例的なメジャーアップデートが安定したメンテナンス計画を可能にする
  • 入る修正が 開発者にとってありがたい、地に足のついた内容
  • 継続的にアプリを維持していくのがめちゃくちゃ楽

昔 Angular で挫折した方も、ぜひもう一度触ってみてほしいです!

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

React について(個人の感想)

  • 文法的に覚えることは少ない(JS/TS + α)
  • でも 「Reactの気持ち」をすごく考えて書かないといけない
    • …ある種のメンヘラフレームワーク(個人の感想です)
  • ただし、現代の AI 支援開発では事実上のデファクトスタンダード
    → きっちり対応できるようにならねば、と思いながら…
Next.js / React の Server Side 機能には
鬼のように昨今脆弱性が見つかっており、対応もつらい…
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

フロントエンドホスティングの変遷

Firebase Hosting / GitHub Pages
            ↓
       Vercel / Netlify
            ↓
       Cloudflare Workers
  • 初期: 静的ホスティングで十分
  • 中期: Vercel / Netlify で DX が劇的に向上
  • 現在: Cloudflare Workers でエッジ+コスト最適

→ 「PaaS の固定費の重さ」を回避したい現場では、Cloudflareの存在感が大きいが、現時点では特定リージョン限定のデータ保管がアメリカ以外では不可能

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

インフラ / DB / ストレージの変遷

Firebase (GCP)
   ↓
Supabase (+ 必要に応じて Terraform × GCP / AWS)
   ↓
Cloudflare ( Workers / D1 or Turso (Cloudflare外) / R2 / KV )
  • Firebase 期: NoSQL × トリガーで楽だが、規律が崩れるとカオス
  • Supabase 期: PostgreSQL の安心感、でもコストとRLSが悩み
  • Cloudflare 期: エッジ + 低コスト、ストレージ・DB・KV まで一貫
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

バックエンドフレームワークの変遷

Express
   ↓
NestJS (など)
   ↓
Hono
  • Express: 軽量だが構造化は自分次第
  • NestJS: Angular ライクな構造化、エンタープライズ感
  • Hono: エッジファースト、Cloudflare Workers と最高に相性が良い
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

最近面白いと思った組み合わせ — Hono × Inertia 🤔

Inertia.js: Laravel / Rails 界隈で使われてきた印象の "モノリシックSPA" レイヤー

これを Hono と組み合わせる と…

  • サーバーサイド ⇄ クライアントサイドで 型をシンプルに引き回せる
  • クライアントから データフェッチの複雑さが消える
  • ルーティング・データ受け渡しの設計コストがガクッと下がる
SPA の「データフェッチ地獄」を回避する、新しい現実解になり得る組み合わせ。
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

Part 4

まとめ

YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

まとめ — これまでとこれから

  • 「楽に・安く・ほどほどに」 をずっと大事にしてフロント〜バック〜DB/ストレージを選んできた
  • 流行り廃りが大きい世界で、結果的にいろいろ触れたのは面白かった
  • ただし、器用貧乏感 は否めない
  • そして、TS 系エコシステムの限界 も感じる今日この頃
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

最近思うこと

  • PHP / LaravelRuby on Rails のような
    成熟したエコシステムの魅力を改めて感じる
  • npm 系エコシステムの 治安の悪化(サプライチェーン問題、脆弱性…)
  • 継続的にアプリ開発・維持する仕事は、ますます厳しくなっている
  • それでも、AI 支援で何とかやっていけるのか…
    なかなか考えさせられる日々です 🤖
YasunoriMATSUOKA
フロントエンド中心キャリアでの技術スタック変遷

ありがとうございました 🙏

ご質問・ツッコミ・「これも触ってみては?」
大歓迎です

YasunoriMATSUOKA

YasunoriMATSUOKA