1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js + Cloudflareで「サボる」ためのWebツール集を個人開発した

1
Posted at

個人開発で neglegere(ネグレゲレ) というWebサービスを作っています。

コンセプトは、

地味に時間を取られる作業を少しだけ任せて、大事なところに力を残す

です。

最初は、自分の声を使って文章を読み上げる「AIボイス」から始めました。

そこから、

  • 音声加工
  • 背景除去
  • 画像変換
  • PDF
  • OCR
  • 読み上げ
  • 電子サイン
  • マイク
  • カメラ
  • rPPG
  • AI要約
  • 文字起こし

など、ブラウザでできそうなものを少しずつ増やしています。

現在は主に、

  • AIボイス
  • 鬼ボイス
  • 背景メーカー
  • 画像変換
  • 音読アシスト
  • 手書き電子サイン
  • ことばレンズ
  • 騒音チェック
  • コンディションチェック

などを公開しています。

この記事では単なる機能紹介ではなく、

「その処理をBrowser / Cloudflare / GPUのどこで実行するか」

という設計を中心に、個人開発で使っている技術や運用方法までまとめます。


技術スタック

全体はNext.jsを中心に構成しています。

領域 技術
Framework Next.js 16
UI React 19 / TypeScript
CSS Tailwind CSS 4
UI Components shadcn/ui / Radix UI
Component Development Ladle
Next.js Adapter OpenNext for Cloudflare
Runtime Cloudflare Workers
DB Cloudflare D1
ORM Drizzle ORM
Storage Cloudflare R2
Realtime Durable Objects / SSE
Authentication NextAuth
Validation / API Schema Zod / zod-openapi
API Docs Swagger UI
PDF表示 PDF.js
PDF編集 pdf-lib
DOCX Mammoth
OCR Tesseract.js
背景除去 IMG.LY Background Removal
AVIF変換 jSquash
手書き Signature Pad
Voice Clone Qwen3-TTS
AI API OpenAI GPT / Transcription
Unit Test Vitest
E2E / VRT Playwright
Error Monitoring Sentry
Uptime Monitoring Better Stack
Dependency Update Renovate
CI/CD GitHub Actions
AI Coding Codex / Claude Code Pro

一番意識しているのは「この処理、本当にサーバーでやる必要ある?」

neglegereでは、何でもAPIへ送る構成にはしていません。

処理によって大きく3種類に分けています。

軽い処理・プライバシー重視
        ↓
      Browser

認証・保存・外部API
        ↓
Cloudflare Workers

GPUが必要
        ↓
       GPU

現在の役割分担をざっくり書くと、

Browser
├─ 画像変換
├─ 背景除去
├─ PDF表示
├─ OCR
├─ 音声加工
├─ 騒音解析
├─ rPPG
└─ Speech Synthesis

Cloudflare
├─ Next.js
├─ Auth
├─ D1
├─ R2
├─ Durable Objects
├─ Cron
├─ Email
├─ URL Fetch
└─ AI API連携

GPU
└─ Qwen3-TTS

という構成です。

例えば画像変換なら、

画像
↓
Browser
↓
WebAssembly
↓
AVIF / WebP
↓
Download

だけで完結します。

サーバーへアップロードする必要がありません。

一方、Qwen3-TTSのような大きなモデルはブラウザやCloudflare Workersでは厳しいため、GPU側へ逃がしています。


Next.jsをCloudflare Workersで動かす

Next.jsはOpenNextを使ってCloudflare Workersへデプロイしています。

OpenNext for Cloudflare:

ざっくり、

Next.js
↓
OpenNext
↓
Cloudflare Workers

です。

Cloudflare側では、

Workers
├─ Next.js
├─ D1
├─ R2
├─ Durable Objects
├─ Cron Triggers
└─ Send Email

を使っています。


Node.js前提ではなくEdge / Cloudflare前提でライブラリを選ぶ

Cloudflare Workersで動かすため、ライブラリ追加時には、

Node.jsで動くか

だけではなく、

Cloudflare Workersで動くか

Web Standard APIで動くか

Node.js専用APIへ依存していないか

WASMを使えるか

を見るようになりました。

特に画像・PDF・音声系はNode.js用ライブラリが多いため、この違いはかなり大きいです。


Cloudflareのbindingも型生成する

CloudflareのD1やR2などはWranglerから型生成しています。

pnpm cf-typegen

これで、

wrangler.jsonc
↓
wrangler types
↓
cloudflare-env.d.ts

という形になります。

D1、R2、Durable Objectsなどのbindingを手書きで型定義し続けなくてよいので、Cloudflare中心の構成では便利です。


