stack.faunder.fi

Faunder Stack

For a long time I’ve been wondering why building modern web applications so often feels harder than it should. We have more tools than ever, yet more and more often I notice that I no longer fully understand what is actually happening: where the data flows, why rendering works the way it does, why a simple page needs multiple layers of abstraction.

Faunder Stack was born from that frustration — and from the idea that full-stack development could once again be something you can understand end to end. Not because everything has to be done manually, but because when you build something, you know what you built and why. Like working with the LAMP stack 20 years ago.

What is Faunder Stack?

Faunder Stack is not a framework. It is a small, opinionated setup that stays as close as possible to the fundamentals:

Why Bun?

Bun because it's the first tool in a long time that genuinely reduces friction. One tool handles the server runtime, TypeScript compilation, and package management. When you can get the basics working without a separate build setup, the whole project feels calmer and more focused. Less moving parts means less things that can break.

Why Preact?

Preact because it's just a UI engine — it doesn't try to be anything else. It solves one thing and does it well and understandably. It's smaller and lighter than alternatives, and it feels more like a library than a platform. I don't want the stack to force me into a specific "framework mindset," but instead allow me to build just as much abstraction as I actually need.

For templating, we use htm . You don't need to learn anything new beyond HTML and JavaScript. If you prefer JSX, you are free to switch to it.

For routing, we use preact-iso. It's lightweight and isomorphic. The router is async-aware: if a route loads a component asynchronously, the old route remains visible until the new one is ready.

For state management, we use @preact/signals. It's lightweight and reactive. State changes automatically update components in the most efficient way possible, without the complexity of traditional state management solutions.

Why SQLite?

SQLite because it's fast, reliable, and simple. For many projects it's the best way to start, and it can take you surprisingly far. You don't need a separate database server, and there's no connection pooling or complex setup to worry about. Bun supports SQLite natively, so you don't need to install any additional libraries.

In Faunder Stack, SQLite is used directly. SQL is visible in the code. There's no ORM layer hiding what's going on. Migrations are files. CRUD is just functions. When something goes wrong with the database, I want to see exactly what query is running.

Why Faunder UI?

Faunder Stack uses Faunder UI as the default design system. It's pure vanilla CSS that includes pre-styled UI components needed for building most services. It significantly speeds up development while remaining pure CSS and using modern CSS features. If you don't want to use Faunder UI, just remove the <link> tag that references the CSS file.

In Preact components, I want to write CSS as CSS. I don't want CSS-in-JS systems, utility-first frameworks, or build-time tricks. I just want: "here is a style, put it in a style element." That's why Faunder Stack includes a small css helper function that returns a style as a VNode.

And for scoping? Modern browsers support @scope. It's a native solution, and we use it. It gives you component-scoped CSS without a framework — as long as you accept targeting modern browsers.

Server Side Rendering

Faunder Stack uses preact-iso for SSR support. It provides isomorphic rendering: the same code runs on the server and in the browser.

Server-side rendering is important: faster first paint, better SEO, better UX. But it's also where many stacks become overwhelmingly complex: loaders, data layers, routing APIs, and mysterious "magic data" lifecycles.

In Faunder Stack, SSR follows a simple snapshot model:

  1. The server gathers the data (e.g., initialTodos).
  2. The server renders the HTML.
  3. The data is embedded into the HTML as window.__SSR_CONTEXT__.
  4. The client hydrates and uses the snapshot only as initial state.
  5. The snapshot is removed after hydration via ssrFinish().

The snapshot is "only at the beginning." After that, data lives in normal state or stores. SSR context keys use the initialX prefix: when you see initialTodos, you know it's a starting value — not the entire truth of the application.

In a Preact component, read the snapshot via ssrContext(). Use it directly in your component state.

In a signal store, seed the signal with initialTodos from the snapshot, then let it live on its own. The snapshot is removed. No mixing of concerns.

Authentication

Authentication in Faunder Stack is intentionally basic: sessions stored in SQLite, password hashing, and a simple getUserFromRequest() function. This is enough for prototypes and small projects.

The key point is that it’s easy to replace. If you need a production-grade solution, you can swap it for something like Clerk or better-auth. Faunder Stack doesn’t force you into a specific auth solution — it gives you a starting point.

No packages, just code

Faunder Stack does not add its own npm packages. It only provides a few utility functions and example code. Everything is always yours to modify.

Everything is also easy to remove or replace. If you don’t need a part, delete it. If you want to do things differently, change the code. Faunder Stack is not a framework that locks you into a model — it’s a set of tools and examples you can shape to your needs.