How Snippiz is built
Snippiz is a small, deliberately boring web app: one Rails application, one SQLite database, one container. This page lists everything it is made of and why, partly because that is the kind of page we like reading ourselves, and partly because you should be able to see what you are trusting with your reading.
Backend
A single Rails application. It fetches your Feeds on a schedule, parses them, and renders every page server-side.
-
Ruby
The language everything server-side is written in.
-
Ruby on Rails 8
The framework behind the whole app: routing, models, views, background jobs, and the session-based sign-in.
-
Feedjira
Parses the RSS and Atom XML behind every Feed you subscribe to.
-
curl
Does the actual fetching of Feeds and websites, rather than Ruby's built-in HTTP client.
-
libvips
Generates the cover thumbnails and Feed icons you see on cards, from the original images.
-
Solid Queue
Runs the background work: checking Feeds, resolving images, importing an OPML file.
Frontend
No single-page app, no JavaScript framework, and no build step. Pages are plain HTML sent by the server, with just enough JavaScript on top to make them feel instant.
-
Hotwire (Turbo)
Swaps in new HTML instead of reloading the page, and updates things like unread counts live as you read.
-
Stimulus
Small, targeted JavaScript controllers for the interactive bits: theme switching, dialogs, the media players, drag-and-drop ordering.
-
Import maps
Ships JavaScript straight to your browser as modules. There is no bundler, no Node, and no node_modules folder in this project.
-
Tailwind CSS
Every style on every page, written as utility classes.
-
daisyUI
The component layer on top of Tailwind: buttons, cards, tabs, dialogs, and the light and dark themes.
-
View Transitions API
The native browser feature behind the sliding and fading between pages. No animation library involved.
Data
Everything lives in SQLite. Your subscriptions, your Tags, and how far you have read are rows in the same database the app reads on every request.
-
SQLite
The database, in production as well as in development. Not a scaled-down stand-in for something bigger.
-
Solid Cache and Solid Cable
Caching and live updates, each in its own separate SQLite database so a busy cache never slows down your Snips.
Running it
One container, deployed from a laptop with a single command. Nothing here needs a cloud console.
-
Docker
The app, its Ruby version, and its system dependencies ship as one image.
-
Kamal
Deploys that image with zero downtime, and rolls back the same way.
-
Puma and Thruster
Serve the app itself, plus HTTP/2, TLS, compression, and caching of static files.
Keeping it honest
Small project, boring tooling. All of it runs before anything ships.
-
Minitest
The test suite, including browser-driven tests for the parts that only break in a real page.
-
RuboCop
Keeps the Ruby code consistent, with every exception to its rules written down and explained.
-
Brakeman and bundler-audit
Scan the code for security issues and the dependencies for known vulnerabilities.
Why it's built this way
Most of these are unfashionable choices. Here is the reasoning behind each one, and what it means for you as a reader.
Because a feed reader is a read-heavy app with one writer, which is exactly what SQLite is good at. The database file sits on the same machine as the app, so a query has no network to cross and typically answers in well under a millisecond. Rails 8 made this a first-class setup rather than a compromise.
Because the server already knows what your Snips page should look like, and sending it as HTML is both faster to display and far less code to maintain. Turbo then keeps navigation snappy without a second copy of the app running in your browser. The practical upside for you: pages render immediately instead of showing empty placeholders first.
Publishers sit behind CDNs that are increasingly suspicious of anything that is not a browser, and Ruby's built-in HTTP client gets turned away by some of them while curl gets through. Feed fetching is the one thing this app absolutely must get right, so it uses the tool with the best track record rather than the most elegant one.
Every cover image you see was downloaded once, resized, and stored by Snippiz. That keeps lists fast, survives publishers who block hotlinking, and means your browser is not quietly announcing your reading habits to a dozen other servers while you scroll.
One Feed is fetched once for everybody subscribed to it, instead of once per account, which is both kinder to the publisher and the reason a popular Feed is up to date the moment you add it. Everything personal (which Feeds are yours, your Tags, how far you have read) is kept separately, per account.
Storing one row per person per article would mean millions of rows to say very little. Instead Snippiz remembers a point in time per Feed ("everything older than this is read") plus the handful of exceptions you made by hand. That is why marking a whole Feed as read is instant no matter how much is in it.
Curious what changed when? The Changelog covers every new feature as it ships.