APIはZodからOpenAPIへつなげる

APIではZodをvalidationに使っています。

さらにzod-openapiを使い、API仕様へつなげています。

Zod Schema
↓
Runtime Validation
↓
OpenAPI Schema
↓
Swagger UI

という形です。

Validation用schemaとAPIドキュメントを別々に管理すると、どこかでズレます。

そのため、

実際にAPIが受け付けるschemaをできるだけAPI仕様の元にもする

という構成にしています。

Swagger UIも用意していますが、内部向けの情報も含むため認証をかけています。


お問い合わせにはCloudflare Turnstile

お問い合わせフォームではCloudflare Turnstileを使っています。

Form
↓
Turnstile
↓
Cloudflare Workers
↓
Email

という構成です。

Cloudflare中心でインフラを組んでいるので、この辺りもCloudflare側へ寄せています。


1. AIボイス:GPU処理だけ別系統へ逃がす

AIボイスでは、録音した本人の声を参照して文章を読み上げる音声を生成します。

音声生成にはQwen3-TTSを使っています。

大まかな構成は、

Browser
↓
Cloudflare Workers
↓
D1 + R2
↓
Cron
↓
GitHub Actions
↓
GPU
↓
Qwen3-TTS
↓
Cloudflare
↓
Browser

です。


重い処理をHTTPリクエストの中で待たない

音声生成のような処理を、

POST /generate
↓
GPU処理
↓
数十秒待つ
↓
Response

とすると、タイムアウトや再試行、ページ離脱への対応が面倒になります。

そこで、

POST /generate
↓
D1へJob登録
↓
R2へ必要ファイル保存
↓
Response

までで一旦終了します。

実際の音声生成は非同期ジョブとして扱います。


pendingがあるときだけGPU側を起動する

Cloudflare Cronで待機中ジョブを確認します。

const pendingCount =
  await countPendingVoiceClonesSnapshot(env.recordings);

if (pendingCount === 0) {
  return;
}

await triggerGpuJob();

つまり、

Cron
↓
pendingなし
↓
終了

です。

仕事があるときだけ、

Cron
↓
pendingあり
↓
GitHub Actions
↓
GPU処理

へ進めます。

GPU系は「何も仕事がない時間に動かさない」のがかなり重要でした。


GitHub ActionsはGPU処理の実行ブリッジとして使う

定期処理そのものをGitHub Actionsのschedule任せにはせず、

Cloudflare Cron
↓
GitHub REST API
↓
workflow_dispatch
↓
GPU

という形にしています。

GitHub ActionsはCI/CDだけでなく、外部のGPU処理を起動するためのブリッジとしても使っています。


Durable Objects + SSEで生成完了を通知する

生成状況確認を毎秒pollingするのではなく、SSEを使っています。

Cloudflare Workersは複数instanceに分かれる可能性があるため、接続先をDurable Objectsへ集約します。

                 Browser A
                     ↑
                     │
Complete API → Durable Object → Browser B
                     │
                     ↓
                 Browser C

これで生成完了イベントをブラウザへpushできます。

ページを閉じている場合もあるので、メール通知も併用しています。


2. 鬼ボイス:生成AIではなくWeb Audio API

録音した声を鬼っぽく変える機能もあります。

こちらは生成AIを使っていません。

Microphone
↓
Recording
↓
Web Audio API
↓
Pitch / Reverb / Effect
↓
Playback

です。

つまり、

今ある音声を加工する
→ Browser

新しい音声を生成する
→ Qwen3-TTS

と分けています。

AIでできる処理でも、普通の信号処理で十分ならAIを使わない方が安くて速いです。


3. 背景メーカー:AIモデルをブラウザで動かす

背景メーカーでは、

  • 背景透過
  • 背景色変更
  • 背景画像差し替え

などをブラウザ側で処理しています。

背景除去にはIMG.LY Background Removalを使っています。

特徴は、

画像をサーバーへ送らずにモデルをブラウザで実行する

ことです。

Image
↓
Browser
↓
Background Removal Model
↓
RGBA
↓
Canvas
↓
Output

です。


重い処理は必要になってからロードする

背景除去ライブラリはdynamic importしています。

const { removeBackground } =
  await import("@imgly/background-removal");

つまり、

ページを見ただけ
→ Model不要

背景除去を実行
→ Model Load

です。


ローディングUIを描画してから重い処理へ入る

ブラウザでWASMやAIモデルを読み込むと、初期化時に重くなることがあります。

そのため、

クリック
↓
Processing表示
↓
一度paint
↓
Model Load
↓
処理

という順にしています。

ユーザーからすると、

