SEOCHO Stack (2025)
Warning
Background
I don't fit the usual boxes. That's fine. The operating mode here is ship fast with parts you can swap.
- I'm not optimizing for status or niches. I'm optimizing for clarity, ergonomics, and hot-replaceability.
- Labels don't ship product. Decisions do.
Red Queen's Race
Everything changes, including your own reasoning. Plan for it.
- Assume you'll be wrong soon. Prove yourself wrong on purpose and on schedule.
- Design for swap-outs. Every module is a part you can replace without drama.
- Prefer learning loops over sunk-cost loyalty. When the world shifts, you shift.
Instagram's Engineering Philosophy (2011)
- Keep it very simple
- Don't reinvent the wheel
- Go with proven and solid technologies when you can
Use 1 and 2 by default. Use 3 only when "proven" also means ergonomic, portable, and easy to replace. New tools exist for reasons. Learn the reason before you commit.
Working rules
- Keep it very simple.
- Don't reinvent the wheel.
- Keep things hot-replaceable.
- Experiment freely, but keep a revert within one PR.
Definition: hot-replaceable
Swappable in a button press or single PR. Minimal glue, minimal fallout. Interoperable is close, but hot-replaceable is stricter.
Ergonomics Over Anything
Your scarcest resource is energy and a few market windows. Pick tools that reduce friction.
-
No config tax. Things should work out of the box.
- TypeScript runtime? Use Bun and skip
tsconfigloader gymnastics. - CJS/ESM headaches? Use runtimes that run both without fuss (again, Bun).
- Python packaging drift? Use uv for lockable, auto-managed deps.
- Heavy bundler ceremony? Use Vite or a framework that hides it well.
- Tools that need bespoke storybook files or boilerplate? Prefer auto-discovery.
- Hand-rolled Dockerfiles/Terraform for every tweak? Prefer Nix/Nixpacks when possible.
- TypeScript runtime? Use Bun and skip
-
Declarative > imperative. Describe what, not how.
-
If it's solvable in 5 lines, don't write 100.
Simple & Elastic > Clever Busywork
Be clear, not clever.
- Favor elastic but replaceable over stable but sticky, only if you can swap fast.
- A tiny script that ships beats a baroque "platform."
- Prefer small, explicit modules you can replace over ornate abstractions you can't.
Use SaaS, Until You Have to Study It
Buy the SLA, rent the complexity.
- Deep SaaS is great when it does one thing well behind simple HTTP.
- Avoid proprietary dialect trap unless it's a de-facto standard.
- Prefer OSS when viable, and self-host if data control or bulk export matters.
Rules of Thumb
- Simple HTTP in / webhook out → good (hot-replaceable).
- Custom DSL or deep proprietary surface → risky, avoid unless standard.
- Own your data, and make export and migration easy.
Examples
Bias
OSS + self-hosting ⇒ you control the data ⇒ bulk export ⇒ easier swap-outs
Occam's DollarShaveClub
Pick the least-work approach that safely ships.
Implementation Ladder
- HTML
- CSS
- ECMAScript
- Pure React
- A framework (Next/Remix/etc.)
- If it's < 1 hour to build safely, build it
- Only then reach for an NPM library
Tooling Ladder
- Git
- GitHub
- GitHub Apps/Actions
- Then anything else
IRL Defaults
- Native
<input type="date">> custom pickers - CSS animations > heavy JS animation libs
fetch> Axios- React Suspense > hand-rolled loading logic
- RSC > client library fetch
- GitHub Actions > external CI (unless there's a strong reason)
Deviate from the ladder only for a real, provable ergonomics win.
Engineering Philosophy (Server-First & Type-Safe)
The best code is no code. The second best uses platform capabilities directly, with strong types, clear boundaries, and server-first data access.
Server-First Architecture
Do NOT fetch on the client if the server can fetch.
❌ Wrong (client fetch):
✅ Right (server fetch):
Type Safety Without Compromise
Never use any. Ever. Infer from sources of truth.
- Better Auth inferred types:
typeof auth.$Infer.Session - Elysia + Eden Treaty or Hono Stacks for type-safe API clients
- Infer DB types from Drizzle schemas
Use Platform Features, Not Libraries
Prefer first-party plugins and capabilities over custom wrappers.
❌ Wrong (custom billing API when a plugin exists):
✅ Right (Better Auth's Stripe plugin):
Direct Over Indirect
From server code, hit the DB directly instead of hopping through your own HTTP.
❌ Wrong:
✅ Right:
Performance is Not Optional
- Parallelize independent work (
Promise.all) - Cache intentionally (framework cache directives, HTTP cache, etc)
- Avoid waterfalls
- Use RSC to minimize client JS
Technology-Specific Guidance
Better Auth
- Use server helpers (
getSession,listOrganizations) on the server only - Don't call client methods from the server
- Prefer built-in plugins (Stripe, Organization)
- Use
referenceIdfor multi-tenant boundaries
Elysia & Eden Treaty
- Eden Treaty for typed client ↔ server calls
- Narrow error types and check before accessing data
- Keep handlers tiny and composable
Database (Drizzle)
- Schema is the source of truth. Infer types from it
- Don't re-add fields already provided by plugins
- Use relations & indexes, and
limit 1on single-row fetches
UI Components
- Use shadcn/ui (already installed)
- Use Sonner for toasts (not inline status messages)
- Server components by default, client components only when necessary
- Fetch Data Inside Component, instead of prop drilling
- They'll be optimized with Partial Prerendering
Anti-Patterns
- Client-side fetching in a server-rendered app
- Duplicating functionality provided by Better Auth/Stripe/etc.
- Ignoring TS errors (no
@ts-ignoreto make it pass) - Building abstractions before you need them
- Fetching without a caching strategy
Code Review Checklist
- No
anytypes - No client fetches for server-available data
- No duplicate re-implementations of platform features
- All TypeScript errors resolved
- Server hits DB directly when applicable
- Proper error handling with type-narrowing
- Toasts via Sonner (not inline)
- Parallelized data fetching where possible
- Server components by default
The Stack (hot-replaceable by default)
- Moonrepo
- Minimal setup, great for multilingual Monorepos
- Multilingual ⇒ interoperable/hot-replaceable
- Bun
- Fast, light, understands TS natively
- No config thrash,
tsconfigpurely for editor tooling
- Elysia (+ Hono where Web-Standard routing/edge is ideal)
- Simple, ergonomic, fast
- Elysia feels familiar to Express, Hono hugs web standards
- Elysia's Eden Treaty and Hono's RPC are type-safe clients-server contracts. Ergonomic.
- Better Auth
- Minimal setup, great plugin surface (Stripe, Organization)
- Server-first helpers, typed end-to-end
- You own the data. Hot-replaceable.
- Drizzle ORM + Postgres
- Schema-first, typed queries, simple migrations
- Postgres is boring-great
- Again, you own the data. Hot-replaceable.
- Biome
- Lint + format in one, fast, low config
- AWS via FlightControl
- Vercel-like ergonomics, AWS under the hood
- Hot-replaceable: mostly ergonomic wrappers over AWS APIs
- Pair with Nixpacks for painless images
- HTTP-only cookie auth + typed REST (or Eden Treaty RPC)
- Keep tokens off
localStorage, keep surface small & typed
- Keep tokens off
- shadcn/ui + Sonner
- Copy-in components (hot-replaceable), great DX for toasts
- Vitest/Jest/Bun
- Similar APIs, pick per project needs
- Automation
- GitHub Actions for CI
- Lefthook (or plain git hooks) for visible, simple commit hooks
Skeptical but pragmatic
- Vercel & Next.js
- Don't love the occasional rebranding/overrides, but: ergonomic.
- Use server components, cache directives, and route handlers well.
- SWR / TanStack Query
- Use only when you must fetch on the client. Server-first otherwise.
- Prisma
- Fine, but Drizzle keeps types closer to SQL and feels lighter.
- If mixing: isolate behind a repository layer for hot-swap potential.
What I usually avoid unless necessary
- Docker (hand-rolled everywhere)
- Docker is imperative in the inside.
- Use Nixpacks or equivalents to avoid yak-shaving.
And try out that new hot thing in town
Let's say after 2 months, there's a new hot library in town. Should I stay uncool? I'd say: take a leap of faith, but with a parachute. Try unreliable but high-leverage ideas, wrapped in interoperable, hot-replaceable, type-safe modules.
Summary
- Be clear.
- Be direct.
- Be type-safe.
- Be Server-first.
- Be library power-user.
- Be a shipper, not an architect.