如何构建您自己的 Shadcn UI 注册表
Source: Dev.to
在历时 4 周构建了 30+ 着陆页区块(hero、pricing、FAQ、CTA、testimonial)后,我终于破解了完美的 Shadcn 注册表设置。
一次关于如何建模区块(Hero、Pricing、FAQ 等),将它们连接到 registry.json,渲染服务器友好预览,并为 npx shadcn add 做准备的完整演练。
1. 介绍
Shadcn UI 附带了官方的组件和区块注册表,但你也可以构建自己的注册表,以适配你的布局和设计系统。
经过 4 周的迭代,我构建了一个面向登录页的注册表,其中包含 30+ 区块,如 hero、pricing、FAQ、CTA 等——全部在 registry.json 中描述,并在通用的 /blocks/[name] 页面中渲染,支持实时预览和语法高亮代码。
指标: 30+ 区块,覆盖 12 类,超过 50 k 行 Tailwind,100 % 与服务器组件兼容。
我们将介绍:
- 单个区块(
Hero01)的结构 - 该区块在
registry.json中的描述方式 - 通用的 Next.js 页面
/blocks/[name]如何按名称渲染任意区块 BlockProvider如何在不触及应用全局主题的情况下注入主题、屏幕尺寸和预览行为- 该设计如何与 React Server Components 和 Shadcn CLI 良好协作
2. 什么是 Shadcn “registry block”?
在高层次上,Shadcn 注册项是指向一个或多个代码文件并描述其依赖关系的 JSON 元数据。
| Type | Role | Example value |
|---|---|---|
| Component | 低层原语(按钮,输入) | registry:component |
| Block | 页面区块(hero,pricing,FAQ) | registry:block |
在我的注册表中: 30 个页面区块(hero ×5,pricing ×3,features ×5,CTA ×4,等)采用 registry:block 建模。
指标: 每个区块平均包含 3–5 个 Shadcn 原语(button、badge、card),通过 registryDependencies。
Source: …
3. 具体实例:Hero01
下面是我的实际 Hero01 —— 一个两列布局的 Hero,包含徽章、标题、描述、两个 CTA 按钮和一张图片:
interface Hero01Props {
badge?: string;
heading?: string;
description?: string;
buttons?: {
primary?: { text: string; url: string; icon?: React.ReactNode };
secondary?: { text: string; url: string; icon?: React.ReactNode };
};
image?: string;
className?: string;
}
关键设计决策
- 仅限营销属性 – 没有低层次的布局调节
- 纯展示组件 – 0 hooks,0 数据获取
- 支持服务器组件 – 没有
use client指令 - 组合 Shadcn – 使用
Button、Badge+ Tailwind 网格
用法
指标: Hero01 = 187 行,12 个 Tailwind 实用类,2 个 Shadcn 组件。

