125k

はじめに

独自のコンポーネントレジストリを設定して実行する方法を学びます。

このガイドでは、独自のレジストリを設定する手順を説明します。配布したいコンポーネント、フック、ユーティリティ、その他のファイルを含むプロジェクトがすでにあることを前提としています。

公開済みの GitHub リポジトリをお持ちの場合は、ルートに registry.json ファイルを追加するだけでレジストリにできます。 詳しくは GitHub レジストリ をご覧ください。

新しいレジストリプロジェクトを始める場合は、テンプレートプロジェクト を出発点として利用できます。すでに必要な設定が済んでいます。

要件

カスタムレジストリは自由に設計・公開できます。唯一の要件は、レジストリカタログとレジストリ項目が registry スキーマ仕様 と registry-item スキーマ仕様 に準拠することです。

レジストリは、HTTP で JSON を配信できれば、Next.js、Vite、Vue、Svelte、PHP など、どのフレームワークでも構築できます。ルートに registry.json ファイルを置いた公開 GitHub リポジトリでも構いません。

レジストリの例を見たい場合は、出発点として利用できる テンプレートプロジェクト を用意しています。

registry.json

registry.json はレジストリのエントリーポイントです。レジストリの名前やホームページを含み、レジストリに存在するすべての項目を定義します。

レジストリのエンドポイント(レジストリをホストしている URL)のルートに、このファイル(または JSON ペイロード)を配置する必要があります。

以下は registry.json ファイルの例です:

registry.json
{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "acme",
  "homepage": "https://acme.com",
  "items": [
    {
      "name": "button",
      "type": "registry:ui",
      "title": "Button",
      "description": "A simple button component.",
      "files": [
        {
          "path": "components/ui/button.tsx",
          "type": "registry:ui"
        }
      ]
    }
  ]
}

レジストリの構造

ソースレジストリは、次の2通りの方法で構造化できます:

  • ルートの単一 registry.json にすべての項目を定義する。
  • ルートの registry.json から include で複数の registry.json ファイルを組み合わせる。

方法A: 単一の registry.json

プロジェクトのルートに registry.json ファイルを作成します。すべてのレジストリ項目を items 配列に追加します。これがレジストリを定義する最もシンプルな方法です。