押したのに何も反応しない

時間を減らせます。


背景除去結果を使い回す

背景除去を一度実行したら、その結果をRGBAとして保持します。

Background Removal
↓
RGBA
   ├─ 透明背景
   ├─ 白背景
   ├─ 色背景
   └─ 画像背景

背景を変更するたびにAIモデルを再実行する必要はありません。


非同期処理の古い結果を捨てる

画像系UIでは、

白背景
↓
すぐ赤背景
↓
さらに青背景

のような操作があります。

処理完了順が入れ替わると、最後に選んだ色ではなく古い結果が表示される可能性があります。

そこでversionを持たせ、

const version = ++renderVersionRef.current;

const result = await render();

if (version !== renderVersionRef.current) {
  return;
}

のように、

最新操作以外の結果を捨てる

ようにしています。


Object URLも必ず解放する

画像処理では、

const url = URL.createObjectURL(blob);

を多用します。

不要になったら、

URL.revokeObjectURL(url);

で解放します。

画像を何度も編集するSPAでは、こういう細かいメモリ管理も効いてきます。


4. 画像変換:WebAssemblyでWebP / AVIFへ

画像変換では、

PNG / JPEG
↓
WebP / AVIF

へ一括変換できます。

AVIFにはjSquashを使っています。

File
↓
Decode
↓
ImageData
↓
WASM Encoder
↓
AVIF Blob

という流れです。

サーバーへアップロードしないため、

  • サーバーCPU不要
  • 転送量不要
  • 元画像を送信しない

というメリットがあります。


5. 音読アシスト:入力形式をTextへ正規化する

音読アシストでは、

  • 直接入力
  • TXT
  • PDF
  • DOCX
  • PDF URL
  • Webページ

などを読み込めます。

PDFにはPDF.js。

DOCXにはMammoth。

を使っています。

入力は違っても最終的には、

File / URL / Text
↓
Text
↓
Sentence[]
↓
Speech Synthesis

へ正規化しています。


長文を一気に読み上げない

長い文章を1つのSpeechSynthesisUtteranceへ渡すと、環境によって不安定になります。

そのため文章を分割します。

Sentence 0
Sentence 1
Sentence 2
Sentence 3

現在位置を持たせることで、

  • 読んでいる文をハイライト
  • 途中から再生
  • タップした文から再生
  • 自動スクロール

などができます。


URLの文章は必要なときだけAI要約

Webページはまず本文を取得します。

その上で、

全文を読む

または、

GPTで要約
↓
短くして読む

を選べるようにしています。

普通に読めるものまで毎回AIへ投げないのも、コスト面で重要です。


6. ことばレンズ:PDF.jsで独自Viewerを作る

ことばレンズでは、PDFやWebページを読みながら、その場で分からない単語を調べられます。

PDF.jsを単純に埋め込むのではなく、独自Viewerを作っています。

PDF
├─ Page Render
├─ Thumbnail
├─ Zoom
├─ Fit Width
├─ Fullscreen
├─ Mouse Lookup
├─ Touch Selection
├─ Magnifying Lens
└─ OCR Fallback

全ページを最初から描画しない

100ページあるPDFを全部Canvas化すると重くなります。

そこでIntersectionObserverを使い、

Viewport付近
↓
Render

としています。

少し先のページまで先読みすることで、スクロール時の待ち時間を減らしています。


PDFの座標から単語を推定する

PDF.jsのTextItemには座標情報があります。

ユーザーがクリックした位置とTextItemの矩形を比較して、どの文字列を押したのかを探します。

さらに、

const charIndex = Math.floor(
  ((pageX - item.left) / item.width) *
  item.text.length
);

のように文字位置も推定します。

スマートフォンでは正確な1点タップが難しいため、範囲選択にも対応しています。


日本語PDF特有の空白も補正する

PDFによっては、

日 本 語

のように文字間へスペースが入ります。

日本語script同士の不自然な空白を除去して、

日本語

へ戻してから検索へ使います。


テキストを持っていないPDFだけOCRする

PDFには、

Text Layerあり

と、

スキャン画像だけ

があります。

まずPDFのText Layerを使い、取れなかった場合だけTesseract.jsへfallbackします。

PDF Textあり
→ そのまま使う

PDF Textなし
→ OCR

です。

最初から全ページOCRすると重すぎるため、必要な場合だけ実行しています。


OCRは実行中Promiseまでキャッシュする

OCR結果だけでなく、処理中のPromiseもキャッシュします。

1回目
↓
OCR開始
↓
Promise保存

2回目
↓
同じPromiseを待つ

これで同じ領域に対してOCRが重複実行されるのを防いでいます。


