Ship Your UI Faster With Turborepo

Rate this content

Iteration velocity is the key to unlocking the potential of your technical teams and business. In this talk, we'll build the groundwork for a highly scalable monorepo using Turborepo and talk about the philosophies behind architecting codebases for teams of all scales. Through the power of Remote Caching, we'll never do the same work twice and we'll deliver your applications at the speed of Turbo.

21 min
14 Nov, 2023


Sign in or register to post your comment.

Video Summary and Transcription

The Turboverse focuses on making the development process faster and more efficient. TurboPak is an incremental bundler with function-level caching, and TurboRepo is a high-performance build system with features like incremental building, remote caching, and parallel execution. TurboRepo can optimize task runners, set up monorepos, and speed up development time. vclink-repo enables seamless integration with the Vercel remote cache, and Conformance and Codoners provide static analysis and automated code reviews. TurboPak and TurboRepo offer faster CI processes and exciting advancements in web bundling.

Available in Español

1. Introduction to Turboverse

Short description:

You're writing, building, and shipping more code than ever. But your CI times have been increasing, and your local developer experience is getting worse. Welcome to the Turboverse, where we focus on making your development process faster and more efficient, from start to finish.

You're writing, building, and shipping more code than ever. It's great to be building a product that your customers love, but your CI times have slowly been creeping upwards. Your local developer experience keeps getting a little worse, and your CI complexity continues to increase. The time it takes to get from idea to ship has slowly gone upwards. That's because you have to sit and wait for things. A lot like these ships. But what if that didn't have to be the case? Hello and welcome to the Turboverse. I'm Anthony. I'm TurboDX. I like to joke that I'm a full stack comedian, but real job TurboDX. These problems I described are things that I get to think a lot about in my role. How can we make sure you never do the same work twice? When you do need to do new work, how do we just make sure it's plain fast and that it's as little work as possible? How do we make sure that all the way from solo developer to the largest enterprise applications in the world, that you have a great experience all the way from start to finish?

2. Exploring TurboPak and TurboRepo

Short description:

Let's explore TurboPak, an incremental bundler optimized for JavaScript and TypeScript. It features function-level caching for faster bundle creation. TurboPack is currently being tested with the Next.js development server and has achieved a 93.9% passing rate for Next.js tests. TurboRepo is a high-performance build system for JavaScript and TypeScript codebases, leveraging learnings from organizations like Meta and Google. It offers features such as incremental building, remote caching, and parallel execution to make your builds faster. Our Vercel team has saved over 8,500 hours in the past two weeks using TurboRepo.

Let's explore TurboPak and talk a little bit about the underlying architecture. And then we'll jump into a demo and use both of these tools to ship our first set of applications with a fully distributed caching layer. We'll also talk about how this repo works mechanically, the benefits of monorepos, and things to think about at a higher level when you're architecting a monorepo.

So first, let's jump into TurboPak. TurboPak is an incremental bundler optimized for JavaScript and TypeScript. We're writing in Rust for bare metal speeds led by the creator of Webpack, Tobias Koppers. On top of that raw speed, we're also making it smarter. If I jump over here to the core concepts, you'll see some information about the TurboEngine. With the TurboEngine, we're getting down to doing as little work as possible, like we were talking about before. The TurboEngine features function-level caching so that we can do as little work as possible to create the bundles for your application. Previously in Webpack, we could only do this at the module level, at the file level. But with function-level caching, we can understand your module graphs much deeper. Because we can analyze the relationships throughout your code more deeply, we can just be faster by going straight to the things that we need to bundle, look at, and analyze, instead of creating a much larger graph. Additionally, Webpack is designed such that it creates an unoptimized graph first, and then optimizes that graph. In TurboPack, we're just making that optimized graph the first time, as you can imagine, a bit faster. At the time of this recording, we're using the Next.js development server as the proving ground for TurboPack. You can visit to see that no, we're not Turbo Yet completely. These are all the tests that are in the Webpack bundler. And additionally, we've stacked a few on top now that we've got better coverage for TurboPack to find out if the Next.js development server TurboPack is ready to go and be shipped out of beta and to stable. You can see that we're currently sitting at 93.9% of those Next.js tests passing. But once we're done there, we'll actually have a really nice head start on getting TurboPack to build for production those Next.js applications. You can try this out today in your Next.js 14 project using //Turbo on the dev script of your Next.js application. We look forward to hearing your experience, and we would appreciate if you run into any issues to report those bugs on the Next.js repo on GitHub.