registry.json
{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "acme",
  "homepage": "https://acme.com",
  "items": [
    {
      "name": "button",
      "type": "registry:ui",
      "title": "Button",
      "description": "A simple button component.",
      "files": [
        {
          "path": "components/ui/button.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "hello-world",
      "type": "registry:block",
      "title": "Hello World",
      "description": "A simple hello world component.",
      "registryDependencies": ["button"],
      "files": [
        {
          "path": "registry/default/hello-world/hello-world.tsx",
          "type": "registry:component"
        }
      ]
    }
  ]
}

この registry.json ファイルは registry スキーマ仕様 に準拠する必要があります。

方法B: include を使う

大規模なレジストリでは、include を使って複数の registry.json ファイルからソースレジストリを構成できます。

registry.json
components
└── ui
    ├── button.tsx
    ├── input.tsx
    └── registry.json
hooks
├── registry.json
├── use-media-query.ts
└── use-toggle.ts

ルートの registry.json では、レジストリのメタデータを定義し、ネストされたレジストリファイルを取り込みます。

registry.json
{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "acme",
  "homepage": "https://acme.com",
  "include": [
    "components/ui/registry.json",
    "hooks/registry.json"
  ]
}

取り込まれる registry.json ファイルは、構成用の有効なレジストリファイルであり、name と homepage は省略できます。レジストリのメタデータを定義する必要があるのはルートの registry.json のみです。

components/ui/registry.json
{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "items": [
    {
      "name": "button",
      "type": "registry:ui",
      "files": [
        {
          "path": "button.tsx",
          "type": "registry:ui"
        }
      ]
    },
    {
      "name": "input",
      "type": "registry:ui",
      "files": [
        {
          "path": "input.tsx",
          "type": "registry:ui"
        }
      ]
    }
  ]
}
hooks/registry.json
{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "items": [
    {
      "name": "use-toggle",
      "type": "registry:hook",
      "files": [
        {
          "path": "use-toggle.ts",
          "type": "registry:hook"
        }
      ]
    },
    {
      "name": "use-media-query",
      "type": "registry:hook",
      "files": [
        {
          "path": "use-media-query.ts",
          "type": "registry:hook"
        }
      ]
    }
  ]
}

include を使う場合、ファイルパスは項目を宣言している registry.json ファイルからの相対パスになります。

項目の追加

UI コンポーネントの作成

最初の項目を追加します。以下はシンプルな <Button /> コンポーネントの例です:

components/ui/button.tsx
import * as React from "react"
 
export function Button(props: React.ComponentProps<"button">) {
  return (
    <button
      {...props}
      className="rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-white"
    />
  )
}
components
└── ui
    └── button.tsx

項目をレジストリに追加する

コンポーネントをレジストリに追加するには、registry.json に項目定義を追加します。include を使っている場合は、そのコンポーネントを所有する取り込み先の registry.json ファイルに項目を追加します。たとえば、UI コンポーネントは components/ui/registry.json に追加します。

registry.json
{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "acme",
  "homepage": "https://acme.com",
  "items": [
    {
      "name": "button",
      "type": "registry:ui",
      "title": "Button",
      "description": "A simple button component.",
      "files": [
        {
          "path": "components/ui/button.tsx",
          "type": "registry:ui"
        }
      ]
    }
  ]
}

レジストリ項目は、name、type、title、description、files を追加して定義します。

追加するファイルごとに、path と type を指定する必要があります。単一ファイル構成のレジストリでは、path はプロジェクトのルートからの相対パスです。include を使う場合は、path は項目を宣言している registry.json ファイルからの相対パスです。type はファイルの種類です。

レジストリ項目スキーマとファイルタイプについて詳しくは、registry-item スキーマのドキュメント をご覧ください。

レジストリの配信

レジストリは、静的な JSON ファイルとしても、動的ルートハンドラーからも配信できます。

方法A: 静的な JSON ファイル

ビルドコマンドを実行して、静的なレジストリ JSON ファイルを生成します。

pnpm dlx shadcn@latest build

ソースレジストリで include を使っている場合、shadcn build は取り込まれたレジストリを解決し、フラット化されたレジストリを出力ディレクトリに書き出します。生成された registry.json に include は含まれません。

Next.js でレジストリを実行している場合は、next サーバーを実行すればこれらのファイルを配信できます。他のフレームワークではコマンドが異なる場合があります。

pnpm dev

ファイルは http://localhost:3000/r/[NAME].json(例: http://localhost:3000/r/button.json)で配信されるようになります。

方法B: 動的ルートハンドラー

リクエスト時にソースの registry.json からレジストリ JSON を配信したい場合は、shadcn/registry のプロデューサー側ローダー API を使います。

shadcn をランタイム依存関係としてインストールします:

pnpm add shadcn

loadRegistry を使ってレジストリカタログを配信します。

app/r/registry.json/route.ts
import { loadRegistry } from "shadcn/registry"
 
export async function GET() {
  try {
    const registry = await loadRegistry()
 
    return Response.json(registry)
  } catch (error) {
    console.error(error)
 
    return Response.json({ error: "Failed to load registry." }, { status: 500 })
  }
}

loadRegistryItem を使って個々のレジストリ項目を配信します。

app/r/[name].json/route.ts
import { loadRegistryItem, RegistryItemNotFoundError } from "shadcn/registry"
 
export async function GET(
  _request: Request,
  context: {
    params: Promise<{
      name: string
    }>
  }
) {
  const { name } = await context.params
 
  try {
    const item = await loadRegistryItem(name)
 
    return Response.json(item)
  } catch (error) {
    if (error instanceof RegistryItemNotFoundError) {
      return Response.json(
        { error: `Registry item "${name}" was not found.` },
        { status: 404 }
      )
    }
 
    console.error(error)
 
    return Response.json(
      { error: "Failed to load registry item." },
      { status: 500 }
    )
  }
}

どちらのローダーも JSON を返す前に include を解決するため、shadcn build を実行しなくても、ルートハンドラーで同じソースの registry.json 構造を使えます。

レジストリのテスト

レジストリを配信できるようになったら、他の開発者が使うのと同じ CLI コマンドでテストします。

URL を使う場合

list や search など項目を検出するコマンドにはカタログ URL を使います。view や add など特定の項目を読み取り・インストールするコマンドには項目 URL を使います。

項目の一覧表示

まず、レジストリカタログが検出できることを確認します。

pnpm dlx shadcn@latest list http://localhost:3000/r/registry.json

項目の検索

クエリでレジストリを検索します。

pnpm dlx shadcn@latest search http://localhost:3000/r/registry.json --query button

項目の表示

次に、1つのレジストリ項目を名前で表示します。

pnpm dlx shadcn@latest view http://localhost:3000/r/button.json

項目の追加

インストールフローをテストするには、項目をインストールしたいプロジェクトで add を実行します。

pnpm dlx shadcn@latest add http://localhost:3000/r/button.json

名前空間を使う場合

レジストリの追加

名前空間を使ってレジストリをテストすることもできます。components.json ファイルがあるプロジェクトで、プロジェクトにレジストリ URL テンプレートを追加します。

pnpm dlx shadcn@latest registry add @acme=http://localhost:3000/r/{name}.json

{name} プレースホルダーは項目 JSON ファイルに解決される必要があります。たとえば、@acme/button は http://localhost:3000/r/button.json に解決されます。カタログは引き続き http://localhost:3000/r/registry.json で個別に配信されます。

項目の一覧表示

次に、レジストリ内の項目を一覧表示します。

pnpm dlx shadcn@latest list @acme

項目の検索

クエリでレジストリを検索します。

pnpm dlx shadcn@latest search @acme --query button

項目の表示

1つのレジストリ項目を名前で表示します。

pnpm dlx shadcn@latest view @acme/button

項目の追加

インストールフローをテストするには、項目をインストールしたいプロジェクトで add を実行します。

pnpm dlx shadcn@latest add @acme/button

詳しくは 名前空間付きレジストリ のドキュメントをご覧ください。

レジストリの公開

レジストリを他の開発者に利用可能にするには、プロジェクトを公開 URL にデプロイします。デプロイ後は、ユーザーは項目 URL から直接項目をインストールできますし、レジストリをプロジェクトの名前空間として追加することもできます。

名前空間の設定手順の共有

@acme/button のような名前空間で項目をインストールしてほしい場合は、プロジェクトにレジストリ URL テンプレートを追加するようユーザーに案内します。{name} プレースホルダーは、CLI がレジストリ項目を解決する際に項目名に置き換えられます。

テンプレートは項目 JSON ファイルに解決される必要があります。たとえば、@acme/button は https://acme.com/r/button.json に解決されます。レジストリカタログは引き続き https://acme.com/r/registry.json で個別に配信してください。

CLI で名前空間を追加できます。

pnpm dlx shadcn@latest registry add @acme=https://acme.com/r/{name}.json

または、components.json ファイルの registries フィールドに手動で追加することもできます。

components.json
{
  "registries": {
    "@acme": "https://acme.com/r/{name}.json"
  }
}

ユーザーは名前空間を指定してレジストリの項目を利用できるようになります。

pnpm dlx shadcn@latest add @acme/button

名前空間をレジストリインデックスに追加する

レジストリがオープンソースで公開されている場合は、名前空間を公式レジストリインデックスに申請できます。これにより、ユーザーは完全な URL テンプレートを貼り付ける代わりに、名前だけで名前空間を追加できるようになります。

申請要件について詳しくは レジストリインデックス のドキュメントをご覧ください。

ガイドライン

レジストリ用にコンポーネントを構築する際のガイドラインは次のとおりです。

  • レジストリ項目は registry/[STYLE]/[NAME] ディレクトリに配置します。ここでは例として default を使っています。registry ディレクトリ配下にネストされていれば、任意の名前で構いません。
  • ブロックでは、次のプロパティが必須です:name、description、type、files。
  • レジストリ項目には適切な名前と説明を付けることを推奨します。LLM がコンポーネントとその目的を理解するのに役立ちます。
  • すべてのレジストリ依存関係を registryDependencies に記載してください。レジストリ依存関係とは、button、@acme/input-form、acme/ui/button、http://localhost:3000/r/editor.json のような項目アドレスです。
  • すべての依存関係を dependencies に記載してください。依存関係とは、レジストリ内のパッケージ名(例: zod、sonner など)です。バージョンを指定する場合は name@version 形式(例: zod@^3.20.0)を使えます。
  • インポートは常に @/registry パスを使ってください。 例: import { HelloWorld } from "@/registry/default/hello-world/hello-world"
  • 理想的には、レジストリ項目内のファイルを components、hooks、lib ディレクトリに配置します。