Hey, it's me, josh - as a memoji

josh.miami

I built a starter template because I kept rebuilding the same thing

Josh Echeverri
Josh Echeverri
5 min read

I built a product called The Pass in a single session. Restaurant kitchen build sheets. Supabase for auth and storage, Drizzle for the database, pg-boss for background jobs, Sentry for error monitoring, Vercel for deploys. The whole thing worked by the end of the night.

Then I looked at what I had and realized the interesting part wasn't the kitchen app. It was everything underneath it.

Auth that actually worked. OTP flows with resend cooldowns and friendly error messages. A dashboard shell with session management. Error monitoring that caught client and server errors with session replay. E2E tests that ran against real Supabase with real OTP codes extracted from Mailpit. A commit pipeline with lint-staged, circular dependency checks, and Prettier. Design tokens that weren't just Tailwind defaults.

None of that had anything to do with build sheets. It was infrastructure. And I was going to rebuild all of it the next time I started something.

The problem with most templates

Most starter templates are built in a vacuum. Someone decides to make a template, adds a login page that's never been tested in production, wires up a database they've never run migrations against under pressure, and ships it with a README that says "just add your own auth."

The result looks complete on GitHub. It falls apart the moment you try to build something real on top of it. The auth flow doesn't handle expired OTP codes. The error monitoring is "just add Sentry" with no actual config. The database setup doesn't mention that Supabase pooler connections fail over IPv6 on Railway.

I wanted the template to come from a real product, with real problems already solved.

What got extracted

I copied The Pass into a new repo and started deleting. Every table related to build sheets, steps, media assets, imports, exports. All the API routes. All the domain-specific pages. The PPTX parser. The print view. The share links.

I expected to be left with scraps. Instead, the infrastructure layer was substantial on its own. Supabase wired up for client, server, and middleware. Drizzle with a migration pipeline. Sentry across all three runtimes. Playwright E2E tests that hit real Supabase. The commit pipeline. The design tokens.

The domain was gone, but the foundation could hold anything. Empty schema. Empty dashboard. Clone it, add your tables, build.

The nuance that matters

The value isn't the file structure. It's the decisions that are already made and the edge cases that are already handled.

Take the Sentry setup. On the surface it looks like "install @sentry/nextjs and add your DSN." But the real setup is four config files across client, server, edge, and instrumentation. You need a tunnel route so client-side errors bypass ad blockers. You need a middleware matcher that excludes that tunnel route from session refresh. You need a global error boundary for unhandled React errors. And you need the whole thing to be a no-op when the DSN isn't set, otherwise local dev breaks.

That took an entire debugging session. The SDK loaded but didn't initialize. Turns out NEXT_PUBLIC_ env vars are baked in at build time, not runtime. The first deploy had no DSN because the env vars were added after the build ran. So the client bundle had an empty string where the DSN should have been. That's a gotcha you only learn by shipping.

The E2E tests were the same kind of fight. They run against real local Supabase, which sounds straightforward. But the API I wrote the helpers against was Inbucket's, because that's what Supabase used to ship. At some point they switched to Mailpit. The helpers broke silently. The tests passed on the OTP send step, but no emails arrived.

Then the email rate limit was 2 per hour. Then the dev server didn't have the Supabase env vars because they weren't in the shell environment. Three rounds of debugging before the tests reliably passed.

Every one of those problems is solved in the template. You inherit the solution without living through the debugging.

The branding

I called it Monolith Industries because the idea of a massive, ominous enterprise software company that exists for one person is funny to me. The tagline is "Highly-opinionated, enterprise-grade Next.js infrastructure for one person." The logo is a smug guy with sunglasses and a cigarette.

The tone is dead serious about something inherently small. That felt right for what this actually is: a well-engineered personal scaffold with corporate gravity it hasn't earned.

What's in it

Next.js 16, TypeScript, Tailwind v4, shadcn/ui, Supabase (Postgres, Auth, Storage), Drizzle ORM, pg-boss, Sentry, Vercel Analytics, Vitest, Playwright.

The design system uses zinc neutrals with an indigo accent. That's deliberate. I didn't want the template to ship with a strong brand palette that you'd have to rip out. The token architecture lives in globals.css via Tailwind v4's @theme inline block. Swap the accent color and it's yours.

There's a one-click Deploy to Vercel button. It prompts for your Supabase env vars, because that's the only thing standing between a clone and a running app.

The point

I'm going to use this for every new project. Client work, side projects, startup ideas. Instead of spending the first day setting up auth, configuring Sentry, writing the ESLint config, and debugging why Supabase emails aren't arriving in local dev, I'll clone the repo and start building.

That's what a starter template should be. Not a showcase of what's possible. A foundation that's already been through production.

Monolith Industries on GitHub