But your bundler is only one part of the story. Now let's talk about TurboRepo and do a little bit of a demoing and talk about some mechanics. TurboRepo is a high-performance build system for JavaScript and TypeScript codebases. We're taking some of the learnings from the largest monorepoing organizations in the world, like Meta and Google, and making those techniques friendly, usable, and productive for everyone. Through features like incremental building, remote caching, parallel execution, and much more, we can make your builds, lints, tests, and any other tasks that you need done as fast as possible. As a proof point, here's a quick screen grab that I grabbed off of for our Vercel team to show how much time we've saved over the past two weeks. Looks like we've saved almost more than 8,500 hours.

3. Optimizing with TurboRepo

Short description:

TurboRepo can save you a lot of time by optimizing your task runners and performing parallel builds and tasks. By using TurboRepo, you can easily set up a monorepo and leverage function level caching to avoid unnecessary work. TurboRepo also speeds up development time through simultaneous running of development scripts and hot reloading. And when it comes to shipping, TurboRepo can help build a fully distributed caching system across CI machines and development environments, saving time and improving efficiency.

Looks like we've saved almost more than 8,500 hours. And even better, all this saved time is time that your task runners aren't running. So whether those be GitHub Actions, Jenkins, GitLab CI CD, wherever they are, those are machines that don't have to do as much work.

So how can you get set up to do this too? An easy way is with npx create turbo at latest. I'll go ahead and run this, and I'll be prompted a few times. I already ran this in the background for the sake of the demo, but this will go ahead and grab a few files from GitHub, and you'll be set up and ready to go with your own monorepo.

A quick pro tip, install turbo globally. If I do turbo dash dash help here, you'll see that we get our help text. This makes it really easy to work in a monorepo, and now that I have that available, I can just type turbo build, and we'll start building the two applications in this monorepo in parallel. We'll see that we handled both of those builds, no problem, in parallel 14 seconds later.

I can also start throwing more tasks into the mix. I can throw a lint in next to those builds, and I will be doing lints across this repo in parallel as well. But you'll notice something interesting here. Two of these tasks were cache, and among the five that we ran, and even cooler, I can write turbo lint build, and we'll get a full turbo. This is all work that we've done before, we haven't changed any source code, we haven't changed anything about this repo, so why do that work again?

TurboRepo can also help you at develop time, so if I do turbo dev, we'll be running our docs development script and our web development script at the same time, and this actually gives me a nice opportunity to show off one thing that we love about monorepos, that you can use one shared library at the same time. So if I go ahead and open up packages UI here, we'll go ahead and maybe I'll edit this button such that I'll put a test text in front here. I went and grabbed us a browser to look at now, so we can see here in this web app, we have this test in front of the actual text, and then we can also jump to 3001, where our docs application is running, and we'll see docs here and test click me. I'll go ahead and remove that, get us back to where we were, and you can see that that hot reloaded in that docs application. I'll go ahead and grab 3000 again, and you can see test has gone as well here too. My changes just propagate one commit, and I'm done. We're a lot faster at develop time now, but what about the part that really matters? Shipping.

