Rebuilding my portfolio
Finally implementing FJ-Elements and taking on the world
Rebuilding my portfolio
Finally implementing FJ-Elements and taking on the world

If you’ve been following along, you know this has been a bit of a journey. In part 1, I talked about why I needed to escape the chaos of “utility class soup” and why I wanted to implement a strict design system. In part 2, I walked through the actual execution of building my own Atomic UI library — FJ-Elements — from scratch inside a clean, isolated Storybook sandbox.
Everything works great in a sandbox. It’s a controlled environment, but now that I’ve finally taken the leap; I packaged the library, published it to NPM, and used it to completely rebuild my portfolio site from the ground up.
Here is exactly how i got onto NPM, what broke when I tried to use it, and the honest lessons I learned from rolling out my own frontend engine.
The launch Plan: where to begin?
Before I could even start thinking about getting the code onto NPM I had to come up with a plan. I needed to figure out my order of operations and how I wanted to tackle it. I know it’s great to get on NPM but where do I go from there? This was all new territory for me, so I wanted to be prepared and I wanted to go into this with a plan.
- Step 1 — overhaul the package.json
- Step 2 — wrangle the bundler configuration
- Step 3 — publish to the public registry
- Step 4 — the portfolio
- Step 4 a —strip back to basics within the portfolio
- Step 4 b — implement the library and rebuilt from the ground up
This is my plan and I was going to stick to it as closely as I could.
Step 1: Overhauling package.json
My local configuration was set up for development, so I had to make some mandatory changes to prepare it for public distribution:
- Goodbye Private: I deleted
"private": trueso NPM would actually allow the upload. - The Version Bump: I bumped the version string from
0.0.0to my official baseline1.0.0. - Distribution Entry Points: I added
main,module, andtypesdeclarations. These fields act as signposts that tell any consuming application exactly where to look inside the compiled/distfolder for the JavaScript, ESM modules, and type definitions. - The Files Array: I added
"files": ["dist"]. This is crucial because it tells NPM to ignore all my internal stories, configurations, and raw source assets. It ensures I'm only shipping the lean, optimized production bundle to the registry. - Managing Peer Dependencies: I moved
reactandreact-domout of standard dependencies and intopeerDependencies. If you leave them as standard dependencies, anyone who installs your library is forced to download a secondary copy of React into their nestednode_modules. That’s a fast track to broken hook lifecycles and runtime crashes.
Step 2: Wrangling the Bundler Configuration
Because my components rely heavily on scoped CSS Modules, a standard TypeScript compile build command isn’t enough. If someone installed the raw package, the styles would either be completely missing or ignored by their compiler.
To fix this, I pulled in two essential Vite plugins: vite-plugin-dts to handle the .d.ts TypeScript definitions, and vite-plugin-css-injected-by-js. The CSS plugin is a lifesaver—it injects the scoped styles directly into the compiled JavaScript payload, meaning I don't have to manually import a massive, monolithic style.css file in the root of my portfolio app.
Next came the boring but necessary part: creating barrel exports. I added an index.ts file to every single subfolder (atoms, molecules, organisms, layout, and typography) to clean up the export paths, and then tied them all together in a master index.ts file in the src root.
After updating my vite.config to make sure my Storybook testing suite and new production build paths weren't stepping on each other, I ran npm run build, hoped for the best, and watched it compile without errors.
Step 3: Publishing to the Public Registry
Publishing was surprisingly straightforward. I logged into my account directly from the terminal via npm login, authenticated using a one-time passcode on the web browser, and typed the magic words:
npm publish --access public
Of course, the first attempt failed. Classic. But for once, it actually wasn’t a code bug! NPM requires you to have Two-Factor Authentication (2FA) fully enabled before you can push public packages. Once I flipped that switch in my account settings and ran it again, it went through perfectly. Seeing FJ-Elements live on the official NPM registry was amazing to see.
Step 4: the portfolio implementation
With the package live, I jumped over to my portfolio project folder, spun up a new git feature branch from main, and did something that felt very very scary; I cleared out almost the entire src folder so I could start fresh.
I hummed and hawed for a while about the best way to tackle the rewrite. Should I just throw a giant soup of my new library components onto a test page to see if they rendered? Or should I go top-to-bottom, building the navbar, the hero, and moving down the page to replicate what was already live? I decided to go with the top-down approach.
The endless bumping loop
I started with the Navbar. I imported it, ran the dev server, and—boom—it actually showed up on the screen exactly how it looked in my sandbox. I felt like a genius. Then I clicked one of the navigation links and the entire application immediately exploded.