4. 在 registry.json 中描述块
我的 hero-01 注册项:
{
"name": "hero-01",
"type": "registry:block",
"title": "Hero 01",
"description": "Two-column hero with badge + CTAs",
"dependencies": ["lucide-react"],
"registryDependencies": ["button", "badge"],
"categories": ["hero"],
"meta": { "image": "/r/previews/hero-01.webp" },
"files": [
{
"path": "src/registry/blocks/hero-01/hero.tsx",
"target": "components/hero-01.tsx"
}
]
}
关键字段
registryDependencies→ CLI 自动安装button+badgecategories→ 为/blocks?category=hero过滤提供动力files.target→ 可预测的components/hero-01.tsx位置
指标: 30 个块 = 12 个类别,共 47 个依赖(npm + registry)。
5. BlockProvider:隔离的预览魔法
Problem: Preview 30+ blocks with theme switching + responsive breakpoints without breaking the site theme.
Solution: BlockProvider creates a self‑contained preview universe:
interface BlockContextValue {
block: SerializableRegistryBlock;
theme: Theme;
screenSize: ScreenSize; // 'mobile' | 'tablet' | 'desktop'
setTheme: (theme: Theme) => void;
setScreenSize: (size: ScreenSize) => void;
}
Why it works
- ✅ Theme changes stay inside
/blocks/hero-01 - ✅ Screen‑size toggle = instant CSS viewport classes
- ✅ 30+ blocks share one preview system
- ✅ Global site theme remains completely untouched
Metric: Provider handles 5 themes × 3 breakpoints = 15 preview variations per block.
6. Server components + registry = ⚡ 性能
服务器优先设计
- Blocks = 服务器组件(无
use client) - Registry JSON = 静态、构建时可读取的
- Preview shell = 最小化的客户端边界
结果
✅ Bundle size: Hero01 = 2.1 KB (gzipped)
✅ TTFB: /blocks/hero-01 = 89 ms
✅ Static pages: 30+ generated at build time
预览外壳(BlockProvider 及其控件)仅在需要时保持在客户端。
Source:
7. 整个注册表的单一路由
通过使用单一的动态路由(pages/blocks/[name].tsx 或 app/blocks/[name]/page.tsx),每个区块都可以按需渲染:
// pages/blocks/[name].tsx
import { getBlockByName } from '@/registry';
import BlockProvider from '@/components/BlockProvider';
export default async function BlockPage({ params }) {
const block = await getBlockByName(params.name);
return (
<BlockProvider block={block}>
{/* Render the block component dynamically */}
</BlockProvider>
);
}
- 不需要为每个区块单独创建页面。
- 让代码库保持 DRY(不要重复自己),并且添加新区块只需更新
registry.json即可。
TL;DR
- 定义 每个 UI 部分为
registry:block,写入registry.json。 - 实现 区块为仅接受营销层级属性的服务器组件。
- 渲染 任意区块时,使用单一的动态路由,并在
BlockProvider中包装,以实现独立的主题和视口预览。 - 享受 快速的构建、极小的 bundle,以及与 Shadcn CLI 完美契合的工作流。
祝构建愉快! 🚀
目录: /blocks/[name]
单个 Next.js 页面驱动 所有 30+ 区块
export async function generateStaticParams() {
return getBlocks().map(block => ({ name: block.name })); // 30+ pages!
}
export default async function BlockPage({ params }) {
const block = getBlock(params.name);
const { component, ...serializableBlock } = block; // Server‑safe
return (
<BlockProvider block={serializableBlock}>
{/* Render the block component */}
</BlockProvider>
);
}
自动生成: /blocks/hero-01, /blocks/pricing-01, /blocks/cta-01, …
指标: 22 个静态页面, 100 % 服务器渲染, 完整的 SEO 元数据。
8️⃣ CLI 集成
npx shadcn add @shadcnship/hero-01
我的注册表完全遵循 Shadcn 架构,因此 CLI 集成无缝进行:
# Direct URL (works now)
npx shadcn add https://mysite.com/r/hero-01.json
# Namespaced (after PR approval)
npx shadcn add @shadcnship/hero-01
CLI 的操作:
- 复制
hero.tsx→components/hero-01.tsx - 安装
lucide-react - 自动安装
button+badge
✅ 总计 30 秒
9️⃣ 经验教训(4 周后 + 30 个区块)
✅ 做
- 仅使用 营销属性(
heading、buttons)开始。 - 严格使用
registryDependencies。 - 保持区块 服务器组件优先。
- 在扩展区块之前构建预览系统 之前。
❌ 不要
- 暴露布局属性(
padding、gap)。 - 混合预览和站点主题状态。
- 在预览页面中硬编码区块名称。
- 忘记提供用于覆盖的
className属性。
最大收获: 通用 /blocks/[name] + BlockProvider = 每个区块零额外代码。
获取完整代码
30+ 生产就绪的区块,带预览系统、主题切换和注册表:
👉 GitHub:
👉 Live demo:
感谢您的时间。
祝使用愉快! 🚀