In just a few minutes, I can have TurboRepo help me at ship time too by building a fully distributed caching system that works across all my CI machines, as well as my development machine here too. I'm going to demonstrate this using GitHub and Vercel. There's a lot of other iterations that you can use to wire up all of these parts, but this is the quickest way for me to do this in the scope of a demo, and I can have this caching system done in just a few minutes. First things first, I need a repo to push my work to, so I'll go ahead and use gh-repo-create. I'll push a local repo here, and you'll see that this happens pretty quickly. I didn't even have to leave my terminal. Pretty convenient. And we'll eventually use this to push our work to Vercel. Vercel will use the Git integration and just understand when I push changes.

4. Linking with vclink-repo

Short description:

I'll use vclink-repo to link my Git repository to a project on Vercel, enabling seamless integration with the Vercel remote cache. I can also leverage GitHub Actions to check and run lint when pushing code to a PR, utilizing the remote cache. By uncommenting a few lines and obtaining the TurboToken secret, I can proceed with shipping. Opening a pull request provides an overview of the builds, including the CI task, which completes quickly due to the full turbo hit.

The next thing I'll do is use vclink-repo. And what this will do is now I'll link my Git repository to a project on Vercel. So I'll go ahead and pick me. I need to make both these projects, so we'll do that here. And now that we've created both those projects, they're linked to my repo that I have here locally.

Now, it's pretty seamless, but since this is a TurboRepo, I'm already connected on Vercel to my repo here through the Vercel remote cache. So if I go ahead and run turbo-build and hit full turbo, and if I do turbo-lint, hit full turbo again, now Vercel knows about these caches.

Another thing that I can demo here pretty quickly is that I can have GitHub Actions check and run this lint when I push code to a PR, and it'll see the remote cache as well. I went and grabbed the example that we have in the TurboRepo documentation. You can see that I dropped it into a GitHub workflow here. I think I might need to make a few changes here. We're not going to run a build. Instead, we're going to do lint, and I'm going to run the lint script from our package JSON. And other than that, we should be good to go.

The one big thing here that I mentioned was this GitHub action can use that Vercel remote cache like we were talking about. So if I uncomment these lines and I'm done, I do need to go grab the secret for my TurboToken from the Vercel dashboard. I've done that now, I have Turbo Team here and I have my secret, the TurboToken, and now it's time for me to go ahead and do some shipping. So I'll add this to a commit here. I think I'm going to need to get off main, actually, but we'll go ahead and do an initial commit.

I'll check out to DevOps JS. Perfect. And then I'll go ahead and make a PR. If I open this pull request, we'll actually get a nice overview that we can look through or what happened during these builds. I think the first one that we can look at is going through our CI task. This was the lint, I forgot to rename it. But if I go ahead and open up this action, we'll go ahead and see that the dependencies are going to install. And then lint's going to run. And before I'm even done figuring out, reading the log, it's done. It looks like this took all of, I don't know, 15 seconds or something, but this lint took one second apparently. It hit a full turbo because we didn't change any of that code.

5. Skipping to Production

Short description:

We ran the lints on our local machine, so there's no need to do that work again in the GitHub action. The outcome can be restored from the task successfully. The docs application also completed quickly, thanks to the full turbo. The run summary shows a time savings of 26 seconds. With everything deployed and linted, we can skip to production.

Remember we ran these lints over on our local machine, so there's really no need to do that work again, right? Just because we're in a GitHub action. So we already know the outcome of this, let's just restore it from task successfully.

Also over here on our docs application, I can go ahead and it looks like this is actually done too, before I was done explaining that log for the other task. But it looks like we built in 10 seconds because again, same thing. We have a full turbo.

And if I go ahead and check this run summary, this is a nice feature for when you have a lot more tasks that you're running. We can see, for instance, that I saved 26 seconds of time. So that's pretty convenient too. So everything's done deploying and linting now. And just like that, only a few moments later, I can go ahead and skip to production.

6. High Level Principles and Package Context

Short description:

Designing and architecting a monorepo with high level principles. Using the platform and leveraging the power of the package manager and tooling to save time. Creating a self-contained context within monorepo packages. Dependencies are installed where they are used. Few dependencies in the root of the monorepo.

