50% off SaaS Starter Kit — only for the first 100 buildersGrab it →
← Back to blog
best-practicesMay 19, 2026·8 min read

The Boring Technology Principle: Why We Reach for Proven Tools Every Time

Exciting new tech is tempting. But after shipping enough products, we keep coming back to the same boring stack — and it keeps saving us.

Ștefan Binisor

Ștefan Binisor

Co-founder, peal.dev

There's a version of us that exists in an alternate timeline where we rewrote everything in the hottest new framework every six months. That version of us ships nothing. We know this because we spent about a year being that version of us.

At some point — probably around the third time we had to rewrite a production auth system because a 'promising' library got abandoned — we started gravitating toward boring technology. Not old technology. Not bad technology. Boring technology. The stuff that's been around long enough to have real answers on Stack Overflow, real edge case documentation, and real people who've hit the same walls you're about to hit.

This is the principle: when you're building something real, the technology should be the least exciting part of the project. The product should be exciting. The tech should just work.

What 'Boring' Actually Means

Boring doesn't mean outdated. PostgreSQL is boring. It's also the most reliable, feature-rich relational database you'll ever use. Next.js is getting to that point — it's been around long enough that most footguns are documented, most deployment gotchas have blog posts about them, and the ecosystem around it is massive. Stripe is boring. Resend is getting there. These are tools with known failure modes.

The opposite of boring isn't exciting — it's unknown. When you pick a database that launched eight months ago, you're not just adopting technology, you're signing up to discover its failure modes yourself. You become the documentation. That's fine if you're the kind of person who enjoys that. We are not those people anymore. We have products to ship.

The goal isn't to use the newest tool. The goal is to spend zero cognitive budget on infrastructure and all of it on the product.

The Hidden Cost of Novelty

Every tool in your stack has a learning tax. You pay it once when you first adopt the tool, and then again every time something breaks in production at 2am and you're debugging from a gas station parking lot on your phone (yes, this happened). With boring tools, that debugging session takes twenty minutes because someone else already wrote the post-mortem. With novel tools, it takes four hours because you're the first person to hit this exact edge case.

There's also an integration tax. New tools haven't been integrated with everything else yet. The TypeScript types might be rough. The Next.js adapter might be a community package maintained by one person. The error messages might be cryptic because nobody's written a 'common errors' guide yet. You're not just adopting the tool — you're adopting its entire ecosystem, or lack of one.

Here's a concrete example. We were evaluating an auth library a while back — relatively new, beautiful DX, great TypeScript support. We built a prototype with it. Then we tried to implement session invalidation with concurrent device handling and custom claims. The documentation ran out. The GitHub issues were people asking the same questions we had. The maintainer was responsive but hadn't gotten there yet. We switched back to Auth.js (boring) in two hours and had it working in another two hours, because the documentation is comprehensive and the community has solved every weird edge case you can think of.

How We Actually Evaluate a Tool

When we're considering adding something to our stack, we run it through a few questions. Not a formal checklist — we just think about these things:

  • How old is it? Under a year is a yellow flag. Under six months is a red flag unless there's a very good reason.
  • How many GitHub stars vs. how many open issues? Stars are vanity. A healthy ratio of stars to open issues (with active responses) tells you about the maintainers.
  • Can I find answers on Stack Overflow for the most boring, common problems? If searching 'tool-name authentication' returns nothing, that's the library's future hitting you in the face.
  • What happens when I need to eject? Can I move off this tool without rewriting everything? Boring tools tend to give you escape hatches.
  • Is there a managed version I can pay for? Boring infrastructure tools usually have hosted versions, which means the business model is real and the tool will exist in two years.
  • Who's using it in production? Not startups. Large companies, or at minimum, many independent developers who've shipped real products.

We also pay attention to how a tool handles failure. Boring tools fail in boring, well-documented ways. Novel tools fail in creative, undocumented ways that require you to read source code at midnight.

Our Actual Stack (And Why)

We keep coming back to the same tools. Here's a simplified version of what we reach for and the boring reasons why:

// Our boring, reliable stack choices:
const stack = {
  framework: 'Next.js',        // massive ecosystem, great docs, Vercel backing
  database: 'PostgreSQL',      // 35+ years old, never let us down
  orm: 'Drizzle',              // newer but TypeScript-first, simple mental model
  auth: 'Auth.js (NextAuth)',  // every edge case documented somewhere
  payments: 'Stripe',          // industry standard, incredible docs
  email: 'Resend',             // built by people who know email, React Email support
  hosting: 'Vercel',           // zero-config Next.js deploys, just works
  storage: 'AWS S3 / Cloudflare R2', // been around forever, go-nowhere APIs
};

