Monorepos are hot these days and they can significantly improve the development workflow and productivity among teams. In this article, we will talk about how to set up a monorepo with Vite, Typescript, and pnpm workspaces. What is a monorepo? Before we jump into the setup, we first need to understand what is a monorepo and why should we care about it. A monorepo is a repository that contains many distinct projects which are somewhat dependent on each other but are logically independent - meaning different teams can work on them in isolation. Let's say we’re building an application and we have two projects inside of it: : This will have all our shared components that can be used by different projects. Shared : This is going to be our main project which will be the consumer of our package. Main Shared One way to work with this type of project is a multi-repo approach where we create two separate repositories and host them at the npm registry. Then, the consumer app can install it from the npm registry and use it how we import and use , , and other packages. React Tailwind Material-UI The problem with this type of workflow is every time we will make any new changes to the package, we will have to publish the app again to the npm registry for the consumer app to get the latest changes and use them. shared Monorepos can significantly improve this workflow by packaging both of these projects into a single repository and importing components directly from the workspace instead of importing them from the cloud. Setup with pnpm workspaces Now that we have an idea about what monorepos are and what problems they solve, let's dive right into the setup and see how we can set up a basic monorepo with . pnpm workspaces To follow along you must have pnpm and node installed on your machine. Scaffold a new project The first thing that we need to do for our setup is, create a new project which we will call "pnpm-monorepo" you can of course name it whatever you want to. mkdir pnpm-monorepo Then let's go inside the root of our project and initialize a file. package.json # pnpm monorepo pnpm init At this point, you should have a basic at the root of your project with somewhat similar code as follows: package.json // pnpm-monorepo/package.json { "name": "pnpm-monorepo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" } Now, let's install some of our root-level dependencies that we are going to use across the project. # pnpm-monorepo pnpm add -D typescript typescript-node Once the common dependencies are installed let's create our base Typescript configuration with the following code: // pnpm-monorepo/tsconfig.base.json { "compilerOptions": { "strict": true, "strictNullChecks": true, "esModuleInterop": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "noUnusedLocals": true, "skipLibCheck": true, "sourceMap": true, "jsx": "react-jsx", "moduleResolution": "node" } } This is the default Typescript configuration that I have written for my project you can customize it based on your project's requirements. Next, we will create our file which will extend our tsconfig.json tsconfig.base.json // pnpm-monorepo/tsconfig.json { "extends": "./tsconfig.base.json" } That's all we need for our Typescript configuration. Now let's create a file at the root of our project that will tell what packages we will have inside our monorepo. pnpm-workspace.yaml pnpm pnpm-workspace makes it surprisingly easier to add and manage multiple packages in a project. # pnpm-monrepo/pnpm-workspace.yaml packages: - "./packages/**" We can add as many packages as we want to by adding the relative path of the packages in this file, for our project we are adding our directory and all of its child directories to the workspace. pnpm-workspace.yaml packages Now we will create both of our and packages that we discussed above inside the directory. Our project will be a application and the project will have some shared code that we will use inside our main app. main shared packages main Vite shared Create a Vite application Let's first create the packages directory at the root level. # pnpm-monorepo mkdir packages Now we will create our app inside of the packages directory which will be a application. To do that navigate inside and run: main Vite pnpm-monorepo/packages # pnpm-monorepo/packages pnpm create vite I am using React & Typescript but the following steps should work irrespective of which framework/library you select. Now let's create our second package in the packages directory, we will call it shared. # pnpm-monorepo/packages mkdir shared At this point, your project's structure should look somewhat similar to this: We have our packages directory and inside it, we have our and packages. main shared Let's now go inside our package and change the following in main package.json // pnpm-monorepo/packages/main/package.json { "name": "@pnpm-monorepo/main" ... } For to properly identify that this package is part of our workspace, we need to change the name property of the package to . In this case, our root name is . pnpm @root-name/package-name pnpm-monorepo Now let's go inside our package and do the same but first, we need to create a file inside of it. shared package.json # pnpm-monorepo/packages/shared pnpm init This will create a file inside our package, now let's change the following: package.json shared // pnpm-monorepo/packages/second/package.json { "name": "@pnpm-monorepo/shared" } , now we should be able to import packages from the workspace similar to how we do for other npm packages. That's it for the configuration part Let's create a file inside our package with the following code: index.ts shared // pnpm-monorepo/packages/shared/index.ts export const sayHi = (userName: string) => console.log(`Hi ${userName}`) We have a basic function which takes a as parameter and console log a message saying "Hi" to them. sayHi userName Now, let's go inside our package and import our package by running this command: main shared # pnpm-monorepo/packages/main pnpm add @pnpm-monorepo/shared According to docs, if we do within a workspace, it first tries to find that package within the workspace, and only if it is unable to find the package within the workspace it goes to the npm registry to install it. pnpm pnpm add As we already have within our workspace, it will install it from the workspace itself. @pnpm-monorepo/shared At this point your inside you app should look something like this: package.json main // pnpm-monorepo/packages/main/package.json { ... "dependencies": { "@pnpm-monorepo/shared": "workspace:^1.0.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, ... } That's it, now we should be able to import variables/functions from our package. shared Let's go inside our and add the following code: main/App.tsx // pnpm-monorepo/packages/main/App.tsx import { sayHi } from "@pnpm-monorepo/shared"; const App = () => { sayHi("Vedansh"); return <h1>Main app</h1>; }; export default App; Run and inside app, a new dev server should spin up and you should see the following output at your : pnpm install pnpm dev main http://localhost:5173 So that's it, this was our very basic monorepo setup with pnpm-workspaces, Vite, and Typescript. Monorepos is a great solution for large-scale applications where you don't want to manage multiple repositories for multiple packages and instead you want to package all of them together into one repository. If you want the complete code of this project you can find it . here Thank you for reading, if you are still here consider following me for more blogs. Also published . here