7. 手書き電子サイン:Canvas + pdf-lib

手書きサインにはSignature Padを使っています。

Canvasで書いた署名を画像化し、pdf-libでPDFへ埋め込みます。

PDF
↓
Browser
↓
Signature
↓
位置・サイズ調整
↓
PDFへ合成
↓
Download

までブラウザ側で行います。

サインのためだけにPDFをサーバーへアップロードする必要はありません。


8. 騒音チェック:Web Audio APIでリアルタイム解析

騒音チェックではマイクを使って周囲の音量の目安を表示します。

getUserMedia
↓
AudioContext
↓
GainNode
↓
AnalyserNode
↓
Spectrum
↓
Current / Average / Peak

という構成です。

ただし重要なのは、

校正済み騒音計のdB SPLではない

ことです。

端末によってマイク性能が異なるため、表示値はあくまで周囲の音量を比較するための目安として扱っています。


マイク音声自体は保存しない

保存する場合も、

Average
Peak
Position

などの計測結果だけです。

Microphone Audio
↓
Browserで解析
↓
破棄

です。


iOS Safariでは権限要求を順番にする

マイクと位置情報を同時に要求すると、モバイルSafariで扱いづらいケースがあるため、

マイク許可
↓
計測開始
↓
位置情報取得

の順にしています。

さらに古い非同期位置情報取得が次の測定へ混ざらないよう、測定IDも持っています。


9. コンディションチェック:rPPGをTypeScriptで処理

実験的な機能として、カメラ映像から体調の参考値を見るコンディションチェックもあります。

rPPG、

Remote Photoplethysmography

を使っています。

測定値としては、

  • 心拍
  • 呼吸
  • 心拍間隔の変動
  • 同期傾向
  • リラックス傾向
  • 信号品質

などを扱っています。

※医療機器ではなく、診断目的ではありません。


RGBの微細な変化を見る

フレームごとに、

type RppgSample = {
  red: number;
  green: number;
  blue: number;
  brightness: number;
  motion: number;
  timestamp: number;
};

のような値を取ります。

Webカメラは完全な一定間隔でフレームが来るわけではないため、まず等間隔へresamplingします。

その後RGBを組み合わせてpulse signalを作ります。


数値を出すことより「信用してよいか」の方が重要

rPPGでは、

Heart Rate = 72

という数字自体は作れます。

ただし、

照明が暗い

顔が動いている

カメラ性能が低い

場合、その72に意味がない可能性があります。

そのためsignal qualityやpeak confidenceを計算し、

数値は出せる
≠
表示してよい

と考えています。

品質が足りない指標は無理に表示しないようにしています。


カメラ映像もサーバーへ送り続けない

rPPGも、

Camera
↓
Browser
↓
RGB Sample
↓
Signal Processing
↓
Result

です。

映像ストリーム自体を外部サーバーへ送り続ける必要はありません。


ログインは「サーバーを使うとき」に寄せる

neglegereでは、なるべく最初からログインを要求しません。

例えば、

画像変換
背景除去
PDF
騒音測定
コンディションチェック

など、ブラウザだけで完結するものはログイン不要にしています。

一方、

データを保存する

AI APIを使う

URLをServer側で取得する

生成結果を後から参照する

場合はログインを使います。

考え方としては、

Browserだけ
→ Login不要

Server Resourceを使う
→ Login

です。


AI APIにはGPT系を採用

アプリ内のAI処理にはOpenAIのGPT系APIを使っています。

主な用途は、

  • 長文要約
  • 投稿文生成
  • テキスト処理
  • 音声文字起こし

です。

Claude APIも候補として比較しましたが、現在はGPT系へ寄せています。


文字起こしまで含めるとOpenAIにまとめやすかった

neglegereは音声系の機能が多いため、

LLMだけ

ではなく、

Text Generation
+
Transcription

まで含めて考えました。

構成としては、

Audio
↓
OpenAI Transcription
↓
Text
↓
必要ならGPTで要約・整形

とできます。

文章生成と文字起こしで複数ベンダーを使い分けるより、APIキーや課金管理もシンプルになります。


コスト面でもGPT系のメリットが大きかった

個人開発では、モデルの性能だけでなく1回あたりのコストも重要です。

neglegereでは、

長文
→ 要約

短い入力
→ 投稿文生成

音声
→ 文字起こし

のような比較的小さな処理を何度も行います。

そのため、

必要十分な性能
+
低いAPIコスト

のバランスを重視しました。

Claude APIも比較しましたが、自分の用途では、

文字起こしを含めたAPI構成とコスト面でGPT系のメリットが大きい

