About
How this app is put together
One small product built end to end, so each framework decision has a real place to live. Below is where each piece is implemented.
Rendering
| Route | Mode | Why |
|---|---|---|
| / | Prerendered | No data, no session, so it can be a static file |
| /notes | SSR with CDN cache | Public list that changes slowly |
| /notes/:slug | SSR with streaming | SEO matters, comments are secondary data |
| /dashboard | ssr: false | Private, interactive, nothing to index |
| /dashboard/analytics | ssr: data-only | Server prepares the numbers, the chart renders on the client |
Data and authorization
Route loaders are isomorphic: they run on the server for the first request and in the browser during navigation. That is why no loader touches the database. Each one calls a server function, and every function that reads or writes private data runs an auth middleware and checks ownership of the specific record.
Caching
There are four caches in play and each has an owner: the router's loader cache, the Query cache, HTTP and CDN caching driven by route headers, and Postgres itself. Invalidating one does not invalidate the others, so each mutation says which cache it refreshes.
Caching the public pages safely
Public pages are served from a shared cache, so their HTML must be identical for every reader. The header renders a neutral account slot on the server and asks /api/me for the session after hydration. That is why a signed-in reader can still receive a cached article page without seeing someone else's name.
Tests and verification
Unit tests cover the pure logic that is easy to get subtly wrong: slugs, cursor encoding, password hashing and verification, and the search and note validators. The HTTP smoke script drives the running app through sign-up, sign-in, publishing, commenting, and rejection of unauthenticated mutations.