A full-stack developer creates a custom Shadcn UI component registry. The registry allows him to easily reuse and share his own UI components across projects. The project uses a specific folder structure for registry items. It is deployed on Vercel, to host the registry at a public URL.A full-stack developer creates a custom Shadcn UI component registry. The registry allows him to easily reuse and share his own UI components across projects. The project uses a specific folder structure for registry items. It is deployed on Vercel, to host the registry at a public URL.

Design Once, Reuse Everywhere — How I Built a Custom Shadcn UI Registry with Next.js and Vercel

2025/10/15 04:03

Table of Contents

  1. Motivation and planning
  2. Setting up the Next.js project
  3. Organising components in the registry
  4. Defining the registry index (registry.json)
  5. Building the registry and generating JSON files
  6. Deploying to Vercel
  7. Conclusion
  8. Resources/Links

1. Motivation and planning

I decided to create a custom Shadcn UI component registry to easily reuse and share my own UI components across projects. As a full-stack developer, I’m passionate about building beautiful, accessible, and performant web apps, and I often craft custom components that I want to use in multiple projects. The Shadcn UI framework provides a CLI for installing components from a remote registry, so running my own registry made perfect sense. It would allow me to distribute a collection of my favourite UI patterns and components (like complex form fields, interactive blocks, etc.) through a central repository.

Before coding, I read through the Shadcn UI documentation on custom registries to understand the requirements. Essentially, a registry is just a web app (in my case, a Next.js app) that serves JSON definitions of components. The only hard requirement is that it must provide a registry.json file at its root URL (the index of all components), and individual JSON files for each component, conforming to Shadcn’s schema. With this in mind, I planned out the project structure and technology stack:

  • Next.js 13 with the App Router for the application framework.
  • Tailwind CSS v4 for styling, since the Shadcn template is configured for Tailwind 4.
  • shadcn/ui CLI and registry template as a starting point, to get the correct file structure and scripts for building the registry.
  • Deployment on Vercel, to host the registry at a public URL where the CLI (and other developers) can reach the JSON files.

2. Setting up the Next.js project

To kickstart development, I used the official Shadcn/UI registry template as my base . This template already had a Next.js project configured for a registry, which saved a lot of setup time. I initialised my project from the template and pushed the initial code to GitHub as elvinlari/shadcn-registry. The template project came with a default registry.json and some example components to demonstrate the structure (like a “hello-world” component). It also included the necessary Tailwind config, Next.js config, and a build script. Using this template ensured my registry would meet Shadcn’s schema and file layout from the start.

After cloning the template, I updated the project metadata. I changed the name and homepage fields in registry.json to reflect my project (giving my registry a unique name and linking to its website). I then removed the scaffolded templates in registry/new-york/blocks/ and registry/new-york/ui/and updated app/page.tsx with my own homepage content. I also verified that Tailwind CSS was set up correctly (the template was already using Tailwind v4, which I stuck with).

3. Organising components in the registry

With the scaffolding ready, I started adding my own components into the registry. The project uses a specific folder structure for registry items. All components live under the registry/ directory, grouped by a namespace (the template uses a default namespace called “new-york”). Under that, I organised components by category.

For example, I created a folder:

registry/ └── new-york/     └── blocks/         └── landing-page/             ├── landing-page.tsx             ├── components/             │   ├── announcement.tsx             │   ├── marquee.tsx             │   └── video-player.tsx             └── README.md 

Here, landing-page is a Block component; essentially a full page section composed of smaller parts. Inside its folder, I have a landing-page.tsx which assembles the block’s JSX, and a components folder containing dependency components. In this case, the dependency components include an announcement banner, a scrolling marquee text, and an embedded video player. I wrote each of those pieces as separate React components (announcement.tsxmarquee.tsxvideo-player.tsx) to keep things modular.

Organising components this way (each in its own folder with a clear structure) made it easy to manage and document them. I also added a README.md inside the landing-page folder, describing what the block and components do and how to use them. This README content is displayed on the registry site as documentation for that component.

To illustrate, here’s a simplified version of the marquee component that I included in the landing page block:

// registry/new-york/blocks/landing-page/components/marquee.tsx  "use client"    import React from "react"  export function Marquee({ text }: { text: string }) {   return (     <div className="overflow-hidden whitespace-nowrap bg-muted px-4 py-2">       <span className="animate-marquee text-sm font-medium">         {text}       </span>     </div>   ); } 

\ This Marquee component simply takes text and scrolls it (assuming a Tailwind CSS animation class animate-marquee is defined). I created similar React components for Announcement (perhaps a top banner with a message) and VideoPlayer (using an HTML5 video or iframe).

Then, in landing-page.tsx of the landing page block, I put these pieces together:

// registry/new-york/blocks/landing-page/landing-page.tsx  import { Announcement } from "@/components/announcement" import { Marquee } from "@/components/marquee" import { VideoPlayer } from "@/components/video-player"  export default function LandingPage() {   return (     <section className="landing-page">       <Announcement message="Welcome to my custom registry!" />       <Marquee text="Building with Shadcn UI is fun" />       <VideoPlayer src="/demo.mp4" />     </section>   ); } 

Notes:

  1. The above code is a representative example for illustration. In my actual code, the content and props were tailored to my needs.
  2. Use path aliases, not relative paths. In your registry source and item files, import from @/components/... instead of ./components/... so the generated code resolves correctly when a consumer installs it into a different folder structure. Relative imports are tied to your registry’s local file layout; once the files are copied into another project, those ./ paths can break because the install location (e.g., app/components/, or a different nesting) won’t match your repo. The @ alias is project-rooted and is what most Next.js/shadcn setups expect, so it remains stable across projects.

Bad (relative, may break after install):

import { Button } from "./components/ui/button" import { Announcement } from "./components/announcement" 

Good (alias, portable):

import { Button } from "@/components/ui/button" import { Announcement } from "@/components/announcement" 

4. Defining the registry index (registry.json)

The heart of the registry is the registry.json file in the project root. This file serves as an index of all components/blocks available. Each entry in registry.json lists a component’s name, type, title, and the files that make it up, among other metadata.

After adding my components, I edited registry.json to register them. For example, I added an entry for the Landing Page block like this:

{   "$schema": "https://ui.shadcn.com/schema/registry.json",   "name": "Lynqsides Shadcn Registry",     "homepage": "https://shadcn-elvinlari-registry.vercel.app",   "items": [     {       "name": "landing-page",       "type": "registry:block",       "title": "Landing Page",       "description": "A full landing page section with announcement banner, marquee, and video player.",       "dependencies": ["@icons-pack/react-simple-icons", "@number-flow/react", "media-chrome", "react-fast-marquee"],       "registryDependencies": ["button", "card", "badge", "tabs"],       "files": [         {           "path": "registry/new-york/blocks/landing-page/landing-page.tsx",           "type": "registry:block"         },         {           "path": "registry/new-york/blocks/landing-page/components/announcement.tsx",           "type": "registry:ui"         },         {           "path": "registry/new-york/blocks/landing-page/components/marquee.tsx",           "type": "registry:ui"         },         {           "path": "registry/new-york/blocks/landing-page/components/video-player.tsx",           "type": "registry:ui"         }       ],       "cssVars": {         "theme": {           "font-heading": "Poppins, sans-serif"         },         "light": {           "brand": "20 14.3% 4.1%",           "radius": "0.5rem"         },         "dark": {           "brand": "20 14.3% 4.1%"         }       },       "css": {         "@layer base": {           "html": {             "scroll-behavior": "smooth"           }         }       }     },     // ... other items ...   ] } 

I set "type": "registry:block" because the landing-page is a composite, full-page section rather than a single UI primitive. Under dependencies, I list external npm packages the consumer must get (@icons-pack/react-simple-icons@number-flow/reactmedia-chromereact-fast-marquee). Under registryDependencies, I declare other shadcn items this block relies on (buttoncardbadgetabs); the CLI will fetch those from the upstream registry, so the block works out of the box. The files array is the install payload: one entry for the orchestrating block file (registry:block) and entries for each supporting piece in components/ (registry:ui). This tells the CLI exactly which files to copy into the target app and their default installation location.

You can also ship design tokens via cssVars: a global theme token for the heading font (Poppins, sans-serif), plus light/dark palette values like brand (HSL triplet) and a radius for rounding. These integrate with shadcn/Tailwind’s variable-driven colours (e.g., bg-backgroundtext-foregroundring, etc.). Finally, I appended a global style through css by contributing to @layer baseto enable smooth scrolling on html. Together, this entry ensures the block, its subcomponents, its npm deps, its shadcn deps, and its theme/base CSS all install and “just work” when someone runs shadcn add against my registry.

5. Building the registry and generating JSON files

Once the code for components and the registry.json index were in place, I used the Shadcn CLI to build the registry. The command:

pnpm registry:build 

scans the registry.json and the referenced component files, and produces the distributable JSON files for each item. After running this, my Next.js app’s public directory was populated with a JSON file per component (as well as an overall registry.json for the index) that will be served to users. For example, public/r/landing-page.json was generated, containing the combined content and metadata of the Landing Page component. The CLI automates this conversion from my source files to the JSON schema that other projects can consume. Having these as static files means the registry doesn’t need a live database. Any request to https://shadcn-elvinlari-registry.vercel.app/r/landing-page.json will just return the JSON describing the component (Next.js serves it like a static asset). This static approach makes hosting easy and caching efficient.

:::info Note: The template provided a dynamic Next.js route handler for registry items, but since we output static files, the dynamic route simply proxies those or handles any special cases. In practice, I found the static files sufficient for all my components.

:::

6. Deploying to Vercel

With everything tested locally, I was ready to deploy. I pushed the final code to the GitHub repo and connected it to Vercel for continuous deployment. The deployment process on Vercel was straightforward: I set up a new project, pointed it to my GitHub repository, and Vercel handled installing dependencies and building the Next.js app. Since this is a Next.js App Router project, Vercel recognized it and optimized the build for me.

I ended up doing a few quick-fix commits to adjust the domain value once the app was live (making sure there were no leftover references to a localhost or template example domain). Those “fix: edit domain” commits in my history were exactly for this.

After a successful deployment, I visited my registry’s URL and saw the documentation site up and running. The homepage of my registry site includes a list of components available and an introduction (I added a personal About the Author section with my social links, to give it a nice personal touch). Each component has its own page showing how to use it and a preview of the component in action. For example, the Landing Page block’s page displays the announcement banner, marquee, and video player demo, as well as the code snippets.

Finally, I tested the integration end-to-end. I went to a separate test Next.js project and tried installing a component from my registry using the Shadcn CLI. I ran the following command:

pnpm dlx shadcn@latest add https://shadcn-elvinlari-registry.vercel.app/r/landing-page.json 

This command pulls landing-page.json from my public registry, copies all referenced files into the project (mirroring my registry’s folder structure), and installs both the npm dependencies and shadcn registryDependencies the block needs. It also applies any cssVars and merges the item’s css into the project’s global styles (or writes the shipped CSS file if I included one). Result: my custom components were ready to use immediately, with code and styles fully wired up.

Conclusion

In summary, I built my own Shadcn UI registry by leveraging Next.js and the Shadcn registry template. I authored custom components, defined them in a registry.json index, and used the Shadcn CLI’s build process to generate JSON specs for each component . The project doubles as a documentation site where I (and others) can browse and preview components, and it serves as a distribution mechanism so that any React project can install these components via one CLI command. Deploying on Vercel made it easy to share my registry with the world — it’s fast and globally accessible.

This was a really fun project because it combined my love for building reusable components with the excitement of creating a platform others can use. I now have a personal UI library, “Lynq Components”, that is just a pnpm dlx shadcn add away for any of my future projects. Feel free to check out the registry (https://shadcn-elvinlari-registry.vercel.app) and reach out if you have any questions or suggestions. Happy coding!

I hope this has been a helpful guide. Thank you for reading!

https://ui.shadcn.com/docs— Shadcn/UI Documentation \n https://github.com/shadcn-ui/registry-template.git—Shadcn/UI Registry Template \n https://github.com/elvinlari/shadcn-registry.git—My Custom Registry Code \n https://shadcn-elvinlari-registry.vercel.app —My Deployed Shadcn Registry

\n

Disclaimer: The articles reposted on this site are sourced from public platforms and are provided for informational purposes only. They do not necessarily reflect the views of MEXC. All rights remain with the original authors. If you believe any content infringes on third-party rights, please contact service@support.mexc.com for removal. MEXC makes no guarantees regarding the accuracy, completeness, or timeliness of the content and is not responsible for any actions taken based on the information provided. The content does not constitute financial, legal, or other professional advice, nor should it be considered a recommendation or endorsement by MEXC.

You May Also Like

Best Crypto to Buy as Saylor & Crypto Execs Meet in US Treasury Council

Best Crypto to Buy as Saylor & Crypto Execs Meet in US Treasury Council

The post Best Crypto to Buy as Saylor & Crypto Execs Meet in US Treasury Council appeared on BitcoinEthereumNews.com. Michael Saylor and a group of crypto executives met in Washington, D.C. yesterday to push for the Strategic Bitcoin Reserve Bill (the BITCOIN Act), which would see the U.S. acquire up to 1M $BTC over five years. With Bitcoin being positioned yet again as a cornerstone of national monetary policy, many investors are turning their eyes to projects that lean into this narrative – altcoins, meme coins, and presales that could ride on the same wave. Read on for three of the best crypto projects that seem especially well‐suited to benefit from this macro shift:  Bitcoin Hyper, Best Wallet Token, and Remittix. These projects stand out for having a strong use case and high adoption potential, especially given the push for a U.S. Bitcoin reserve.   Why the Bitcoin Reserve Bill Matters for Crypto Markets The strategic Bitcoin Reserve Bill could mark a turning point for the U.S. approach to digital assets. The proposal would see America build a long-term Bitcoin reserve by acquiring up to one million $BTC over five years. To make this happen, lawmakers are exploring creative funding methods such as revaluing old gold certificates. The plan also leans on confiscated Bitcoin already held by the government, worth an estimated $15–20B. This isn’t just a headline for policy wonks. It signals that Bitcoin is moving from the margins into the core of financial strategy. Industry figures like Michael Saylor, Senator Cynthia Lummis, and Marathon Digital’s Fred Thiel are all backing the bill. They see Bitcoin not just as an investment, but as a hedge against systemic risks. For the wider crypto market, this opens the door for projects tied to Bitcoin and the infrastructure that supports it. 1. Bitcoin Hyper ($HYPER) – Turning Bitcoin Into More Than Just Digital Gold The U.S. may soon treat Bitcoin as…
Share
BitcoinEthereumNews2025/09/18 00:27
The Future of Secure Messaging: Why Decentralization Matters

The Future of Secure Messaging: Why Decentralization Matters

The post The Future of Secure Messaging: Why Decentralization Matters appeared on BitcoinEthereumNews.com. From encrypted chats to decentralized messaging Encrypted messengers are having a second wave. Apps like WhatsApp, iMessage and Signal made end-to-end encryption (E2EE) a default expectation. But most still hinge on phone numbers, centralized servers and a lot of metadata, such as who you talk to, when, from which IP and on which device. That is what Vitalik Buterin is aiming at in his recent X post and donation. He argues the next steps for secure messaging are permissionless account creation with no phone numbers or Know Your Customer (KYC) and much stronger metadata privacy. In that context he highlighted Session and SimpleX and sent 128 Ether (ETH) to each to keep pushing in that direction. Session is a good case study because it tries to combine E2E encryption with decentralization. There is no central message server, traffic is routed through onion paths, and user IDs are keys instead of phone numbers. Did you know? Forty-three percent of people who use public WiFi report experiencing a data breach, with man-in-the-middle attacks and packet sniffing against unencrypted traffic among the most common causes. How Session stores your messages Session is built around public key identities. When you sign up, the app generates a keypair locally and derives a Session ID from it with no phone number or email required. Messages travel through a network of service nodes using onion routing so that no single node can see both the sender and the recipient. (You can see your message’s node path in the settings.) For asynchronous delivery when you are offline, messages are stored in small groups of nodes called “swarms.” Each Session ID is mapped to a specific swarm, and your messages are stored there encrypted until your client fetches them. Historically, messages had a default time-to-live of about two weeks…
Share
BitcoinEthereumNews2025/12/08 14:40