DEV Community

Cover image for No pipeline, no oil spills
Peter
Peter

Posted on • Originally published at smeldr.dev

No pipeline, no oil spills

At some point in the last decade, shipping a web application started requiring a
build step. Then a second one. Then a config file for the bundler, a config file for
the CSS processor, a config file for the linter that runs before the bundler, and a
CI definition that orchestrates all of them in the right order.

Every one of those steps is a dependency. Every dependency has a version. Every version
will, eventually, break something.

When I started building Smeldr, I made a deliberate choice: no build pipeline. Not
"we will add it later." Not "we use a lightweight one." None. The build tool is
go build. The output is a single binary. That is the whole pipeline.


KISS, taken seriously

Keep It Simple is easy to say. It is harder to actually do when the ecosystem keeps
offering you tools that each solve a real problem. Webpack solves module bundling.
PostCSS solves CSS compatibility. PurgeCSS solves stylesheet size. Each is reasonable
on its own. Together they form a chain where a breaking change in one link stops
deployment.

Simplicity is not about being clever. It is about counting the things that can go
wrong and removing as many as you can. A pipeline with five steps has five places to
fail. A pipeline with zero steps has zero.

The question I kept asking was: does this tool solve a problem I actually have, or a
problem the tool introduced by existing?


No vendor lock-in

Build pipelines accumulate platform-specific config. A Vercel deployment looks
different from a Fly.io deployment. A Netlify function is not a plain HTTP handler.
Before long, the pipeline is not just a build tool, it is an implicit contract with
a specific platform.

A Go binary does not care where it runs. Linux on a VPS, a container on any
orchestrator, a bare metal server in a data center. No platform-specific config.
No adapter layer. The deployment target is: somewhere that can run a process.

That is a decision you can change later without rewriting anything.


Deployment risk

The riskiest moment in any software project is deployment. Something that worked
locally is moving into production. The fewer differences between those two environments,
the lower the risk.

A build pipeline is a set of differences. The bundler version on your laptop versus
CI versus the version from six months ago when the last deployment happened. The
environment variables that PostCSS needs to find its config. The Node version that
the build tooling requires, quietly different from the one in production.

With go build, the binary that runs on your laptop is the binary that runs in
production. There is no transformation step that can introduce a discrepancy. You
test the artifact, you ship the artifact.


A deferred cost

A build pipeline is often justified as a time saver. Configure the bundler once,
and development goes faster.

That framing is mostly correct. The bundler does save time on the specific problems
it was designed to solve. The time it costs shows up later, distributed across every
upgrade cycle, every dependency audit, every CI failure that turns out to be a version
mismatch between the bundler and a plugin.

You are not eliminating the cost. You are deferring it, and adding interest.

The pipeline was supposed to give you time back. For some software it does. For a
lot of software it turns out to borrow that time from your future self instead.


What it costs

There are real trade-offs. No build pipeline means no tree-shaking for JavaScript, no
CSS minification by default, no hot module replacement during development.

The boundary is not "backend versus frontend." smeldr.dev has frontend JavaScript and
still ships without a bundler. The boundary is further out than people assume: it is
the point where you are building a large interactive single-page application whose
JavaScript genuinely benefits from tree-shaking, code splitting, and hot module
replacement. Static assets served over standard HTTP, and CSS served as-is, do not
need any of that.

The honest version: if you are building a JavaScript-heavy single-page application,
you need a bundler and you should have one. The build pipeline exists because it solves
real problems for a specific class of software.

The question is whether your software is in that class, or whether you acquired a
pipeline because that is what software acquires.


go build is the pipeline

Smeldr compiles with go build. The binary embeds templates and static assets via
embed.FS. There is no separate asset pipeline, no manifest, no fingerprinting step.
The binary is self-contained.

This is not a backend-only claim. The project's site, smeldr.dev, runs on Smeldr. It
has interactive frontend features and no build pipeline. Two categories of JavaScript,
neither of which requires a bundler.

The first is a third-party 3D animation: 110k particles, WebGL2, a file too large
and complex to write inline. That one is vendored and served as a static file with
<script src defer>. One HTTP request, no transformation.

The second is everything else: navigation toggle, copy buttons, devlog tag filtering,
a mobile drawer, dark mode with localStorage persistence. These live as plain
JavaScript inside Go templates. They embed into the binary with the same embed.FS
that embeds the HTML. No separate asset pipeline, no manifest. The compiler includes
them the same way it includes everything else.

Two different categories of frontend code. The same answer for both.

Deployment is: copy binary, restart process. Or build a container image in one
Dockerfile stage. Or use fly deploy. The platform does not matter because the binary
makes no assumptions about it.

When a dependency updates, go get and go build tell you immediately if something
broke. There is no intermediate tool to negotiate with. The compiler is the build
system, and it has been maintained by Google since 2009.


The shoulder tension disappears when you do not need most of the pipeline. Not
because pipelines are bad engineering, but because the best tool for the job is often
the one you do not have to configure, maintain, and debug at 2am.


Smeldr is open source, written in Go, no build pipeline required.

Top comments (0)