↑ top
← Back to blog
EN | ES

Why I Didn't Use a Framework (And What It Actually Cost Me)

No Next.js, no Astro, no Hono. Just a 1600-line JavaScript file. Here's an honest accounting of the tradeoffs.

✦ AI Summary

Cloudflare Workers is not a browser. It's a V8 isolate with a subset of the Web Platform APIs and a set of Cloudflare-specific bindings. The worker ships as a single JS file. There's nothing to install, nothing to bundle, nothing that can break.

Why I Didn't Use a Framework (And What It Actually Cost Me)

When I started building this site, I made a deliberate choice: no framework. No Next.js, no Astro, no Remix. Not even a lightweight router library like Hono or Itty Router.

One file. One entry point. Everything in src/worker.js.

That file is now 1600 lines long.

Here's what I gained, what I lost, and whether I'd make the same choice again.

Why No Framework

The primary reason: I wanted to understand the platform.

Cloudflare Workers is not Node.js. It's not a browser. It's a V8 isolate with a subset of the Web Platform APIs and a set of Cloudflare-specific bindings. If you layer a framework on top, you learn the framework's abstractions. You don't learn what the platform can do.

The secondary reason: build steps add friction.

A framework like Astro or Next.js requires a build step to compile templates, bundle assets, and generate static output. That means a build tool, a config file, a CI step, and a local environment that matches CI. For a personal site with a handful of routes, this felt like a large tax for a small benefit.

What I Got

Zero dependencies at runtime. The worker ships as a single JS file. There's nothing to install, nothing to bundle, nothing that can break because a dependency released a bad update.

Complete visibility. Every request goes through code I wrote and can read. When something breaks, I look at worker.js. There's no framework internals to trace through.

Tiny bundle. The worker script is well under Cloudflare's 1MB limit, with plenty of headroom. A framework adds dead code even when you configure tree-shaking.

Fast cold starts. Workers boot in microseconds partly because there's less code to parse. A large framework bundle adds to parse time.

What It Actually Cost

1600 lines of code that does everything. Routing, rendering, CSS, JavaScript, KV access, D1 queries, R2 reads, syntax highlighting, table of contents extraction, OG tag generation — it's all in one file. A new contributor would need to read the whole thing to understand the structure.

Template literals as templating. HTML is built by string concatenation in template literals. There's no type checking on props, no component isolation, no diff-based rendering. A typo in a class name fails silently. Adding a new design element means finding the right string in the right function and editing carefully.

javascriptfunction blogCardHTML(post, i) {
  return `<article class="blog-card ${i === 0 ? 'blog-card-featured' : ''}">
    <div class="blog-card-body">
      <h2 class="blog-card-title">${escapeHtml(post.title)}</h2>
      ...
    </div>
  </article>`;
}

It works. It's not pleasant to maintain at scale.

No hot reload. wrangler dev reloads on file save, but it's a full restart, not HMR. For CSS changes, you reload the browser and wait ~1 second. It's fine. It's not the instant feedback loop you get with Vite.

I wrote a lot of code I didn't need to write. The syntax highlighter is ~60 lines. A ToC extractor. A markdown renderer configuration. An HTML escaper. These all exist as libraries. I wrote them anyway.

The Real Tradeoff

The no-framework choice made sense for this project at this scale. A personal site with 10 routes, one developer, and no build step requirement.

The 1600-line file is not a problem right now. It would become one with a second developer, a design system with many components, or a significantly higher number of routes.

Frameworks exist because teams figured out that shared abstractions pay for themselves at a certain scale. The interesting question is: what is that scale? For most projects, it's lower than people assume. For mine, I haven't crossed it yet.

The answer I'd give someone starting today: use a lightweight router (Hono is 14KB and excellent) and keep your templates in template literals if you want. Skip the build step. Reach for a framework when the codebase tells you it needs one — when you're copy-pasting layout code for the third time or when the first collaborator gets lost.

Don't start with complexity you haven't earned.

Was this helpful?