convex-create-component
Convexの部品を設計・構築し、独立したテーブルや明確な境界線、アプリ向けのラッパーを備え、再利用可能なバックエンドロジックや外部連携機能を分離して、Convexの機能を整理・再利用しやすくするSkill。
📜 元の英語説明(参考)
Designs and builds Convex components with isolated tables, clear boundaries, and app-facing wrappers. Use this skill when creating a new Convex component, extracting reusable backend logic into a component, building a third-party integration that owns its own tables, packaging Convex functionality for reuse, or when the user mentions defineComponent, app.use, ComponentApi, ctx.runQuery/runMutation across component boundaries, or wants to separate concerns into isolated Convex modules.
🇯🇵 日本人クリエイター向け解説
Convexの部品を設計・構築し、独立したテーブルや明確な境界線、アプリ向けのラッパーを備え、再利用可能なバックエンドロジックや外部連携機能を分離して、Convexの機能を整理・再利用しやすくするSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o convex-create-component.zip https://jpskill.com/download/19141.zip && unzip -o convex-create-component.zip && rm convex-create-component.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/19141.zip -OutFile "$d\convex-create-component.zip"; Expand-Archive "$d\convex-create-component.zip" -DestinationPath $d -Force; ri "$d\convex-create-component.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
convex-create-component.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
convex-create-componentフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 6
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
[Skill 名] convex-create-component
Convexコンポーネントの作成
明確な境界と小さなアプリ向けAPIを持つ、再利用可能なConvexコンポーネントを作成します。
使用する場面
- 既存のアプリで新しいConvexコンポーネントを作成する場合
- 再利用可能なバックエンドロジックをコンポーネントに抽出する場合
- 独自のテーブルとワークフローを持つべきサードパーティ統合を構築する場合
- 複数のアプリで再利用するためにConvex機能をパッケージ化する場合
使用しない場面
- メインアプリに属する一度限りのビジネスロジック
- Convexテーブルや関数を必要としない薄いユーティリティ
convex/に残すべきアプリレベルのオーケストレーション- 通常のTypeScriptライブラリで十分な場合
ワークフロー
- ユーザーに何を作成しているのか、最終的な目標は何かを尋ねます。もしリポジトリがすでに答えを明確にしている場合は、その旨を伝え、続行する前に確認します。
- 下記の決定木を使用して形状を選択し、対応する参照ファイルを読みます。
- コンポーネントが正当化されるかどうかを決定します。機能が分離されたテーブル、バックエンド関数、または再利用可能な永続状態を必要としない場合は、通常のアプリコードまたは通常のライブラリを優先します。
- 以下の短い計画を立てます。
- コンポーネントが所有するテーブル
- 公開するパブリック関数
- アプリから渡す必要があるデータ(認証、環境変数、親ID)
- ラッパーまたはHTTPマウントとしてアプリに残るもの
convex.config.ts、schema.ts、および関数ファイルでコンポーネント構造を作成します。- アプリの生成されたファイルではなく、コンポーネント自身の
./_generated/serverインポートを使用して関数を実装します。 app.use(...)を使用してコンポーネントをアプリに接続します。アプリにまだconvex/convex.config.tsがない場合は、作成します。ctx.runQuery、ctx.runMutation、またはctx.runActionを使用して、components.<name>経由でアプリからコンポーネントを呼び出します。- Reactクライアント、HTTP呼び出し元、またはパブリックAPIがアクセスを必要とする場合は、コンポーネント関数を直接公開するのではなく、アプリにラッパー関数を作成します。
npx convex devを実行し、完了する前にコード生成、型、または境界の問題を修正します。
形状の選択
ユーザーに尋ねてから、いずれかのパスを選択します。
| 目標 | 形状 | 参照 |
|---|---|---|
| このアプリ専用のコンポーネント | ローカル | references/local-components.md |
| アプリ間で公開または共有 | パッケージ化 | references/packaged-components.md |
| ユーザーが明示的にローカル + 共有ライブラリコードを必要とする | ハイブリッド | references/hybrid-components.md |
| 不明 | デフォルトでローカル | references/local-components.md |
続行する前に、必ず1つの参照ファイルを読んでください。
デフォルトのアプローチ
ユーザーが明示的にnpmパッケージを望まない限り、ローカルコンポーネントをデフォルトとします。
convex/components/<componentName>/の下に配置します。- 独自の
convex.config.tsでdefineComponent(...)を使用して定義します。 - アプリの
convex/convex.config.tsからapp.use(...)でインストールします。 npx convex devにコンポーネント自身の_generated/ファイルを生成させます。
コンポーネントのスケルトン
テーブルと2つの関数、およびアプリの接続を含む最小限のローカルコンポーネントです。
// convex/components/notifications/convex.config.ts
import { defineComponent } from "convex/server";
export default defineComponent("notifications");
// convex/components/notifications/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
notifications: defineTable({
userId: v.string(),
message: v.string(),
read: v.boolean(),
}).index("by_user", ["userId"]),
});
// convex/components/notifications/lib.ts
import { v } from "convex/values";
import { mutation, query } from "./_generated/server.js";
export const send = mutation({
args: { userId: v.string(), message: v.string() },
returns: v.id("notifications"),
handler: async (ctx, args) => {
return await ctx.db.insert("notifications", {
userId: args.userId,
message: args.message,
read: false,
});
},
});
export const listUnread = query({
args: { userId: v.string() },
returns: v.array(
v.object({
_id: v.id("notifications"),
_creationTime: v.number(),
userId: v.string(),
message: v.string(),
read: v.boolean(),
})
),
handler: async (ctx, args) => {
return await ctx.db
.query("notifications")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.filter((q) => q.eq(q.field("read"), false))
.collect();
},
});
// convex/convex.config.ts
import { defineApp } from "convex/server";
import notifications from "./components/notifications/convex.config.js";
const app = defineApp();
app.use(notifications);
export default app;
// convex/notifications.ts (app-side wrapper)
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
import { components } from "./_generated/api";
import { getAuthUserId } from "@convex-dev/auth/server";
export const sendNotification = mutation({
args: { message: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runMutation(components.notifications.lib.send, {
userId,
message: args.message,
});
return null;
},
});
export const myUnread = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
return await ctx.runQuery(components.notifications.lib.listUnread, {
userId,
});
},
});
参照パスの形状に注目してください。convex/components/notifications/lib.ts内の関数は、アプリからcomponents.notifications.lib.sendとして呼び出されます。
重要なルール
ctx.authはコンポーネント内では利用できないため、認証はアプリに保持してください。- コンポーネント関数は
process.envを読み取れないため、環境アクセスはアプリに保持してください。 - 親アプリのIDは、
Id型がアプリ向けのComponentApiでプレーンな文字列になるため、境界を越えて文字列として渡してください。 v.id("parentTable")は使用しないでください。
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Convex Create Component
Create reusable Convex components with clear boundaries and a small app-facing API.
When to Use
- Creating a new Convex component in an existing app
- Extracting reusable backend logic into a component
- Building a third-party integration that should own its own tables and workflows
- Packaging Convex functionality for reuse across multiple apps
When Not to Use
- One-off business logic that belongs in the main app
- Thin utilities that do not need Convex tables or functions
- App-level orchestration that should stay in
convex/ - Cases where a normal TypeScript library is enough
Workflow
- Ask the user what they are building and what the end goal is. If the repo already makes the answer obvious, say so and confirm before proceeding.
- Choose the shape using the decision tree below and read the matching reference file.
- Decide whether a component is justified. Prefer normal app code or a regular library if the feature does not need isolated tables, backend functions, or reusable persistent state.
- Make a short plan for:
- what tables the component owns
- what public functions it exposes
- what data must be passed in from the app (auth, env vars, parent IDs)
- what stays in the app as wrappers or HTTP mounts
- Create the component structure with
convex.config.ts,schema.ts, and function files. - Implement functions using the component's own
./_generated/serverimports, not the app's generated files. - Wire the component into the app with
app.use(...). If the app does not already haveconvex/convex.config.ts, create it. - Call the component from the app through
components.<name>usingctx.runQuery,ctx.runMutation, orctx.runAction. - If React clients, HTTP callers, or public APIs need access, create wrapper functions in the app instead of exposing component functions directly.
- Run
npx convex devand fix codegen, type, or boundary issues before finishing.
Choose the Shape
Ask the user, then pick one path:
| Goal | Shape | Reference |
|---|---|---|
| Component for this app only | Local | references/local-components.md |
| Publish or share across apps | Packaged | references/packaged-components.md |
| User explicitly needs local + shared library code | Hybrid | references/hybrid-components.md |
| Not sure | Default to local | references/local-components.md |
Read exactly one reference file before proceeding.
Default Approach
Unless the user explicitly wants an npm package, default to a local component:
- Put it under
convex/components/<componentName>/ - Define it with
defineComponent(...)in its ownconvex.config.ts - Install it from the app's
convex/convex.config.tswithapp.use(...) - Let
npx convex devgenerate the component's own_generated/files
Component Skeleton
A minimal local component with a table and two functions, plus the app wiring.
// convex/components/notifications/convex.config.ts
import { defineComponent } from "convex/server";
export default defineComponent("notifications");
// convex/components/notifications/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
notifications: defineTable({
userId: v.string(),
message: v.string(),
read: v.boolean(),
}).index("by_user", ["userId"]),
});
// convex/components/notifications/lib.ts
import { v } from "convex/values";
import { mutation, query } from "./_generated/server.js";
export const send = mutation({
args: { userId: v.string(), message: v.string() },
returns: v.id("notifications"),
handler: async (ctx, args) => {
return await ctx.db.insert("notifications", {
userId: args.userId,
message: args.message,
read: false,
});
},
});
export const listUnread = query({
args: { userId: v.string() },
returns: v.array(
v.object({
_id: v.id("notifications"),
_creationTime: v.number(),
userId: v.string(),
message: v.string(),
read: v.boolean(),
})
),
handler: async (ctx, args) => {
return await ctx.db
.query("notifications")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.filter((q) => q.eq(q.field("read"), false))
.collect();
},
});
// convex/convex.config.ts
import { defineApp } from "convex/server";
import notifications from "./components/notifications/convex.config.js";
const app = defineApp();
app.use(notifications);
export default app;
// convex/notifications.ts (app-side wrapper)
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
import { components } from "./_generated/api";
import { getAuthUserId } from "@convex-dev/auth/server";
export const sendNotification = mutation({
args: { message: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runMutation(components.notifications.lib.send, {
userId,
message: args.message,
});
return null;
},
});
export const myUnread = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
return await ctx.runQuery(components.notifications.lib.listUnread, {
userId,
});
},
});
Note the reference path shape: a function in convex/components/notifications/lib.ts is called as components.notifications.lib.send from the app.
Critical Rules
- Keep authentication in the app, because
ctx.authis not available inside components. - Keep environment access in the app, because component functions cannot read
process.env. - Pass parent app IDs across the boundary as strings, because
Idtypes become plain strings in the app-facingComponentApi. - Do not use
v.id("parentTable")for app-owned tables inside component args or schema, because the component has no access to the app's table namespace. - Import
query,mutation, andactionfrom the component's own./_generated/server, not the app's generated files. - Do not expose component functions directly to clients. Create app wrappers when client access is needed, because components are internal and need auth/env wiring the app provides.
- If the component defines HTTP handlers, mount the routes in the app's
convex/http.ts, because components cannot register their own HTTP routes. - If the component needs pagination, use
paginatorfromconvex-helpersinstead of built-in.paginate(), because.paginate()does not work across the component boundary. - Add
argsandreturnsvalidators to all public component functions, because the component boundary requires explicit type contracts.
Patterns
Authentication and environment access
// Bad: component code cannot rely on app auth or env
const identity = await ctx.auth.getUserIdentity();
const apiKey = process.env.OPENAI_API_KEY;
// Good: the app resolves auth and env, then passes explicit values
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runAction(components.translator.translate, {
userId,
apiKey: process.env.OPENAI_API_KEY,
text: args.text,
});
Client-facing API
// Bad: assuming a component function is directly callable by clients
export const send = components.notifications.send;
// Good: re-export through an app mutation or query
export const sendNotification = mutation({
args: { message: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runMutation(components.notifications.lib.send, {
userId,
message: args.message,
});
return null;
},
});
IDs across the boundary
// Bad: parent app table IDs are not valid component validators
args: { userId: v.id("users") }
// Good: treat parent-owned IDs as strings at the boundary
args: { userId: v.string() }
Advanced Patterns
For additional patterns including function handles for callbacks, deriving validators from schema, static configuration with a globals table, and class-based client wrappers, see references/advanced-patterns.md.
Validation
Try validation in this order:
npx convex codegen --component-dir convex/components/<name>npx convex codegennpx convex dev
Important:
- Fresh repos may fail these commands until
CONVEX_DEPLOYMENTis configured. - Until codegen runs, component-local
./_generated/*imports and app-sidecomponents.<name>...references will not typecheck. - If validation blocks on Convex login or deployment setup, stop and ask the user for that exact step instead of guessing.
Reference Files
Read exactly one of these after the user confirms the goal:
references/local-components.mdreferences/packaged-components.mdreferences/hybrid-components.md
Official docs: Authoring Components
Checklist
- [ ] Asked the user what they want to build and confirmed the shape
- [ ] Read the matching reference file
- [ ] Confirmed a component is the right abstraction
- [ ] Planned tables, public API, boundaries, and app wrappers
- [ ] Component lives under
convex/components/<name>/(or package layout if publishing) - [ ] Component imports from its own
./_generated/server - [ ] Auth, env access, and HTTP routes stay in the app
- [ ] Parent app IDs cross the boundary as
v.string() - [ ] Public functions have
argsandreturnsvalidators - [ ] Ran
npx convex devand fixed codegen or type issues
同梱ファイル
※ ZIPに含まれるファイル一覧。`SKILL.md` 本体に加え、参考資料・サンプル・スクリプトが入っている場合があります。
- 📄 SKILL.md (10,398 bytes)
- 📎 assets/icon.svg (485 bytes)
- 📎 references/advanced-patterns.md (3,642 bytes)
- 📎 references/hybrid-components.md (1,017 bytes)
- 📎 references/local-components.md (1,100 bytes)
- 📎 references/packaged-components.md (1,822 bytes)