NextJS vs. Hono/Vite vs. Go/Vite – comparing container size, startup time and memory usage

For development, I needed a simple application to test mails – something like Mailpit or MailHog, but with support for SendGrid and other mail services. So I thought I’ll vibecode one in NextJS, then rewrite it to Hono/Vite and rewrite it again to Go/Vite. All iterations are published as Docker containers (maildotnull/maildotnull) and I ran multiple tests on my notebook to check the resource consumption. The results were quite interesting, so let’s have a look:

The setup

The application itself is always the same: a small mail-testing tool that receives mails and shows them in a web UI. I measured the following for each iteration:

  • Container size: the size of the Docker image
  • Build time: the time GitHub Actions needs to build and publish the image
  • Startup time: the time until the container is ready to serve requests
  • Memory used: the memory consumption of the running container

NextJS (maildotnull/maildotnull:0.1.2)

The first version was written in NextJS, because it’s probably the fastest way to get a full-stack application up and running:

  • Container size: 195.70 MB
  • Build time on GitHub: 4m 48s
  • Startup time: 956ms
  • Memory used: 86.92 MB

It works, but almost 200 MB and nearly 90 MB of memory for a small mail-testing tool felt like a lot.

Hono/Vite (maildotnull/maildotnull:0.1.4)

The second iteration uses Hono as backend and Vite for the frontend. It’s still JavaScript/TypeScript, so the rewrite was quite easy:

  • Container size: 172.43 MB
  • Build time on GitHub: 2m 59s
  • Startup time: 522ms
  • Memory used: 54.27 MB

Better – the startup time was cut almost in half and the memory usage went down by ~35%. But the container size stayed pretty much the same, because you still need to ship the Node.js runtime with it.

Go/Vite (maildotnull/maildotnull:0.1.9)

For the third iteration, I kept the Vite frontend and rewrote the backend in Go. Go compiles to a single binary, so the container doesn’t need a runtime at all:

  • Container size: 13.56 MB
  • Build time on GitHub: 1m 50s
  • Startup time: 175ms
  • Memory used: 5.29 MB

That’s a huge difference! The container is ~14x smaller than the NextJS version, starts 5x faster and uses only 5 MB of memory instead of 87 MB. I ran the tests multiple times (0.1.5, 0.1.8, 0.1.9) and the numbers were stable – memory was always between ~4.9 and 5.5 MB.

Note: This is not a completely fair comparison – NextJS gives you a lot of features (SSR, routing, image optimization, …) that a small tool like this simply doesn’t need. If you build a large application and your team knows React well, then NextJS is still a valid choice.

But in my opinion, for small self-hosted tools that should run 24/7 in the background, the Go/Vite combination is hard to beat: tiny image, instant startup and a memory footprint that you won’t even notice on your server. That’s why maildotnull will stay on Go/Vite – and honestly, with today’s coding agents, rewriting a small application in another stack is not a big deal anymore.

If you want to try it yourself, you can run it with the following command:

docker run -p 8080:8080 maildotnull/maildotnull:latest

or

podman run -p 8080:8080 docker.io/maildotnull/maildotnull:latest

Further Information

Categories:

Tags:

Comments are closed

About
about armin

Armin Reiter
Web3 & Blockchain
Cybersecurity | Open-Source

Vienna, Austria

a31 Labs Logo

Legal information