The Linux kernel - over 40 million lines of code, one of the most sophisticated software projects on Earth - still builds with Make. Modern JavaScript build tools promise faster development, smarter bundling, and better developer experience. Make was written in 1976. I still reach for Make first.
Consider Make before reaching for modern build tools. For many projects, Make is simpler, faster, and more portable. Complexity needs justification.
This might sound like nostalgia talking, but it's not. I've watched build systems come and go for decades. Grunt gave way to Gulp gave way to Webpack gave way to Vite. Meanwhile, the Linux kernel, Git, and countless production systems still build with Make. There's a reason.
Build tools are one of those areas where the industry keeps reinventing the wheel, usually making it more complex in the process. It's the layer tax applied to development itself - each new tool adds abstraction, configuration, and dependencies between you and the actual build.
Updated January 2026: Added 20-Year Build Test and Monday Morning Checklist.
The 20-Year Build Test (The Physics of Entropy)
Every build dependency you add is a bet that someone will maintain it for the lifetime of your project. Most bets lose.
Here is the test: Turn off the internet. Set your system clock to 2046. Run your build.
- Make: Works. The syntax hasn't changed since 1976.
- Webpack: Fails. npm install reaches for a registry that may not exist.
- Vite: Fails. Same reason, plus transitive dependencies three layers deep.
- Your custom pipeline: Fails. The CI service you depend on pivoted to AI in 2031.
I have run this test mentally on every build system I have used. The ones that pass have no runtime dependencies. The ones that fail are betting their build on someone else's maintenance schedule. The Linux kernel does not bet. Neither should your infrastructure.
The Webpack Configuration Nightmare
If you've inherited a Webpack configuration, you know the feeling. Hundreds of lines spanning loaders, plugins, environment-specific overrides, and cryptic incantations that nobody remembers writing. The documentation tells you it's "extensible." What it means is "you'll spend hours debugging why your build broke."
The industry has a name for this: configuration hell. Webpack configurations can grow to rival the complexity of the applications they build. Teams assign engineers to maintain build configs. That's not building software - that's maintaining the tools that build software.
Vite improved things. Faster dev server startup, simpler defaults, better Hot Module Replacement. But it's still a complex tool with its own plugin ecosystem, its own configuration syntax, its own breaking changes between versions.
The pattern repeats. Every few years, a new build tool promises to solve the problems of the previous one. Developers migrate. New complexities emerge. The cycle continues.
What Make Gets Right
Make has one core idea: if the source files haven't changed, don't rebuild. That's it. Dependencies and timestamps.
A basic Makefile is readable:
output: input1 input2
command to build output from inputsYou list what you want to build, what it depends on, and how to build it. Make handles the rest. If input1 changed but input2 didn't, Make knows. If nothing changed, it skips the build entirely.
Incremental builds from 1976. The problem modern tools spend thousands of lines of JavaScript solving, Make solved with filesystem timestamps fifty years ago.
Yes, Make has quirks. Tab characters matter. Variables have multiple syntaxes. The documentation reads like it was written for people who already know Make (because it was). But these quirks are documented, stable, and the same across every Unix system since the Carter administration.
Boring Is a Feature
Make is boring. It hasn't changed significantly in decades. The Makefile syntax you learn today is the same syntax that built software in 1990.
This is usually presented as a limitation. "Make is old." "Make doesn't understand JavaScript modules." "Make doesn't have built-in Hot Module Replacement."
But boring means predictable. Boring means stable. Boring means the same commands work on your laptop, your CI server, and production machines without installing Node.js 18.7.2 specifically because 18.8 broke something.
The Linux kernel - 30 million lines of code, one of the most sophisticated software projects on Earth - builds with Make through the Kbuild system. Not because kernel developers are stuck in the past, but because Make does exactly what they need without getting in the way. When someone asks "shouldn't the kernel use CMake or Meson instead?", the answer is always the same: why would they introduce complexity when Make works?
The Dependency Problem
Modern build tools carry dependencies. Node.js version requirements. npm packages that may or may not exist next year. Plugin ecosystems where a single unmaintained package can break your entire build chain.
I've seen builds fail because a dependency of a dependency of a Webpack plugin was removed from npm. I've watched teams spend days debugging why their CI builds broke after a minor Node.js update. This isn't theoretical - it's Tuesday.
Make has no runtime dependencies. It's part of the POSIX standard. It exists on every Linux and macOS machine. The same Makefile that runs today will run in 2036 without modification.
That's not true for your Vite configuration. Or your Webpack setup. Or whatever comes after them.
Make as a Contract with the OS
Modern build tools are "opinionated," which is a polite way of saying they're brittle. In a CI/CD pipeline, brittleness is a hidden cost.
When your Vite config breaks because of a minor version bump in a transitive dependency, your entire delivery pipeline freezes. Engineers scramble to understand why the build that worked yesterday fails today. The answer is usually buried in a changelog for a package three levels deep in your dependency tree.
A Makefile is a contract with the operating system. It doesn't care about your node_modules. It cares about file timestamps and exit codes. These are the most stable interfaces in computing—they haven't changed since the 1970s and won't change in the 2030s.
By using Make as the entry point for your CI/CD, you insulate your automation from framework churn. The Makefile becomes living documentation: make build, make test, make deploy. New engineers don't need to learn your build system—they need to type make help. The complexity lives behind stable targets that work the same way on every project, every machine, every year.
Try running npm install on a two-year-old project. Now try running make on a forty-year-old Makefile. One of them still works.
When Modern Tools Make Sense
Make isn't perfect for everything. JavaScript's module system, tree shaking, code splitting, JSX transformation - these are real problems that specialized tools solve well.
If you're building a complex React application with thousands of components, dynamic imports, and server-side rendering, a modern bundler earns its complexity. Webpack and Vite exist because web development has genuinely novel requirements.
But most projects aren't complex React applications. Most projects are:
- Backend services that don't need bundling at all
- Static sites where copying files and maybe running Sass is enough
- Tools and scripts that just need to compile
- Libraries where the build is "run TypeScript compiler"
For these projects, a Makefile is often simpler, faster, and more maintainable than a modern build system.
The Maintenance Burden Nobody Talks About
Build tools require maintenance. Webpack configurations drift as plugins are updated or deprecated. Vite releases new versions with subtle breaking changes. The JavaScript ecosystem moves fast, and your build configuration has to keep up.
This is technical debt that compounds. Every time you skip a dependency update, the eventual migration gets harder. Teams end up locked to ancient Node.js versions because the build breaks on anything newer.
Make configurations don't drift. A Makefile written ten years ago works today with no changes. The shell commands it invokes might need updates, but the build logic itself is stable.
This isn't a small thing. Maintenance costs compound. The time teams spend keeping build tools working is time not spent building features.
What I Actually Use
For my own projects, I use a tiered approach:
Make for orchestration. Even when I use modern tools, Make coordinates them. make build runs whatever tools are needed. make test runs tests. make deploy deploys. The commands are consistent across every project, whether it's Python, Go, TypeScript, or C.
Specialized tools where they earn their complexity. Vite for complex frontend applications. esbuild for fast TypeScript compilation. But always wrapped in a Makefile so the interface is consistent.
Plain shell scripts for simple cases. Sometimes copying files and running a compiler is all you need. A ten-line shell script called by Make beats a hundred-line configuration file.
The result is that make build works on every project I maintain. New developers don't need to learn each project's build system. CI configurations are simple. Dependencies are minimal.
Build Tool Decision Matrix
Check which factors apply to your project:
The Philosophy Difference
Modern build tools optimize for developer experience during development. Faster reloads. Smarter caching. Prettier error messages. These are real benefits.
Comparative analysis of build systems shows Make optimizes for simplicity and longevity. Readable configurations. No dependencies. Stable behavior across decades.
The question is which you value more. For projects that will outlive their original developers - and most projects should aim to - longevity wins. For rapid prototyping where the build system might get replaced anyway, developer experience wins.
Too often, I've seen teams choose developer experience for projects that needed longevity. The result is build systems that nobody understands three years later. What felt productive in 2023 becomes a maintenance burden by 2026.
Boring tools are tools that still work. Make is boring. Make still works.
The Bottom Line
Make isn't the right tool for every project. Complex frontend applications with code splitting and tree shaking genuinely benefit from modern bundlers.
But Make is the right default. Start with Make. Add complexity only when you've proven you need it. The Webpack configuration you can avoid writing is the Webpack configuration you don't have to maintain.
The Linux kernel builds with Make. Git builds with Make. PostgreSQL builds with Make. These are some of the most successful, longest-lived software projects in history. Maybe they know something about build tools that the JavaScript ecosystem keeps forgetting.
Fifty years from now, someone will still be running make. I'm less confident about Vite.
"Fifty years from now, someone will still be running make. I'm less confident about Vite."
Sources
- Linux Journal: Kbuild - The Linux Kernel Build System — Documentation of how the Linux kernel's Make-based build system handles millions of lines of code
- LogRocket: Vite vs Webpack for React Apps in 2025 — Analysis of modern build tool complexity and configuration overhead
- Medium: Modern Build Systems Comparative Analysis — Comparison of GNU Make, CMake, Ninja, and Meson including simplicity metrics
Build System Audit
Is your build system adding value or just complexity? Get perspective from someone who's seen build tools come and go.
Get Assessment