It turned out I had accidentally left a bunch of old, dead internal component imports pointing to files that no longer existed.
Once I cleaned that up, the real friction started. It’s one thing to look at a component completely isolated in a cozy Storybook sandbox, but seeing it sit inside a live application layout changes your perspective instantly. The font sizes felt slightly off, alignments looked weird, and little design bugs became glaringly obvious.
This kicked off a relentless game of development ping-pong. I would find a small visual bug in the portfolio, open up the library codebase, make a tweak, rebuild the assets, bump the semantic version number, publish to NPM, run npm update in the portfolio terminal, and see if it fixed it. I went through this loop more times than I care to admit just trying to get the baseline typography sizing right. One component down, and I didn't even want to think about how many were left to go.
Facing the Custom Component Dilemma
When I moved onto the Hero section, things went relatively smoothly using my layout components to get elements positioned where I wanted them. But I quickly ran into a core philosophical problem: even though I am building a global library, a website is always going to need some highly specific, custom visual pieces.
I made a mental note that I might generalise some of these unique sections and bake them into the library in the future. But at this early stage — where I’m just trying to get an MVP out the door — it made way more sense to build those hyper-specific pieces locally in the portfolio project, rather than cluttering my atomic library with one-off code.
As I moved further down the page, I kept asking myself: Should I bake this specific customisation option directly into the global component library, or should I leave it as a local override for this specific implementation? I’m strongly leaning toward keeping the library strict and handling variations locally because it preserves layout flexibility, but I’d honestly love to hear how other developers draw that line.
The Breakdowns and Final Implementation
Ironically, the Skills section was one of the absolute easiest parts to move over—not because the library made it simple, but because it was almost completely custom code anyway! Aside from wrapping the text elements in my library's Typography component, the skills grid was essentially a direct copy-and-paste of the custom animated layout I had already written for the old site.
However, pulling that section over exposed a major blind spot in how I designed my grid system’s API. I realised that my library’s layout Cell component wasn't playing nice with custom styles passed down from the application.
When I tried to pass unique visual properties or custom padding directly to a cell via a style prop, it would completely overwrite the internal CSS variables handling the responsive grid gutters. The cells smashed flush against each other, and my layout spacing vanished.
I had to pause, go back to the library, and rewrite the underlying Cell and GridX components so that they safely extended native HTML attributes and deeply merged incoming external styles with my internal flex calculations.
The Verdict
After a lot of back-and-forth, version bumping, and style refactoring, I finally got the entire portfolio converted over to use FJ-Elements.
Honestly? It is an incredibly rewarding feeling to build your own UI engine from scratch, ship it to the world, and watch it power your actual live website in real time. Seeing all that hard work translate into a functional, fast production site makes the initial sandbox setup completely worth it.
If there is one major lesson I’m taking away from this integration phase, it’s that flexibility is everything. Moving forward, whenever I build new components or go back to update my current ones, my top priority is going to be ensuring they support clean extension APIs — whether that’s accepting custom classes easily or allowing safe style overrides without breaking the layout rules.
The project is live, but it’s definitely not finished. I’m going to keep building on top of this system, so keep your eyes peeled for future updates as I roll out new layout tools and components.
If you’re working on your own design system or have ideas for components you’d love to see added to an atomic library, drop a comment or reach out — I’d love to hear your thoughts!
And if you’re interested checkout parts 1 and 2 here FJ Elements part 1
메타데이터
- post_id
- 2be4c13729e4
- slug
- rebuilding-my-portfolio-2be4c13729e4
- url
- https://medium.com/@joshhallan/rebuilding-my-portfolio-2be4c13729e4
- canonical_url
- https://medium.com/@joshhallan/rebuilding-my-portfolio-2be4c13729e4
- author_url
- https://medium.com/@joshhallan
- status
- ok
- fetched_at
- 2026-06-09 14:34:10