// What we avoid:
const redFlags = [
  'launched-last-month-but-has-great-DX',
  'one-maintainer-part-time-project',
  'no-typescript-support',
  'breaking-changes-every-minor-version',
  'docs-are-just-a-readme',
];

Drizzle is the one 'newer' thing in that list, and we adopted it carefully. The reason it passed our bar: the mental model is just SQL. You don't have to learn a new query language. The TypeScript inference is excellent. And critically — even if Drizzle disappeared tomorrow, your actual SQL knowledge is portable. That's an escape hatch.

The Boring Database Migration Pattern

Let's get concrete. Here's how we handle database schema changes with Drizzle — a pattern we've used across multiple projects without drama:

// schema.ts — your source of truth
import { pgTable, text, timestamp, uuid, boolean } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: text('email').notNull().unique(),
  name: text('name'),
  emailVerified: timestamp('email_verified', { mode: 'date' }),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});

export const accounts = pgTable('accounts', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id')
    .notNull()
    .references(() => users.id, { onDelete: 'cascade' }),
  provider: text('provider').notNull(),
  providerAccountId: text('provider_account_id').notNull(),
});
# Generate migration from schema changes
npx drizzle-kit generate

# Review the generated SQL before applying
cat drizzle/migrations/0001_add_accounts_table.sql

# Apply to database
npx drizzle-kit migrate

# That's it. No magic, just SQL you can read and understand.

The reason this is good: the generated migration is readable SQL. You can review it. You can run it manually if something goes wrong. You're not locked into a framework's magic. This is what boring tooling looks like — transparent, auditable, no surprises.

When You Should Break the Rule

We're not dogmatic about this. There are legitimate reasons to adopt newer technology:

  • The boring alternative genuinely doesn't solve your problem. If you're doing something genuinely novel, you sometimes need novel tools.
  • The new tool was built by people who ran the boring tool at scale and is explicitly solving its documented limitations. (This is the Drizzle case — the authors knew Prisma's limitations deeply.)
  • You're building something experimental where learning is the point. Prototypes and side projects are great places to try new things.
  • The boring tool has a license change, pricing change, or maintenance issue that makes it a liability.

The key is making the choice deliberately, not getting swept up in Twitter hype. When a new tool trends on Hacker News, that's information about what developers find interesting, not necessarily what you should build your production system on.

The Meta-Point About Shipping

Here's what we've noticed: developers who ship a lot of things tend to use boring stacks. Developers who talk about tech tend to use novel stacks. This is not a coincidence. The cognitive overhead of constantly evaluating new tools, updating integrations, and debugging novel failures is directly subtracted from time spent building features users care about.

Every time you pick a boring tool, you're essentially pre-paying yourself. You've reduced the expected debugging cost, the documentation-reading cost, and the 'what is this error message even saying' cost. That's real time you get to spend on the product.

Novel tech is spending future time in exchange for present excitement. Boring tech is investing in future productivity. We've made both trades. We know which one we prefer now.

This is also why our peal.dev templates are built on boring stacks. When you buy a template, you're not just getting code — you're getting a stack where the decisions have been made, the footguns are documented, and the integrations are tested. The last thing you want is to spend three days figuring out why some new auth library doesn't play nicely with some new database client. The template exists so you can skip straight to building your actual product.

A Practical Starting Point

If you're starting a new project and want to apply this principle, here's the shortest possible version of our decision process:

function shouldAdoptTool(tool: {
  ageInMonths: number;
  hasTypescriptSupport: boolean;
  stackOverflowResults: number;
  hasHostedVersion: boolean;
  maintenersCount: number;
  hasBreakingChangesHistory: boolean;
}): 'yes' | 'wait' | 'no' {
  if (tool.ageInMonths < 6) return 'wait';
  if (!tool.hasTypescriptSupport) return 'no';
  if (tool.stackOverflowResults < 100) return 'wait';
  if (tool.maintenersCount === 1 && !tool.hasHostedVersion) return 'wait';
  if (tool.hasBreakingChangesHistory && tool.ageInMonths < 24) return 'wait';
  
  return 'yes';
}

// Most things return 'wait'. That's the point.
// 'Wait' means: let someone else discover the edge cases first.

Most things should return 'wait'. You're not being slow — you're being efficient. Someone else is going to find the edge cases, write the blog posts, open the issues, and get them fixed. Then you get to benefit from all of that work for free, just by not being first.

The goal is to ship products people use. Not to have the most interesting tech stack. The most interesting tech stack is the one nobody ever asks about because everything just works.

Newsletter

Liked this post? There's more where it came from.

Dev guides, honest build stories, and the occasional 2am debugging confession — straight to your inbox. No spam, unsubscribe anytime.

Browse templates
Written by humansWeekly dropsSubscriber perks

Join the Discord

Ask questions, share builds, get help from founders