Ecopages

Why I Built a Web Framework

Ecopages is a project I started almost three years ago, when Bun came out.

The possibility of running TypeScript without having to care about bundling and all the rest made me think: maybe we can build a no-bundle framework. Something that doesn't need to be built before it can run, with basically no dependencies.

So I started to put my hands on it and investigate what, at the time, was still a novelty.

I was especially impressed by Bun's loaders and how simple they were. I was experimenting a lot with web components, and being able to load processed Tailwind CSS inside them was a big shift, so I started to play with it.

My first idea was to build a static site generator based on web components and JSX. At the time I was admiring the work behind Eleventy and Astro, and then I discovered @kitajs/html. Together with Lit and Bun, it gave me the pieces I needed to build what I had in mind.

Verbosity Over Magic

One of my principles was to prefer verbosity to magic. Dependencies should be declared explicitly. I wanted to see how things were connected instead of trusting the framework to guess everything for me.

Server rendering web components helped to push me in this direction. It was a real headache. Every time I thought I had it, something else went wrong. HMR. Static rendering. Hydration. I ended up defining different ways to render pages depending on the integration being used.

At one point I built a system that fetched a page to generate its static HTML, then used experimental Lit libraries to hydrate the DOM. It worked and, thanks to Bun's speed, generation was also pretty fast. I wasn't testing millions of pages, of course. But at the time Next.js builds could take a long time, so the performance didn't worry me too much for my use case.

Next.js was definitely another source of inspiration. The way Ecopages loaded data to build static pages was strongly influenced by it. I mean, I think most major frameworks have been influenced by Next.js, both the ones that admit it and the ones that do not.

I was using APIs like getInitialProps. The difference was that dependencies were explicit and React was not involved.

Around the same time, I was working on other small projects. One was @ecopages/scripts-injector, a simple script injector with an API heavily inspired by Eleventy's is-land. I included it in Ecopages and built my naive implementation of resumability, although hydration on demand is probably a better name for what I was doing.

That was very cool: I could build lightweight static pages and load JavaScript only when it was needed.

Learning by Building

Until that point, I was coding manually, at night and in my spare time. I didn't have an engineering education. I came from Literature and Philosophy, followed by a very varied working life.

I felt that I needed to dive deeply into the technologies I loved if I wanted to master the craft. I also felt that I needed to grow quickly. The path I chose was nonstop building.

From a static site generator for Lit and JSX, I soon found myself looking for an MDX renderer. Then came integrations, still one of my favourite ideas in the framework. An integration was a class based on an abstract definition of how Ecopages should render a file according to its extension. This is why I started using suffixes such as page.lit.tsx and page.kita.tsx. I still love that approach.

I was also studying development patterns and different ways to write code, then immediately trying them in the framework. I built classes for asset management and file processing. I built an image processor based on Sharp, plus a small image component that generates optimised images statically and makes them available inside documents through virtual modules.

Virtual modules were another very cool discovery. Bun's implementation was simple, but at the time it behaved differently when bundling and when running as a plugin. I needed a few tricks, but eventually I had it working.

This was the point of the project for me. Do something. Discover what is wrong. Refactor it. Do it better. Then find another problem and start again.

When AI Entered the Project

Around this period, AI started to become very powerful and very cheap. I remember the move from tab completion to the first agentic iterations. It looked crazy. There was also a real illusion of goodness. The output looked great until you reviewed the code and discovered that some of it was completely broken.

I also used AI to validate many of my ideas because I didn't have somebody available to discuss the framework with. I shared some projects with colleagues, but people have their own lives. In the end, meeting for a beer was always the real hangout moment. Who cares about your framework, really? Fair.

AI was there and always ready to talk about it. Some of those conversations helped me. Others sent me in the wrong direction, more than once. Magical code and solutions that looked fantastic came back later as massive, hard-to-fix bugs. I fixed most of them manually.

Still, I was really into discovering and deepening my knowledge, and I still am. I wanted these topics to stay with me throughout what I hope will be a long software engineering career. Ecopages gave me a place to explore them for real, including all the bad decisions and refactors that came with the process.

The Framework Kept Growing

I started building more packages. @ecopages/browser-router intercepts anchor links and morphs the DOM to navigate in the browser. I added smaller utilities too, including a file-system library that switches between Bun and Node based on Bun's recommendations.

Soon enough, I needed to render React applications as well. Then I started thinking about foreign children, with components from one integration nested inside another. Then React layouts and persistence. Then a router that understands layouts. Then file identities, so the client also knows which file is which.

Then I said: maybe I need a small server.

I was still pushing towards a simple static generator. That is where the eco in Ecopages comes from. Most of the time you don't need a server. But when you need it, you need it.

I started looking at patterns from frameworks I was using and playing with, such as Hono and ElysiaJS. I read about WinterTC and thought a lot about how to build a strong server layer: typed handler definitions, typed requests, middleware, and a reasonable way to share server data with the component layer. It was a lot of fun.

Meanwhile, AI agents were getting stronger and stronger. Refactoring became much easier, also because I had split the code into small pieces. A change sometimes had to touch many points in the application, but that was okay. The boundaries were there and I could follow them.

Moving Away From Bun

Eventually, Bun stopped convincing me as much as it had at the beginning. I still love the idea of a single tool, but self-hosting Bun applications caused strange issues here and there. From my experience, support sometimes seemed to happen mainly on Twitter when somebody from the team was tagged directly. More importantly, I started to feel locked in.

I was distributing the library through JSR, and every time I published a package I wondered whether TypeScript-only distribution was the right choice. I like JSR, but I couldn't have bin files and I love the npx experience.

So I told myself: let's go. AI is here now. Enabling Node cannot be that hard.

It was hard.

I have always tried to keep full control, but moving from Bun to Node overwhelmed me more than once. I was basically building a small runtime. I had to transpile code and recreate loader behaviour. I kept asking myself: why can't I just use Vite?

Things became more complicated. I started manipulating ASTs with Oxc, working on caching, and trying to reuse the parser. Then I decided to replace esbuild with Rolldown, only to discover after completing the refactor that it does not support ECMAScript decorators. It wasn't a disaster, but one of my projects uses decorators heavily. I support both legacy and ECMAScript decorators, and in my mind I was ready to leave the legacy version behind.

That is framework development, at least the version of it I know. You make a decision, follow it much further than expected, discover the awkward detail, and decide what to do next.

Where Ecopages Is Now

Agentic coding has reached levels I couldn't have imagined when I started this project. By iterating with AI, using adversarial reviews, reading the code myself, and relying on the tests I already had in place, I now have a fast and effective framework. Maybe not the fastest, but fast enough, and built around my preferences.

I use Ecopages for my personal projects and some private side projects. Real usage has helped me find strange bugs and keep improving the framework. I have learned a lot from building it and I still do. I strongly believe it has made me a better developer.

Now, in a period of heavy agentic AI, it is also helping me move towards the future with more awareness. I know I need to keep updating myself for whatever comes next.

If you want to try it, you can find it at ecopages.app. If you like it, or if something feels weird, hit me up.