When a single repository isn't enough.
March 2026
The turning point
Before this portfolio, I had already built others. Several, actually. Different designs, different repositories, but always the same structure: a Next.js project with everything inside.
And it worked. Until I decided this portfolio was going to be different.
This time it wasn't just a project showcase. I wanted a blog, a quote form that collects everything at once, a projects page with full case studies. And if I had all of this on the public site, I needed somewhere to manage it.
Quotes coming in, inbox messages, blog posts to review. Was I going to put all of that inside the same portfolio repository?
The problem with everything together
I already knew how it would end. The src folder would grow, files would multiply, and at some point I'd be spending time figuring out where things are instead of coding.
But it wasn't just about organization. There were practical issues that bothered me:
‒ Mixed performance profiles — the public portfolio needs to be light and fast. The admin handles databases, integrations and heavy logic. Completely different profiles sharing the same build
‒ Unnecessary middleware — authentication, route protection, session checks. The portfolio doesn't need any of that. But with everything together, middleware runs for all routes
‒ Coupled deploys — a change in the admin forced a full portfolio rebuild. And vice versa. Build time growing for no reason
‒ Limited customization — each app has its own identity. The portfolio has one visual language, the admin has another. With everything together, any customization becomes a mess of conditionals
The decision was clear: separate.
The structure that emerged
I set up the monorepo with Turborepo and pnpm workspaces. Today it has two apps and several shared packages:
‒ apps/web — the public portfolio. Blog, projects, quotes, contact. Runs as lightweight as possible, focused on performance and SEO
‒ apps/admin — admin panel. Manages quotes, messages, blog posts, social media publishing. This is where the heavy logic lives
And the shared packages are what ties everything together:
‒ packages/ui — interface components reused across both apps
‒ packages/database — Drizzle schema, Neon (PostgreSQL) connection, fully typed end to end
‒ packages/sanity — client, GROQ queries, types and CMS utilities
‒ packages/email — email templates with React Email (confirmation, reply, newsletter)
What surprised me
The first thing that surprised me was pnpm dev. One command at the root and both apps spin up simultaneously, each on its own port. Sounds simple, but when you're used to opening two terminals and running two separate projects, it's a huge difference in your daily workflow.
The second was real sharing. I change a component in packages/ui and it updates in both apps instantly. I change the database schema in packages/database and both admin and web already have access to the updated types. No publishing packages, no copying files. One source of truth.
And the third was realizing how much better each app breathes when separated. The portfolio builds in seconds because it doesn't carry the admin's complexity. The admin can have its own heavy dependencies without affecting the public site's load time. Each one does its thing, without getting in the other's way.
What it cost
Not everything was smooth. Setting up shared imports between packages took work. Not because it was conceptually hard, but because the errors that showed up were confusing — module resolution issues, TypeScript complaining about paths, exports that weren't recognized.
None of these errors were impossible to fix. But each one took longer than it should to diagnose, because error messages rarely pointed to the real problem. You'd think it was the import, but it was the exports field in package.json. You'd think it was TypeScript, but it was the field order in tsconfig.
"The hardest part of a monorepo isn't the architecture. It's convincing TypeScript that your packages exist."
What I'd do differently
If I started from scratch today, I'd make the same choice. But I would have defined the shared package structure from day one, instead of extracting things as needed. When you extract later, you end up with broken imports and refactors that could have been avoided.
I would also have spent more time upfront understanding how each package's package.json needs to be configured. Most of the errors I had came from poorly defined exports. One hour reading the docs at the start would have saved hours of debugging later.
This is just the beginning
The monorepo opened doors I hadn't thought of when I started. The ability to add new apps without reconfiguring everything, to have a codebase that grows in an organized way, to share without duplicating. There's more coming.
I plan to document each addition here on the blog — what I did, why I did it, and what I learned.
If you want to follow this journey, find me on LinkedIn. And if you have a project in mind, check out the quote form — that's exactly what it's there for.
"Organization isn't about having less code. It's about knowing where everything lives."