You've seen that end game, it's nice to go back and think about designing and architecting a monorepo now that we know we can save time everywhere. So let's think about some high level principles.

I'll go ahead and maybe I'll make a notes here, just in a text file so that we can jot some things down. The first thing that I usually start to think about is that we want to use the platform. You're used to hearing this in the JavaScript and web world as the browser and web APIs, but there's also the JavaScript and TypeScript ecosystems that know how to use workspaces. They have expectations and conventions that we can use to our advantage. We can use the power of our package manager of node.js and of our tooling. All of those expectations, we can use them together and speed up all of these tasks. So that's where TurboRepo sits. We use everything within your repo. It's just a thin wrapper around everything to save you a bunch of time. And when we do this, you end up with a much longer term healthy repo, because again, you have everything using the conventions and using the expectations of the ecosystem. So everything stays fast, easy, and convenient from day one to day 100,000.

Another thing that I like to think about is that in a monorepo, my application packages and my library packages almost start to feel like a multi-repo context. So packages feel almost like multi-repos. So what do I mean by this? Now there's limits to this idea.

For instance, if you were really working in a multi-repo, your web application would have its own lock file. That's not going to be the case in a monorepo, right? The lock file is here at the root and your web application is down in here. But maybe I'll open packages.ui package.json here, and I can kind of demonstrate that this starts to feel like its own little world, its own almost self-contained box. So for instance, these exports, we have clearly defined what comes out of this UI package. These are the three things that are available, nothing else. If you path into this package, you're not doing the thing that you want to be doing. And another thing is that dependencies are installed where they get used. So we know that ESLint is being used in here, we know that the configurations from around our other library packages, this workspace star is telling the NPM that this is in our repo. So you start to end up with this story where the UI package is almost like its own little world, almost like its own little repo. But when it goes out to the outside world, when for instance, when it gets used in one of our applications, it's almost as if the package came from the NPM registry, when we're thinking about it from the consumer's side, right? This is installed as a repo UI package, and when we go to use it, it just feels like something that didn't come from our repo almost.

Now perhaps tagging onto the back of these two ideas is as few depths in the root as possible. Okay, so this might sound a little weird at first for some folks, but I promise this is just a better way to work when you're thinking about the long term health of your monorepo. So if I go ahead and open up this turbo repo example here, the starter, you'll notice that there really isn't much here when it comes to dependencies, you do see turbo, you do see prettier. These are things that operate outside of the context of the applications and library packages that are in your repository.

7. Managing Dependencies and Strong Defaults

Short description:

Installing dependencies where they are used allows flexibility in versioning. Tools like pmpm up-r, ManyPackage, and SyncPack help manage relationships and keep dependencies in sync. Strong defaults with escape hatches allow customization while reducing duplicated code. Vercel provides tools like Conformance and Codoners to ensure organization-wide adherence to defaults and best practices.

So it makes sense for them to be up here in the root. Then when you want to go use a dependency from the NPM registry or wherever else in your repository, you go ahead and install it where it's used. For instance, we're installing next 14 here in this web application. This is really important for the simple fact that, you know, maybe your web team and your docs team want to use the same version of next maybe they don't want to use the same version of TypeScript. There's completely valid reasons for this. You know, maybe the web team needs some feature of TypeScript from 5.3.3 and above that the docs team can't really get to because maybe they're stuck on version 4.0.

Maybe not the best thing for the docs team to be able to do, but at the same time, reality sets in and everybody needs the agility and the flexibility to have this escape patch such that everyone can keep working without having to force each other onto the same versions of things. This is important because when you're working in scale and you have tons of teams in the same monorepo, while the virtue of having one version of everything in the repository is ideal, reality sets in, right? Our applications need to make it out to users and when they do, they need to work correctly. So when we have these various different versions that we can use throughout the repo, we have a little bit more flexibility to keep shipping and come back to this TypeScript bump, for instance, when we have the ability to do so.

