4 minute read
The problem
Deep into building Shelf, I realised I was holding a mental model of the app that was partially wrong. Not catastrophically wrong — the app was working — but wrong in the way a hand-drawn map is wrong. Good enough to navigate. Not accurate enough to trust for anything precise.
I couldn’t find a tool that showed me the app clearly. So we built one.
Why it compounds
Remix’s file-based router encodes hierarchy in filenames. The convention is elegant. The cognitive overhead of holding forty files in that naming convention is real.
Each route file does three jobs: a loader for server-side data fetching, an action for mutations, and a default export for the component. Routes nest inside each other through <Outlet /> — the component tree at any URL is a stack of nested layouts, none of it visible from the file tree.
Those three layers compound. But there’s a fourth: AI-assisted builds add their own incomplete map on top of yours. If your mental model is partially wrong, the briefs you write will be partially wrong, and the code that comes back will reflect that. We caught several of these after the fact — a loader dependency assumed, an outlet chain misread, a data fetch landing in the wrong route.
The map problem isn’t just cognitive overhead. It’s a drift multiplier.
What we built
Three iterations, each one answering a question the previous one couldn’t.
v1: Route tree with badges. Colour-coded badges marking which routes had loaders (L), actions (A), and components (UI). Right panel showed the outlet chain from root to leaf. Useful immediately — it made obvious we were fetching more data than several components needed.
v2: URL-first with nested containment. The containment view was the breakthrough. Seeing _app wrapping dashboard wrapping _index as literal nested boxes made the layout architecture immediately readable. What the file naming implies, the diagram makes explicit.
v3: Ecosystem map. A cross-layer view: routes, database tables, the AI layer, external APIs. Edges colour-coded by type. Hover a node and everything unconnected dims.
The answer for Shelf: the AI layer is almost entirely decoupled from the Remix app. Claude runs inside ECS tasks, not route loaders. The ecosystem map made that visible in a way no amount of code reading had.
How the parsing works
The route tree is simpler than it looks. Filter app/routes/, parse dot-notation for parent-child relationships, check each file for loader, action, and export default to badge the route. Fully automatable across any Remix app.
The ecosystem map is different. Route-to-database edges require reading the code. For the prototype we mapped it manually — fifteen minutes, more accurate than any static analysis because we know what the code does. For a general tool you’d need static analysis (grep for db.query, prisma.model) or a config file.
What a small-team solution looks like
The tool is a static HTML file — no build step, no backend, no framework. Open it in a browser. It works.
Don’t wait for the tool that would work for anyone. Build the tool that works for you, now. If it generalises, open-source it later.
vizstack
We shipped it as vizstack — an open-source CLI that runs against any Remix or Next.js App Router codebase and produces a single self-contained HTML file.
node parse.js ./your-app viz.html
open viz.html
No install, no server, no dependencies. Works on Epic Stack (23 routes, 3 tables) or Inbox Zero (253 routes, 49 tables, 4 AI providers). Trigger detection — webhooks, scheduled crons, polling endpoints — works zero-config from URL patterns.
One known limitation: Prisma gives full table schemas. Raw SQL drivers collapse to a single DB node.
