Migrating a CRA monorepo to Vite

Migrating a CRA monorepo to Vite

Hello developers! Look at your monorepo. Now back to ours. Now back at yours. Now back to ours. Sadly, your monorepo is a lot slower than ours, but if you stopped using CRA / Webpack, it could be as fast as ours!

Now, that sounds a lot like the hit Old Spice commercial, doesn’t it? You’re 100% right, it does. Jokes aside, the first paragraph is kind of how we felt when we started using Vite instead of Webpack.

What is Vite?

If you’re like me, that means you don’t usually get too excited when you see a new JS compiler, framework or a library. Probably because there are so many of them that it’s impossible to keep track of the newest one. If you don’t believe me, try your luck at https://npmdrinkinggame.party. If you manage to enter a name that doesn’t exist in the NPM registry, I’ll buy you a beer. 🍻

Vite is a build tool that simplifies the build process and frontend development in general. I was pleasantly surprised when I started learning more about it. It was quite easy to get started with and learn about key concepts, configurations and levels of customisation it provides. It’s well maintained and there are a lot of community plugins available. It has hella stars on GitHub (the most important thing, obviously) and even has support for SSR. Also, the documentation is pretty good. Not perfect, but pretty good.

Do you even monorepo, bro?

This is when things get a bit tricky, though. I’ll try and simplify the project structure we’ve had with CRA, which was pretty easy to set up a year or two ago. So even though it’s not technically a monorepo, we’ve set up an app with multiple entry points.

Suppose we have 3 apps called blog, portfolio and webshop. Each of those apps is a separate folder with its own App.tsx file. The current folder structure looks something like this:

Folder structure

The next step was to figure out how to individually start one of the apps without importing all of the entry points in index.tsx . We achieved this by using environment variables which requires small changes to the package.json file.


By setting the REACT_APP_BUILD_TARGET environment variable, we are now able to make use of the dynamic import syntax. Since the import function returns a promise we’re going to use the .then() method to access the result. It would have been great to use the async/await syntax instead, but unfortunately top-level await is still unsupported.


This approach worked and we’ve been using it for almost 2 years now. This is where Vite comes in. Although it should have been fairly easy to replace CRA with Vite, and for the most part it was, there were a couple of hiccups with dynamic imports that we weren’t able to solve in the same way as we did with CRA. Using the import nor the import.meta.glob functions did not work as expected. The 2 most common problems were that Vite couldn’t parse the template strings (when using the import function) and that the whole codebase was imported regardless of whether it was actually used or not (when using the import.meta.glob function). That was a bit weird, to say the least, but we kept trying different approaches and unfortunately none of them worked.

Possible solutions

Luckily, we had some ideas that should work without dynamically importing modules. The first one was to create a small Vite plugin that would dynamically generate the index.tsx file in which the import path for the root App component would depend on the current VITE_BUILD_TARGET (former REACT_APP_BUILD_TARGET -> check out Vite’s env setup for better understanding).


This worked great until we tried to start multiple apps at once. That would cause an issue because each time a new app was started, the index.tsx file would be updated with the new import path for the App component, which would always point to the app that was most recently started. That was obviously not what we wanted to achieve so we had to give the second idea a try.

This idea relies mostly on the index.html file. Let me explain. So, remember how you could interpolate environment variables in index.html with CRA and their little hidden Webpack magic? Turns out that’s pretty simple to do with Vite as well. There are a couple of community plugins available for this but we decided to build our own since it was really simple:


All that was needed was to find all occurrences between 2 % signs and replace them with the values from the environment config. This provided us with a “dynamic” index.html file. Since the index.html file has to contain a script tag that loads the index.tsx file, we realised we could use that to our advantage:

The /src/%VITE_BUILD_TARGET%/App.tsx expression might look a bit weird but it enables us to start multiple apps at once.
For example, when starting the blog app the src attribute would be transformed to /src/blog/App.tsx.

Vite or bust?

All in all, we really like Vite. Mainly because it made the development and CI/CD processes a lot faster and easier to manage. Whether you’re debating to migrate your CRA project(s) to Vite, start a new project with Vite or something else, Vite has probably got you covered. 10/10 would recommend.

I realise this looks like a sponsored blog post but it really isn’t. That’s exactly what someone who’s writing a sponsored blog post would say, right?

Filip Polić

Filip Polić

Software Engineer

Explore more

We shape our core capabilities around lean product teams capable of delivering immense value to organisations worldwide

Got a project?
Let's have a chat!

Zagreb Office

Radnička cesta 39

Split Office

Put Orišca 11, 2nd floor

Contact info

Split+Zagreb, Croatia
+385 91 395 9711info@profico.hr

This website uses cookies in order to provide a better user experience and functionality. By continuing to browse the site you agree to our  Cookie and Privacy Policy .