There are of course tradeoffs here. I won't say that that's, you know, the perfect situation and there are costs to that flexibility, but if you are someone who wants to, you know, keep TypeScript always at the latest version, tools exist for this, pmpm up-r if you're using pmpm, this "-r flag is for saying recursive and so now if I run this, I'll get TypeScript at latest across the entire repo. There's also tools like ManyPackage, if you've ran into that one, or SyncPack that can manage these relationships and keep everything in sync automatically if you want them to. Additionally, we're looking at creating features built right into TurboRepo to do this ourselves. It will probably look something like TurboInstall, so be on the lookout for that.

One last thing that I like to mention is strong defaults with escape hatches. So one example of this would probably be TypeScript, for instance, in this repository. So we have a base TypeScript configuration that is meant to be used across the repository. We're setting great defaults here and we want to encourage the use of these defaults across the application. Now, for instance, Next.js has a specific set of compiler options that it needs, maybe a React library you want to establish specifically there that you want to use React.jsx. So as you can see through this extends key that TypeScript has, we can refer to that base JSON and we'll use all of these defaults, but we can set our own and stack more and overwrite more. And now again, we can go up into applications as well and start to include and exclude things that make sense for this specific application. So in this way, we have these nice defaults. Again, you see the extends key, we're reaching down into that TypeScript configuration and using Next.js.json, but we're doing things here that are specific to this application. It's become super important at scale. Your developers know what to expect across the repo, and we have flexibility here, but we're also reducing duplicated code. So we kind of have the best of both worlds here. But even when we do follow this set of standards and principles, we've built great defaults with those necessary escape patches. How do we make sure that our entire organization uses those defaults and responsibly uses those escape patches? We've built tools at Vercel for this too. Conformance and Codoners. Conformance is a static analysis checker that automates things like performance, security, quality, and best practices within your repository.

8. Conformance, Codoners, TurboRepo, and TurboPak

Short description:

The Conformance feature in TurboRepo goes beyond ESLint as a static analysis checker by running across multiple files and providing individual reports. Codoners allows mimicking the organizational structure in the repository for automated code reviews. You can use these features without deploying on Vercel, ensuring code privacy. TurboRepo offers faster CI processes, while TurboPak provides excitement for the future of web bundlers.

This goes a few steps further than ESLint as a static analysis checker, because it runs across multiple files at the same time and reports in on those individual files. Additionally, it comes with a set of rules that we at Vercel recommend, and you can write your own too.

What you saw there in the video as well was the Codoners feature. You can mimic the structure of your organization right directly in your repository to make sure that the right folks give the right reviews at the right times on the right code. Automating that process becomes really valuable too.

I think my favorite thing about these features is that you don't even have to deploy your applications on Vercel to go ahead and use these features. We will never see your source code, and so if you have a high standard for being able to share source at your organization, then you can still take advantage of these features. I think my other favorite thing is, I didn't even have to make a slide for it because the design team made this great video. If you're feeling the pain in your CI system where things just aren't as fast and you just want it to be faster and stop doing the same work over and over again, then hopefully TurboRepo looks inspiring to you, and hopefully TurboPak has gotten you excited for the future of bundlers for the web. I'm Anthony, and I hope you have a great rest of your DevOps.jsConf.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

React Summit US 2023React Summit US 2023
8 min
Canva’s App UI Kit: Empowering Developers With Modern Web Technologies
At Canva, we help 150 million + monthly users create and design. How do we enable developers to do the same? Intro Canva's Apps SDK, which is built to empower developers to efficiently create UIs for their Canva Apps using React. Canva recently streamlined the app creation process by compiling some of the most frequently used and requested UI components by our developers in our @Canva /app-ui-kit package. I'll be sharing how we iterated upon our Apps SDK to empower developers globally to build their brand into Canva.