nextjs-components
Early last year, I began transcribing the react components from Vercel's component library site for use on my personal site. It was initially closed source but now in 2022, I decided to open source it. This is a reflection of how I tackled it and some thoughts and learnings I had along the way.
Kevin Wang
“I just want to see some code!”
- GitHub repo: nextjs-components/nextjs-components
- npm link: nextjs-components
- Docs site: https://nextjs-components-thekevinwang.vercel.app/
npm i nextjs-components@latest
Background
Here's a tiny bit of background of how I arrived at the idea to transcribe Vercel's components. I made my first site in 2018, with Create React App V1, started a separate Markdown blog with Gatsby V2 in 2019, and eventually rewrote the first site as an MDX blog with Next.js in 2021.
All along the way, I found myself doing a considerable amount of CSS work like figuring out which paradigms to use as well as designing my own components. I eventually decided that this class of work was not something I was interested in anymore (I was probably focused on Cognito or Dynamo at the time), but I still wanted some solution to make a pretty site.
Enter, Vercel design.
These were probably the best looking components I had come across, but they were/are unfortunately closed source. I wanted to use them so I began transcribing them directly from the site directly into my website repo, which was also closed source.
Why didn't I open source sooner?
I initially developed these components in my closed source website repository. I kept the repo closed source because I thought I was doing two cool new things:
- Deploying my site with the AWS CDK (v1.91.0, released Feb 23, 2021) which I don't think was popular at the time... so I thought I was doing something hip and new and I wanted to keep it secret. In retrospect, this was pretty naive.
- And of course, transcribing Vercel components. I occasionally came across random tweets where some developer would copy another developer's open source portfolio, only to pass it off as their own in an interview. This is pretty evil. So I kept my code closed because I didn't want others to potentially take advantage of my free labor in that same way. Today, I simply don't care as much and it's not like my code is super amazing or perfect. Copy away! ...And speaking of copying... look at
nextjs-components
itself 😆.
Transcribing
Back in my college years at the New School for Jazz and Contemporary music, I used to transcribe solos of old jazz greats like Hank Mobley and Bird to learn the language, learn how to improvise in a way that didn't sound like actual garbage, and pay my dues as a student of the art.
This process of transcibing Vercel's components comes from a similar place — a place of respect, admiration, and learning from someone who designed and created these things before me.
In engineering terms, you can think of transcribing as reverse-engineering, but that sounds super lame.
CSS
I relied most heavily on the Chrome inspector for my transcription needs. The first step was usually copying over a large chunk of style declarations to a local css module.
Then I was able to conclude a few things from an element based on the DOM element.
In an example such as the following,
styled-jsx
jsx-3783837704
meant that styled-jsx was used
Global styles
geist-container
meant that there was a global class declaration somewhere
CSS modules
badge_badge__LNH0p
meant that a css module was used. In this case the
file would be badge.module.css
and the selector would be the
derived by stripping away the module & hash in [module]_[local]__[hash:base64:5]
— so, .badge
JSX style={{...}}
--flex:1; --justify-content:center; --align-items:center;
meant that css variables
were set via the style
attribute on a JSX element.
For dynamic class names, whether they were via props or state updated by user interactions, I used my best judgement and handled that in JSX logic with the help of clsx.
React Devtools
At times, there were complex behaviors that weren't easy to conclude just through the chrome inspector, so I reached for the React Devtools to view things like hooks usage, or context structures.
I noticed a few hook names that were shockingly close to react-aria's, so I added that as a reasonable dependency. It's also a great library in general 👏.
Viewing source
There were times that I found myself needing to grok the bundled JS source (not pretty), when I couldn't figure out what I needed through the inspector or react devtools alone.
Result
The result of transcribing a single component looked something like...
npm
Bundling and publishing this project to a public registry, like npm, from scratch was actually new to me. I had previously only worked around existing CD pipelines, like Jenkins → Artifactory, or GitHub actions to npm, and hadn't dealt with the end-to-end process myself, so this was cool to figure out.
TypeScript support
I started with TypeScript from the beginning. This was a no brainer.
Babel over SWC, unfortunately
I had initially reached for swc for .tsx
to .js
compilation needs, and it was fast A.F.
2555.26% times faster than babel... (is my math correct here? 🤨)
But there isn't any existing support/plugins for transforming styled-jsx, so I needed to use babel instead. It was significantly slower, but realistically very much an acceptable trade off at my scale.
💭 The TypeScript compiler is probably worth looking into as well.
Picking a name
Choosing some cool hip package name was not a rabbit hole that I wanted to waste my
time going down, so I picked a name on a whim and called it a day — nextjs-components
.
Publishing
After compiling and copying over files to an output folder, npm publish
was all I needed
to get nextjs-components live (an npm account is required of course).
There are some missing automation pieces that I'm looking to add in the near future.
Takeaways
Editor
component
Figuring out how to build the live Editor
component was one of the cooler things
I discovered and involved a very different angle of thinking that I hadn’t really
come across previously. It was effectively component-ception — you’re writing JSX
to render an editor that allows your write JSX in your browser which then renders
components.
Further more, wiring the Editor
up as an MDX component was yet another layer of
brainf**k as the MDX document needs access to one "scope" of components, which
includes the Editor
itself, but then the Editor
embedded in MDX needs its own
"scope" of components as well. This was very awkward to wrap my head around for the
first time, but it has since become less foregin.
Editor
demo component from this blog post because I ultimately
removed it from the nextjs-components
library — it was unecessary bloat.
It still lives on in the repository, but only on the docs site code. https://nextjs-components-thekevinwang.vercel.app/design/gridFriction
There were a few components like Entity and micro-interactions like focus ring and hover behavior on Button that were very tedious and tricky to nail down.
There are also several inconsitencies on the Vercel design page with one being multiple
implementations of Button
despite there being only one documented component. So I had
decide on what I wanted to avoid transcribing.
Next Steps
This project is still a work-in-progress (v0.1.0-rc50
at the time of writing this).
There are still several complex components that I haven't gotten around to transcribing,
and there are some components that are in-progress.
See the repo projects for work being tracked.
I'll do my best to keep this up to date in between full time work and life priorities
Give it a star ⭐️ if you like what you see!
If you have any questions or comments, please don't hesitate to reach out directly! 🙏
Thanks for reading! 🙇🏻