と判断しました。


全部をGPTにはしない

AI関連だけ見ても、

文章生成・要約
→ GPT

文字起こし
→ OpenAI Transcription

ボイス生成
→ Qwen3-TTS

背景除去
→ IS-Net系モデル

音声加工
→ Web Audio API

騒音
→ Signal Processing

rPPG
→ TypeScript

と分けています。

「AIでできるからAIを使う」ではなく、普通のブラウザAPIやアルゴリズムで済むならそちらを優先します。


デザインは極力「AIっぽく」しない

開発にはAIをかなり使っていますが、デザインについては逆に、

AIっぽく見えないこと

を意識しています。

生成AIにWebサービスの見た目をそのまま任せると、

強いGradient

Glow

Glassmorphism

大量のCard

抽象的な3D Object

作り込まれすぎた背景

のようなデザインになりやすいと感じています。


特にAI生成画像は背景がAIっぽさを出しやすい

人物だけなら自然でも、

人物
+
部屋
+
小物
+
照明
+
背景装飾

まで全部AIに生成させると、細部まで作り込まれすぎて逆にAIっぽく見えることがあります。

そのため、

実写画像

または

生成した被写体
↓
背景透過
↓
人物・物だけ使う
↓
背景はWeb側で作る

という使い方をよくしています。


AIには「完成画像」ではなく素材を作らせる

AIに一枚の完成したWebデザインを作らせるより、

人物
物
アイコン的な素材

を作らせて、

AI
↓
素材

Web
↓
余白
文字
背景
配置
Responsive Layout

と分けています。

これなら、

  • レスポンシブにしやすい
  • 文言変更に強い
  • 背景色を変えられる
  • AI画像特有の情報量を減らせる

というメリットがあります。


実写も積極的に使う

例えば、

スマートフォンで音を測る

カメラで状態を確認する

ような機能では、イラストより実写の方が使い方を一瞬で理解できます。

画像は単なる飾りというより、

その機能をどう使うかを説明するUIの一部

として使うことが多いです。


AIコーディングはCodexとClaude Code Proを併用

開発には、

  • Codex
  • Claude Code Pro

を使っています。

Codex:

Claude Code:

2つを使っている理由の1つは、

片方へ利用量が集中してレート制限に当たりすぎないようにするため

です。


Git worktreeで並行開発する

並列化そのものはCodexだけでもできます。

重要なのはAIの種類ではなく、作業環境を分けることです。

git worktree add ../project-a feature/a
git worktree add ../project-b feature/b

とすれば、

Worktree A
↓
Codex
↓
Feature A

Worktree B
↓
Claude Code Pro
↓
Feature B

のように独立タスクを並行できます。

Codexを複数session動かす場合もあります。


同じ大きなファイルはなるべく同時に触らせない

並行開発するときは、

Codex
→ Feature A

Claude Code
→ Feature B

のように責務を分けます。

同じ巨大ファイルを両方に触らせると、最後に人間がmerge conflictと戦うことになります。

AIを何個起動するかより、

タスク境界をきれいに切る

方が重要だと感じています。


AI向けルールやSkillsは作り込みすぎない

AI向けのルールを大量に作る考え方もありますが、個人的にはそこへあまり力を入れていません。

モデル自体がかなり速い速度で賢くなっているため、

現在のモデル向けに細かい手順や制約を大量に固定すると、将来モデルの判断能力が上がったときに逆に邪魔になる可能性がある

と考えているためです。

そのため、

Project固有で絶対に守る必要がある制約

は残しますが、

実装手順を細かく全部決める

ことはなるべく避けています。

AIに制約を増やし続けるより、

AIが賢くなることを前提に、必要最低限だけ制約する

という考え方です。


AIが「できました」と言っても完了ではない

AIの出力をそのままmainへ入れることはしません。

基本的には、

Codex / Claude Code
↓
Implementation
↓
Diff確認
↓
Unit Test
↓
E2E
↓
VRT
↓
Merge

です。

特に、

  • Canvas
  • PDF
  • Camera
  • Microphone
  • WASM
  • Cloudflare Workers

などは、実ブラウザで動かさないと分からないことも多いです。


CI/CDも「賢くサボる」

CI/CDにはGitHub Actionsを使っています。

大きな流れは、

Pull Request
      │
 ┌────┼────┐
 │    │    │
 ▼    ▼    ▼
Test E2E  VRT
 │    │    │
 └────┼────┘
      │
   All Green

です。


Unit Test

Vitestを使っています。

CIでは、

Format
↓
Lint
↓
Unit Test
↓
Coverage

まで確認します。

