joelclaw-web について
joelclaw-web は、joelclaw.com の Next.js アプリを保守するためのガイドで、コンテンツ管理、公開、デザイン、インフラページの安全対策を扱います。Convex を実行時のコンテンツソースとし、公開ページでのインフラ情報の扱いに注意を促します。
主な機能
- 01Convex を優先するコンテンツ管理
- 02記事の下書きと公開手順
- 03コンテンツの再検証と確認
- 04サイトデザインとナビゲーション指針
- 05インフラの機密情報の非表示
活用シーン
- 01記事の下書きや公開
- 02ADR と発見コンテンツの更新
- 03非公開情報を出さずにネットワーク状況ページを保守
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ joelhooks/ joelclaw --skill joelclaw-webインストール後、Claude Code で/joelclaw-webを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamlassets/small-logo.svg
詳細
joelclaw.com Web アプリ
joelclaw モノレポ(~/Code/joelhooks/joelclaw/)内の apps/web/ にある Next.js アプリです。
main へのプッシュ時に Vercel にデプロイされます。ダークテーマで、ミニマルかつシステム風の美観です。
OPSEC ルール
公開ページで実際のインフラの詳細を決して公開しないでください。
- ノード名/ホスト名:実際の tailnet ホスト名は使わず、Stephen King の作品世界に登場する別名を使ってください
- ポート:サービスのポート番号を公開しないでください
- ユーザー名:サービス識別子から
com.joel.などのプレフィックスを取り除いてください - IP/サブネット:実際の IP アドレスや CIDR 範囲を決して表示しないでください
- ネットワーク機器のブランド/モデル:一般化してください(例:「Synology DS1821+」ではなく「NAS」)
- Tailscale:メッシュ VPN 製品として言及するのは問題ありませんが、tailnet 名やノードの IP は公開しないでください
公開する図では、コントロールプレーン、アーカイブ、開発クライアント、リレー、ルーターなど、汎用的な役割名を使ってください。実際のホスト名を架空の別名に見せかけて公開しないでください。プライベートなトポロジーの境界は引き続き守ってください。
コンテンツモデル(Convex 優先)
実行時の正規コンテンツは Convex の contentResources に保存されます。
article:<slug>(type = article)adr:<slug>(type = adr)discovery:<slug>(type = discovery)
apps/web/content/ 以下のファイルシステム上のコンテンツは、シード/バックフィル用の素材であり、実行時のソースではありません。
実行時の読み取りポリシー:
apps/web/lib/posts.ts→ Convex 優先の記事(article:*)apps/web/lib/adrs.ts→ Convex 優先の ADR(adr:*)apps/web/lib/discoveries.ts→ Convex 優先のディスカバリー(discovery:*)apps/web/lib/convex-api.ts→ チェック済みの Web 向け Convex 関数コントラクトと、実行時のanyApiプロキシ- 兄弟ディレクトリにある非公開の
joelclaw-apiチェックアウトへのlink:依存関係は追加しないでください。Vercel がクローンするのはこのリポジトリのみです。 - 任意のローカル用フォールバック(本番環境以外のみ):
JOELCLAW_ALLOW_FILESYSTEM_POSTS_FALLBACK=1(記事)JOELCLAW_ALLOW_FILESYSTEM_CONTENT_FALLBACK=1(ADR/ディスカバリー)
記事のフィールドは引き続き MDX フロントマターの形式に対応します:
---
title: "Post Title"
type: "article" | "essay" | "note" | "tutorial"
date: "2026-02-19T11:00:00" # ISO datetime, NOT just date
updated: "2026-02-19T14:30:00" # optional, bumps sort position
description: "One-liner for cards and meta"
tags: ["tag1", "tag2"]
draft: true # optional, hides from prod
source: "https://..." # optional, for video-notes
channel: "Channel Name" # optional, for video-notes
duration: "00:42:02" # optional, for video-notes
---
並べ替え:投稿は updated ?? date の降順で並びます。順序を決定的にするため、日付のみではなく完全な ISO 日時を使用してください。updated を設定すると、元の公開日を変えずに投稿を先頭へ移動できます。
スラッグ:Convex の fields.slug から導出されます(resourceId = article:<slug>)。
ハードトリガーワークフロー
write article about X
- タイトル/トピックからスラッグを生成します。
resourceId = article:<slug>、type = "article"、完全な MDX 本文をfields.contentに、fields.draft = trueとしてcontentResourcesを upsert します。fields.dateに現在の ISO タイムスタンプを設定します。- スラッグと下書きプレビューリンクを返します(開発環境で下書きを表示できる場合は
/<slug>)。
publish article <slug>
- Convex から
article:<slug>を読み込みます。 fields.draft = falseおよびfields.updated = nowでパッチ/upsert します。POST /api/revalidateを使い、影響を受けるすべてのサーフェスを再検証します。以下を指定します:- タグ:
post:<slug>、article:<slug>、articles - パス:
/、/<slug>、/<slug>.md、/<slug>/md、/feed.xml、/sitemap.md
- タグ:
/、/<slug>、/<slug>.md、/feed.xmlに公開済みの投稿が含まれていることを確認します。
メディアの埋め込み
コンテキストを補足する場合は、/cool のディスカバリーと記事に YouTube 動画を必ず埋め込んでください。<YouTube id="VIDEO_ID" /> MDX コンポーネントを使います(.mdx の記事と .md のディスカバリーの両方で利用できます)。URL(youtube.com/watch?v=VIDEO_ID)から動画 ID を抽出してください。関連するセクションの上部、本文の説明より前に埋め込みを配置します。
執筆スタイル
本文の執筆には、正規の joel-writing-style スキルを使用してください。主な特徴:率直な表現、主張が実際に Joel のものである場合は一人称、効果的な悪態、短い段落、太字による強調、会話調でありながら技術的な文章です。企業的な言い回しは避け、Joel の信念や哲学を捏造しないでください。
デザインシステム
- テーマ:ダーク(
bg-[#0a0a0a])、ニュートラルグレー、--color-claw: #ff1493(ホットピンクのアクセント) - フォント:Geist Sans(本文)、Geist Mono(コード/データ)、Dank Mono(合字対応のコードブロック)
- コンテンツ幅:
max-w-2xl(672px)— 読みやすさを重視して意図的に狭くしています - ヘッダー:1行構成 — 左側にかぎ爪アイコンと「JoelClaw」、右側にナビゲーションリンクと検索。ヘッダーにタグラインはありません。
- ナビゲーション項目:Writing(
/)、Cool(/cool)、ADRs(/adrs)、Network(/network) - アクティブなナビゲーション:アクティブな項目は白、非アクティブな項目は neutral-500 で表示し、
usePathname()で判定します - 検索:pagefind を使用した ⌘K ダイアログ。タイプ別のアイコン/バッジを表示します
- モバイル:
MobileNavコンポーネントを使った全画面オーバーレイナビゲーション - コードブロック:Catppuccin Macchiato テーマ、rehype-pretty-code
- サイドノート:Tufte スタイルの CSS サイドノート(純粋な CSS で、JavaScript は使用しません)
主要ファイル
| ファイル | 用途 |
|---|---|
app/layout.tsx | ルートレイアウト、フォント、メタデータ、フッター |
app/page.tsx | ホームページ(投稿一覧) |
app/[slug]/page.tsx | 投稿詳細ページ |
app/adrs/page.tsx | ADR 一覧 |
app/adrs/[slug]/page.tsx | ADR 詳細(タイトルの重複を避けるため H1 を除去) |
app/cool/page.tsx | Cool/発見事項の一覧 |
app/network/page.tsx | インフラストラクチャのステータスページ |
components/site-header.tsx | アクティブなナビゲーションを含むヘッダー(クライアントコンポーネント) |
components/mobile-nav.tsx | モバイル用オーバーレイナビゲーション |
components/search-dialog.tsx | ⌘K 検索 |
lib/posts.ts | Convex から記事を読み込みます(article:*) |
lib/adrs.ts | Convex から ADR を読み込みます(adr:*) |
lib/discoveries.ts | Convex から発見事項を読み込みます(discovery:*) |
lib/convex-api.ts | デプロイ可能なランタイムプロキシと、型付きの Convex 公開コントラクト |
lib/constants.ts | サイト名、URL、タグライン |
lib/claw.ts | かぎ爪アイコンの SVG パス |
ADR の表示ルール
- ADR のランタイムソースは Convex です(
resourceId = adr:<slug>のcontentResources) - Vault 同期では、引き続きリポジトリのスナップショットを
apps/web/content/adrs/に更新します - プロジェクトのスナップショットを Convex に登録します:
bun scripts/seed-adrs-discoveries.ts - 詳細ページ(
app/adrs/[slug]/page.tsx)では、ページ側ですでに ADR 番号の接頭辞付きタイトルを表示するため、Markdown コンテンツから H1 を除去します - 正規表現:
content.replace(/^#\s+(?:ADR-\d+:\s*)?.*$/m, "").trim()
新しい投稿の追加
- Convex で下書きを作成します(
contentResources.upsert、resourceId = article:<slug>、draft: true)。 dateフィールドには ISO 形式の日時を使用します。- 必要に応じて画像を
apps/web/public/images/<slug>/に追加し、MDX で/images/<slug>/...を参照します。 draft: falseに設定して公開し、タグとパスを再検証します(post:<slug>、article:<slug>、articles、/、/<slug>、/<slug>.md、/<slug>/md、/feed.xml、/sitemap.md)。- ルート、Markdown 版、ホームページ、フィードの整合性を確認します。
バックフィルスクリプト:
- 記事リソースには
scripts/seed-articles.ts - ADR および発見事項のリソースには
scripts/seed-adrs-discoveries.ts
ネットワークページ
ネットワークページ(app/network/page.tsx)には、エイリアス名を使って実際のインフラストラクチャを表示します。更新時は次の手順に従います。
- 実際のシステム状態を確認します(
kubectl get pods、tailscale status、launchctl printなど)。 - OPSEC ルールを適用し、すべてのホスト名をエイリアスに置き換え、ポート/IP アドレス/ユーザー名を削除します。
- 更新しやすいよう、データ配列をファイルの先頭に置きます。
- ステータスドット:緑(オンライン、ピングアニメーション付き)、黄(アイドル)、灰色(オフライン)