The setup
One codebase, two experiences
- The Marketplace lives in two places: a gallery inside every Freshworks product, and a public website.
- One monorepo powers both, so they share components and look like one product.
One monorepo
Shared components one UI, both places
In-product gallery
SPA inside every Freshworks product
Vue 2 · Vue CLI · VuexVue 3 · Vite · Pinia
Public website
SSR at freshworks.com/apps
Nuxt 2Nuxt 3 · Vite
Why now
The clock on Vue 2
- Vue 3 became the default in 2022, and Vue 2 support was ending in December 2023. Staying put meant a toolset slowly going stale.
- Vite, Pinia and Vitest were becoming the new defaults, and a UI revamp was already on the roadmap with marketing dates attached.
Vue 3 is faster, smaller, more maintainable and it’s easier to target native.— Evan You
Plan first
Six upgrades, one quarter
- Vue 3, Nuxt 3, Vite, the Composition API, Pinia, and Crayons in place of Element UI. Each one weighed, estimated and written up for the team to review.
- The checklist paid off early: non-critical UI could wait, and backend asks went out ahead so that work ran in parallel.
Prove it
A POC before rocking the boat
- A thousand-plus apps live on the Marketplace, so we rebuilt just the homepage on Vue 3 first: real APIs, real store, no blockers.
- Nuxt 3 was still a release candidate. Waiting meant keeping a Vue 2 copy of every shared component, so we took the RC instead.
Wait for stable
Duplicate shared components in Vue 2 for the website
doesn’t scalevs
Nuxt 3 RC our call
Keep one set of components, and test the RC hard
Nuxt Bridge tried, droppedRollout
No big bang
- Code first, UI after, one app at a time, so nothing broke in production along the way.
- The gallery went first. By the website’s turn we knew the road, so its two phases ran in parallel.
Phase 1
Vue3 upgrade
(gallery)
Phase 2
UI changes
(gallery & common)
Phase 3
Nuxt upgrade
(website)
Phase 4
UI changes
(website)
Design call · details
Fix it upstream
- Smooth, even under load. Page transitions on the Nuxt website, and a fuzzy search over a large catalogue: a clear loading state, results that surface without jerks, and focus that stays where you’d expect.
- Fixed where it lived. Crayons popovers had no control over when they opened and closed, so menus on the Marketplace felt glitchy. So I added delay props to Crayons, modelled on the Element popover we’d used before. Merged in December 2022, so every team on Crayons got it.
open and close delays,now just a prop
The other side
Faster loops, fewer holes
- We paid down debt on the way: a single trunk branch with tags per environment, and a Volar-based editor setup.
- The upgrade took 85 of 90 audit vulnerabilities with it, and the dev loop got a lot quicker.
| Before | After | |
|---|---|---|
| Gallery dev server start | ~8.6 s | ~0.8 s |
| Gallery build | ~36.7 s | ~9.5 s |
| Website dev server start | ~40.4 s | ~5 s |
| Packages audited | 2445 | 1645 |
| Vulnerabilities | 90 | 5 |
Takeaways
What I’d carry into the next one
-
Weigh every item
List the upgrades, then ask what each one buys you. Some can wait a release.
-
Prove it on one page
A POC on a real page, with real APIs and store, answers what a doc can’t.
-
Ship in stages
Code first, UI after, one app at a time. No big bang for users or the pipeline.
-
Write it down
Document and audit each stage, so the next person starts where you stopped.
Deep dive · ~10 min read
How we migrated Freshworks’ critical monorepo tackling tech debts
The analysis, the POC outcomes, every stage, and the full numbers.
Read the full story