SwiftShelf is a community-driven library of SwiftUI components. This post covers how it went from a weekend idea to something other people can contribute to, including the first architecture I had to throw away.
Why I built it
I'd just started a new project and needed a login form. Finding SwiftUI inspiration meant opening a dozen GitHub repos, scrolling through READMEs and clicking into folders just to see what a component looked like. Whenever I needed UI ideas, I ended up:
- Searching GitHub repos with no previews
- Opening Figma files that had designs but no code
- Bookmarking random Stack Overflow threads with snippets
- Forgetting where I'd seen the component I wanted
What I wanted was a place to just see SwiftUI components next to their code, ready to copy. Not a design system or a tutorial site, just a gallery. So I started building one that weekend.
The first version
The plan was simple:
- Screenshots of each component
- Copy-paste ready SwiftUI code
- Community contributions, since I couldn't build everything alone
- Some kind of validation to keep quality up
- Fast, even as the library grows
The first version took about three days: a Next.js app with a few components I'd made myself. Each component had a YAML file for metadata, a Swift file with the code and a screenshot. I deployed it to Vercel and showed it to a friend, who asked the obvious question: how do other people contribute? That's what pushed me to treat it as a real project.
The architecture that didn't scale
To load the component data, I wrote a build script that:
- Read every snippet in the
/snippets/folder - Parsed the YAML, the Swift code and the screenshot paths
- Generated one big
snippets-data.tsfile with everything embedded - Bundled it with Next.js
With 4 components, the generated file was already 845 lines, over 200 lines per component. At that rate, 100 components would mean around 20,000 lines of generated code, a huge bundle, slow builds and a massive git diff every time someone added a component.
I added a few more components to check, and it was worse than I expected:
- Build time: 45 seconds, and growing
- JavaScript bundle: already 8 MB
- Page load: almost 4 seconds on a fast connection
It worked, but it wasn't going to survive a dozen contributions, which is the whole point of a community library.
Reading files at build time instead
I spent a few days reading about how other sites handle large amounts of content: incremental static regeneration, edge functions, different loading strategies. The answer was in the Next.js docs on Server Components: I didn't need to generate a TypeScript file at all.
Instead, the data loading runs on the server at build time:
- Keep the file-based structure, because it's easy for contributors to understand
- Read the files directly in a Server Component during
next build - Send only the data each page needs to the client
- Let Next.js generate static HTML
The old flow was: build script → read files → generate a TypeScript file → bundle it all. The new one is: next build → Server Component reads the files → static HTML. Users get pre-rendered pages and only about 120 KB of JavaScript for the interactive parts. The code is open source if you want the details: github.com/luizmellodev/SwiftShelf.
The difference after the rewrite:
| Before | After | |
|---|---|---|
| Build time | ~45 s | ~15 s |
| JavaScript bundle | ~8 MB | ~120 KB |
| First load | ~4 s | ~0.8 s |
| Generated code | 20,000+ lines projected | none |
Build time now grows by roughly 0.1 s per component, and the bundle size stays the same no matter how many components there are.
Organizing each component's code
Next problem: how should contributors structure their code? Some components had several files (view, model, extensions), others were one file with everything, and only some used // MARK: comments. That would be confusing for anyone submitting a component.
So contributors write a single Swift file, and SwiftShelf splits it for display. I wrote a small parser for that. It's not a compiler, it just understands enough structure:
- Split the code into lines
- Use
// MARK:comments as section boundaries - Without MARK comments, fall back to code structures (
struct,enum,class,extension,#Preview) - Group the lines into sections
- Name each section's file from its title
A snackbar component written with MARK comments shows up on the site as separate tabs, like SnackbarType.swift, SnackbarView.swift and Preview.swift, each with syntax highlighting so you can copy just the part you need. Contributors only have to write clean Swift with MARK comments, which most iOS developers do anyway.
Making contributions easy to review
I've had PRs sit for days on other open source projects because of formatting or missing metadata, so I wanted the technical checks to be automatic. Every pull request runs a GitHub Action that validates:
- Folder structure:
component-name.usernamein kebab-case, with the username matching the GitHub username in the metadata. - Metadata (
meta.yml): requiredtitle,author,github-username,tagsanddescription, tags from an allowed list, at most 3 tags, valid YAML. - Swift code (
snippet.swift): mustimport SwiftUIand define at least oneView. - Screenshot (
screenshot.png): PNG, under 500 KB, 800–2000 px wide and 1000–3000 px tall, ideally 9:16.
The errors say exactly what's wrong, and for invalid tags it suggests the closest valid ones. The full script is in the repository.
So a contribution goes like this: fork the repo, add the component, open a PR, and the checks run within seconds. If they pass, I only need to review the component itself. Once it's merged, Vercel deploys it and it's live a few minutes later.
Stack
- Next.js 14 with Server Components, which is where the performance gains came from
- TypeScript, Tailwind CSS and Radix UI for accessible components
- Node.js build scripts, js-yaml for metadata and ImageMagick for image checks
- The Swift parser described above
- Vercel for hosting and GitHub Actions for validation
No database
Every component lives in the repository as three files: the metadata, the Swift code and a screenshot. No database, no API keys, no setup. Each component is isolated, so it's easy to review, version control comes for free, and the folder name credits the author.
Two decisions shaped the project:
- Files instead of a database. Zero configuration, git history, offline development, free hosting and easy rollbacks. The trade-off is no complex queries, which a gallery doesn't need.
- Build time instead of runtime. A small bundle, fast loads and no server to run. All the data is read during
next buildand baked into static HTML.
Where it is today
At the time of writing, SwiftShelf has 5 components, builds in about 15 seconds, ships a 120 KB bundle, and every PR gets checked automatically. The part I care about most is people I've never met finding the components useful and sending their own.
Next on the list:
- Search, so components stay easy to find as the library grows
- Better syntax highlighting in dark mode
- Variations, to show different states of the same component
Maybe later: a live code playground, version history per component and some basic analytics. But I'd rather see how people use it first than build features from a roadmap I wrote alone.
What I learned
- My first architecture was bad, and I needed to build it to see why. Measuring it is what led to the better one.
- Server Components changed how I think about data loading. Reading files at build time instead of bundling everything was the fix.
- Automation is what makes community contributions work. With the boring checks automated, I can focus on the content.
- Start simple. No database, no auth, no backend. Just files in a repository, and it works.
Contributing
SwiftShelf is open source and contributions are welcome:
- Fork the repository
- Add your component using the three-file structure
- Run
npm run validatelocally - Open a pull request
The contribution guide has the details. If you build SwiftUI apps, take a look at SwiftShelf, and if you've made something you're proud of, send it over.
Technical Resources
- Live site: swift-shelf.vercel.app
- GitHub repository: github.com/luizmellodev/SwiftShelf
- Architecture documentation: ARCHITECTURE.md
- Contribution guide: CONTRIBUTING.md
- Mini article: A condensed version of this story