rPPGや騒音計算、PDF座標計算など、pureなロジックはなるべくUnit Testへ寄せています。


E2Eはnext devではなくOpenNext buildへ当てる

本番環境は、

Next.js
↓
OpenNext
↓
Cloudflare Workers

です。

そのためE2Eも、

pnpm exec opennextjs-cloudflare build
pnpm e2e

として、本番に近いbuildへPlaywrightを当てています。


同じPRの古いCIはキャンセルする

PRへ連続pushした場合、

Commit A
↓
E2E実行中

Commit B push
↓
Aは不要

になります。

そこでcancel-in-progressを使い、最新commitだけを残しています。

GitHub Actionsの実行時間節約にもなります。


失敗したときだけArtifactを残す

成功したCI結果を毎回詳しく見ることはありません。

そのため、

Success
→ 何もしない

Failure
→ Report / Artifactを残す

という考え方にしています。


VRTで見た目も検証する

Playwrightを使ってVisual Regression Testも行っています。

Unit TestとE2Eが通っていても、

文字が見切れた

Buttonサイズが変わった

Mobileだけ崩れた

Dialogがずれた

といった問題は残ります。

そこで、

Unit Test
→ Logic

E2E
→ Behavior

VRT
→ Visual

と役割を分けています。


VRTのbaselineはPR実行時にmainから作る

snapshotをずっとGitに保存しておくのではなく、

main
↓
Build
↓
Screenshot

PR
↓
Build
↓
Screenshot

Compare

という形にしています。

さらにmainとPRを同じGitHub Actions runnerで撮影することで、

OS差

Font差

Chromium差

によるfalse positiveを減らしています。


VRTの差分をPR上ですぐ確認できるようにする

VRTが失敗した場合に、

Actions
↓
Artifact
↓
ZIP
↓
画像を探す

のは面倒です。

そのため差分画像を保存し、PRコメントから、

diff

expected

actual

を確認できるようにしています。

成功後は古い「差分あり」のコメントも更新します。


Renovateで依存更新も自動化

依存更新にはRenovateを使っています。

minor / patchなどをまとめて更新し、リリース直後のバージョンにはすぐ飛びつかないよう少し期間を空けています。

依存更新PRも、

Renovate
↓
PR
↓
Test
↓
E2E
↓
VRT
↓
全部OK
↓
Auto Merge

としています。

人間が見るのは失敗したときだけです。


依存更新を「毎回確認する仕事」から「例外処理」へ

理想的には、

Renovate
↓
CI
↓
Green
↓
Auto Merge

で終わります。

人が見るのは、

Unit Test ❌

E2E ❌

VRT ❌

のときだけです。

この、

正常系を人間が確認し続けない

という考え方は、サービスの「賢くサボる」というコンセプトにも近いです。


本番エラーはSentryで拾う

本番のアプリケーションエラー監視にはSentryを使っています。

ローカルやCIですべての状況を再現できるわけではありません。

特に、

  • 特定ブラウザ
  • 特定端末
  • 特定データ
  • 特定操作順

でしか発生しない問題があります。

そこで、

Production
↓
Error
↓
Sentry
↓
Stack Trace確認
↓
Codex / Claude Codeで調査
↓
Fix
↓
PR
↓
Test / E2E / VRT
↓
Merge

という流れで修正できます。


AI開発とSentryの相性はかなりいい

Sentryで、

どのエラーか

どのコードで起きたか

どのくらい発生しているか

が分かれば、その情報をもとにAIへコード調査を任せやすくなります。

Sentry
→ 問題の手掛かり

Codex / Claude Code
→ コード横断調査・修正

CI/CD
→ 修正で他を壊していないか確認

という役割分担です。


死活監視にはBetter Stack

Sentryはアプリ内部のerror監視ですが、

そもそもWebサイトが開かない

HTTP Requestが返らない

5xxになっている

場合は別の監視が必要です。

そのため外形監視にはBetter Stackを使っています。

役割としては、

Sentry
→ Application Error

Better Stack
→ Uptime / External Monitoring

です。


GitHub Actionsで死活監視する案も考えた

死活監視だけなら、

on:
  schedule:
    - cron: "*/5 * * * *"

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - run: curl --fail https://example.com/

でも作れます。

ただ、最終的にはBetter Stackを使いました。

理由は、

GitHub ActionsはCI/CDの仕組みであって、監視専用サービスではない

からです。


専用監視サービスの方が考えなくていいことが多い

自作すると、

HTTP Check
↓
Retry
↓
何回失敗したらIncident?
↓
通知
↓
通知重複防止
↓
Recovery通知

などを考える必要があります。

