The most surprising thing about integrating esbuild with Nx and Turborepo is how little is actually different once it’s set up; the magic is in the speed, not a fundamental shift in your workflow.
Let’s see what this looks like in a real project. Imagine you have an Nx workspace with a couple of apps and libraries. We’ll add a new Next.js app and configure it to use esbuild for its build process.
First, ensure you have the necessary Nx plugins installed:
npm install --save-dev @nx/next esbuild
# or
yarn add --dev @nx/next esbuild
# or
pnpm add --save-dev @nx/next esbuild
Now, let’s generate a new Next.js application within your Nx workspace:
npx nx g @nx/next:app my-esbuild-app
After generation, navigate into your new app’s directory (apps/my-esbuild-app) and find the next.config.js file. This is where we’ll make the crucial change. By default, Next.js uses Webpack. To switch to esbuild, you’ll modify the experimental configuration:
// apps/my-esbuild-app/next.config.js
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
experimental: {
// This is the key!
// Setting this to true enables esbuild for Next.js builds.
// It's still experimental, hence the name.
swcMinify: true, // Next.js uses SWC for minification, which esbuild leverages.
// For full esbuild integration in Next.js, especially for transpilation,
// you might need to explore custom webpack configurations that point to esbuild.
// However, for most common use cases, enabling swcMinify is the primary step
// that leverages esbuild's speed for the minification part of the build.
// For a more comprehensive esbuild experience beyond just minification,
// you'd typically configure esbuild directly within a custom webpack setup,
// or use a tool that abstracts this, like Turbopack (though that's a different path).
// For Nx, the `@nx/next` plugin often handles these optimizations under the hood
// when you enable certain experimental features or use specific build targets.
},
// other Next.js configurations...
};
module.exports = nextConfig;
The swcMinify: true option in next.config.js tells Next.js to use SWC (Speedy Web Compiler) for minification. SWC is written in Rust and is incredibly fast, often outperforming traditional JavaScript-based minifiers like Terser. Crucially, esbuild itself is a bundler and minifier written in Go, and SWC’s capabilities are often what people are looking for when they want "esbuild speed" in Next.js. While Next.js doesn’t directly swap its entire build pipeline for esbuild with a single flag, enabling SWC minification is the most direct and supported way to leverage esbuild-like performance for the minification step.
Now, let’s integrate this with Nx and Turborepo. Nx’s build system is designed to cache results based on inputs. When you run a build, Nx analyzes your package.json, your project’s code, and your configuration files. If none of these have changed since the last build, Nx will serve the cached output, saving you significant time.
To build your application with Nx, you’d typically use:
npx nx build my-esbuild-app
If you’re using Turborepo, it acts as a caching layer on top of Nx. When you run npx turbo run build, Turborepo will execute the build command defined in your project.json for my-esbuild-app. If the build artifacts are already in your Turborepo cache (local or remote), Turborepo will restore them, again saving you from re-running the build.
The core mental model here is that esbuild (or SWC in Next.js’s case) provides the speed for individual build steps, while Nx and Turborepo provide the intelligence to avoid re-running those fast steps unnecessarily. They work in concert: esbuild/SWC makes the build fast, and Nx/Turborepo makes it not happen when it doesn’t need to.
The power of this combination lies in its transparency. You don’t typically need to write custom esbuild configurations or complex webpack overrides for common Next.js setups within Nx. The @nx/next plugin is smart enough to leverage these faster tools when available and configured. Your next.config.js remains largely the same, but the underlying execution is significantly accelerated.
One aspect that often surprises developers is how esbuild’s speed is achieved. It’s not just a matter of better algorithms; it’s a combination of factors. Firstly, it’s written in Go, a compiled language known for performance, which allows it to leverage multi-core processors much more effectively than JavaScript-based tools. Secondly, its internal architecture is designed for parallelization and efficient memory management. Unlike bundlers that might process modules sequentially or have significant JavaScript overhead, esbuild aims to do as much work as possible in parallel and minimize its own runtime. This means tasks like parsing, transforming (e.g., Babel/TypeScript to JavaScript), and minifying are executed with remarkable velocity, often on the order of milliseconds for moderately sized modules.
The next step you’ll encounter is optimizing your development server startup time, which might still be a bottleneck even with fast builds.