SEOCHO Stack (2025)
SEOCHO is...
- Straight: each components opt for least-work
- Ergonomic: components build easy and error-free
- Overt: no meaningless pre-optimization
- Composable: components optimistically cooperate
- Hot-replaceable: bad actor can get sorted out with 5 lines of code change
- Orthogonal: each components specialize mutually exclusive but collectively exaustive
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.
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; 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; skip
tsconfig
loader 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; 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; 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; make export and migration easy.
Examples
- Resend‑style HTTP: ✅ acceptable
- Zapier: ✅ until webhooks; ❌ beyond (too much study)
- BigQuery: ✅ commodity SQL; ❌ deep dialect if you'll need portability
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):
'use client'
export function BadComponent() {
const [data, setData] = useState(null)
useEffect(() => {
fetch('/api/data')
.then((r) => r.json())
.then(setData)
}, [])
return <div>{data}</div>
}
✅ Right (server fetch):
// Server component
export async function GoodComponent() {
const rows = await db.select().from(table)
return <div>{rows.length}</div>
}
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):
export const billingApi = new Elysia({ prefix: '/billing' }).post('/create-checkout-session', async () => {
/* ... */
})
✅ Right (Better Auth's Stripe plugin):
await client.subscription.upgrade({
plan: 'pro',
successUrl: '/dashboard',
cancelUrl: '/pricing',
})
Direct Over Indirect
From server code, hit the DB directly instead of hopping through your own HTTP.
❌ Wrong:
const response = await fetch(`${process.env.NEXT_PUBLIC_URL}/api/v1/campaigns/${id}`)
const campaign = await response.json()
✅ Right:
const [campaign] = await db.select().from(campaigns).where(eq(campaigns.id, id)).limit(1)
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
referenceId
for 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;
limit 1
on 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-ignore
to make it pass) - Building abstractions before you need them
- Fetching without a caching strategy
Code Review Checklist
- No
any
types - 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;
tsconfig
purely 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.