Better Stackなら、この部分を監視サービス側へ任せられます。

また、

GitHub Actions
→ Build / Test / Deploy / Batch

Sentry
→ Error

Better Stack
→ Uptime

と役割が分かれていた方が構成も理解しやすいです。


HeartbeatならCronやBatchも監視できる

外形監視だけでなく、

Cloudflare Cron
↓
Job Complete
↓
Heartbeat

という構成にすれば、

サイトは開く

でもBatchだけ止まっている

ケースも検知できます。

これは今後の定期処理監視でも使いやすいと思っています。


開発から運用までを一つのループにする

最終的な開発フローはこんな形です。

             Codex / Claude Code
                     │
                     ▼
               Implementation
                     │
                     ▼
                Pull Request
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
        Test        E2E        VRT
          │          │          │
          └──────────┼──────────┘
                     │
                   Merge
                     │
                     ▼
                Production
                  │      │
             Sentry    Better Stack
                  │      │
                  └──┬───┘
                     │
                  Problem
                     │
                     ▼
             Codex / Claude Code

依存更新だけは別ルートで、

Renovate
↓
PR
↓
Test / E2E / VRT
↓
Auto Merge

です。


ブラウザ処理を増やすメリット

ブラウザ側へ処理を寄せるメリットはいくつかあります。

サーバーコスト

例えば画像変換なら、

Upload
↓
Server Convert
↓
Download

が不要です。

転送量

5MB画像ならUpload + Downloadだけでも転送量が増えます。

Browser内なら0です。

プライバシー

特に、

  • 顔写真
  • カメラ
  • マイク
  • PDF

などは、送らなくてよいなら送らない方がシンプルです。


ただし何でもBrowserにはしない

Browserには、

  • 端末性能差
  • メモリ制限
  • WASM初期ロード
  • ブラウザ差
  • 長時間処理
  • 大規模モデルが厳しい

という弱点があります。

そのため、

Browser
→ 軽い処理・プライバシー

Cloudflare
→ 認証・保存・API

GPU
→ 大規模AI

という分け方に落ち着きました。


「AIを使う」より「どこまでAIを使わないか」を考える

作っていて面白かったのは、

AIサービスなのに、

AIを使わない部分をかなり考えるようになったこと

です。

例えば、

音声加工
→ Web Audio API

画像変換
→ WASM

PDF
→ PDF.js

OCR
→ Tesseract.js

騒音
→ Signal Processing

rPPG
→ TypeScript

Voice Generation
→ Qwen3-TTS

文章・要約
→ GPT

です。

全部APIへ投げる方が実装は簡単な場合もあります。

ただ、

Latency

Cost

Privacy

Offline処理

Vendor dependency

を考えると、Browserで十分なものはBrowserに残した方が使いやすいことも多いです。


個人開発でもかなり並行開発できるようになった

以前なら、

設計
↓
実装
↓
テスト
↓
次

と完全に直列でした。

今は、

                   ┌─ Codex → Feature A
                   │
自分 → Task分割 ───┼─ Claude Code → Feature B
                   │
                   └─ 自分 → Review / Design

と進めることがあります。

さらに、

依存更新
→ Renovate

Test
→ GitHub Actions

本番Error
→ Sentry

死活監視
→ Better Stack

へ任せています。

AIにコードを書かせること以上に、

自分が毎回やらなくてよい作業を増やす

方が、個人開発では大きいと感じています。


最終的なアーキテクチャ

全体をまとめると、

                    ┌────────────────────┐
                    │      Browser       │
                    │                    │
                    │ Image / PDF        │
                    │ Audio / Camera     │
                    │ OCR / rPPG         │
                    │ WebAssembly        │
                    │ Speech Synthesis   │
                    └─────────┬──────────┘
                              │
                     必要なデータだけ
                              │
                              ▼
                    ┌────────────────────┐
                    │     Cloudflare     │
                    │                    │
                    │ Workers / Next.js  │
                    │ D1 / R2            │
                    │ Durable Objects    │
                    │ Cron / Email       │
                    │ Auth / API         │
                    └─────────┬──────────┘
                              │
                       Heavy AI Only
                              │
                              ▼
                    ┌────────────────────┐
                    │        GPU         │
                    │                    │
                    │     Qwen3-TTS      │
                    └────────────────────┘

開発側は、

Codex / Claude Code Pro
        │
        ▼
   Git worktree
        │
        ▼
    Pull Request
        │
   ┌────┼────┐
   ▼    ▼    ▼
 Test  E2E  VRT
   │    │    │
   └────┼────┘
        │
       Merge
        │
        ▼
   Production
    │       │
 Sentry  Better Stack

依存更新は、

Renovate
↓
PR
↓
Test / E2E / VRT
↓
Auto Merge

まで自動化しています。

SEOはコンテンツだけでなく技術面もかなり意識している

neglegereでは機能を作るだけでなく、検索エンジンやAI検索から内容を理解してもらいやすくするための技術SEOも意識しています。

Next.jsのMetadata APIを使い、

title
description
Open Graph
Twitter Card

などの基本的なメタデータを設定しています。

さらに、

sitemap.xml
robots.txt

もNext.js側から生成しています。


sitemapには公開ページだけを載せる

sitemap.tsでは、単純にすべてのRouteを出すのではなく、

公開ツール
LP
FAQ
お問い合わせ
利用規約
プライバシーポリシー

など、検索対象にしたい公開ページだけを列挙しています。

逆に、

ログイン後のページ
ユーザー固有ページ
設定画面

などはsitemapへ含めません。

また、

トップページ
→ priority 1

主要ツール
→ priority 0.8

各LP
→ priority 0.7

のようにページの重要度も分けています。


robots.txtでもクロール範囲を制御する

robots.tsでは、

/api/
/me/
/post-assist/settings
/settings/
/swagger

など、検索結果へ出す必要がないページをクロール対象から外しています。

そして、

robots.txt
↓
sitemap.xml

への導線も設定しています。

つまり、

公開したいページ
→ sitemap

検索させたくないページ
→ robots

と明示的に分けています。


構造化データも意識している

今後さらに強化したいのがJSON-LDなどの構造化データです。

例えばサービス全体なら、

{
  "@context": "https://schema.org",
  "@type": "WebApplication",
  "name": "neglegere",
  "url": "https://neglegere.com/"
}

のように、

これは何というサービスなのか

Webアプリなのか

何ができるのか

どのURLが正規URLなのか

を機械が理解しやすい形で明示できます。

機能ごとのLPについても、

WebApplication
SoftwareApplication
FAQPage
BreadcrumbList

など、そのページの役割に合った構造化データを使えないか検討しています。

単純に文章中へキーワードを増やすSEOより、

ページそのものの意味を機械が読み取りやすくする

方を重視しています。


AI検索も意識して「意味を構造化する」

最近はGoogle検索だけでなく、ChatGPTなどのAI経由でサービスを探すことも増えてきました。

そのため、

HTML
Metadata
JSON-LD
Sitemap
Robots

のように、

サービスの情報を機械が解釈しやすい形で持たせる

ことは、従来のSEOだけでなくAI検索への対応としても重要になると考えています。

ただし、

AI専用の謎テクニック

を大量に入れるのではなく、

人間にも分かりやすい文章

正しいHTML構造

適切なMetadata

Sitemap / Robots

必要に応じたStructured Data

というWebの基本をしっかりやる方針です。


ツール本体とLPを分けるのもSEOを考えた結果

neglegereでは、

/word-lookup
/noise-meter
/condition-check

のような実際に使うツール画面とは別に、

/lp/word-lookup
/lp/noise-meter
/lp/condition-check

のような説明用LPも持っています。

ツール本体は、

すぐ使える
操作しやすい
余計な説明を置かない

ことを優先します。

一方LPでは、

何ができるか
どういう用途か
どんな特徴があるか

を文章として持たせます。

Tool
→ UX優先

LP
→ 説明・SEO優先

と役割を分けることで、

操作画面をSEO用テキストだらけにせず、検索側にも十分な情報を渡せる

ようにしています。


まとめ

neglegereを作っていて、最近よく考えるのはこの2つです。

この処理、本当にサーバーでやる必要ある?

そして、

この作業、本当に毎回人間がやる必要ある?

画像、PDF、音声、カメラなどはBrowserへ寄せる。

認証、保存、APIはCloudflareへ任せる。

大きなAIモデルだけGPUへ逃がす。

実装はCodexとClaude Code Proで並行する。

文章生成や文字起こしは、用途とコストを見てGPT系APIを使う。

依存更新はRenovate。

品質確認はUnit Test / E2E / VRT。

本番エラーはSentry。

死活監視はBetter Stack。

デザインについては逆に、AIを使っているからこそ、AIっぽくなりすぎないよう実写や背景透過素材を使う。

そんな感じで、

サービス自体だけでなく、開発や運用も含めて「賢くサボる」

ことを目指しています。

まだ実験的な機能も多いですが、Webブラウザだけでできることは想像以上に多いです。

今後も、

BrowserでできることはBrowserへ。人間がやらなくていいことは自動化へ。

という方向で作っていこうと思います。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?