← All posts
nextjsarchitecturesaasopen-source

Why we chose these tools to build Next.js Launchpad

Every dependency is a choice with tradeoffs. Here is the reasoning behind each major tool in this boilerplate: what we picked, what we considered, and why.

Next.js Launchpad Team··8 min read

A boilerplate is a set of opinions frozen in code. Every library you include is a library your users will have to understand, maintain, and debug at 11 pm when something breaks. That raises the bar for inclusion: a tool either earns its place or it doesn't ship.

What follows is the honest reasoning behind every major choice in this stack, including what we seriously considered and why it lost.

The data layer: Postgres, Drizzle, and Zod

These three tools form a single chain. Zod defines the shape of data at the boundary, Drizzle maps that shape to the database, and Postgres stores it. They're worth understanding together.

Postgres

We chose Postgres over MySQL, SQLite, and PlanetScale. Postgres is the correct default for relational data: full ACID compliance, excellent JSON support, and availability on every major cloud provider. SQLite is genuinely compelling for edge deployments and zero-latency reads, but the moment you need concurrent writes or horizontal scaling, it starts adding complexity rather than removing it.

Drizzle ORM

Chosen over: Prisma, Kysely, raw SQL

Drizzle generates plain SQL you can read and understand. There's no magic layer between what you write and what runs. The schema is TypeScript, migrations are SQL files you can inspect and commit, and the query builder stays close to SQL syntax.

Prisma was the serious alternative. It has a stronger ecosystem of third-party tooling and more approachable documentation for newcomers. We chose Drizzle because its generated queries are predictable and its migration story (drizzle-kit generate → review SQL → drizzle-kit migrate) keeps you in control.

Zod

Chosen over: Yup, Valibot, io-ts, manual validation

Zod runs at every trust boundary: tRPC input schemas, form validation via @hookform/resolvers, and environment variable validation via @t3-oss/env-nextjs. The same schema definition validates on both server and client, which means one source of truth for the shape of your data.

The framework: Next.js, TypeScript, and Tailwind

Next.js

Chosen over: Remix, SvelteKit, Nuxt

We use the App Router. The reasoning is unglamorous: it has the largest ecosystem, the best deployment story (Vercel zero-config, or self-hosted on any Node host), and the most community answers when something goes wrong.

The App Router has a genuine learning curve. Server vs. client components, async params, the "use client" boundary - these trip people up the first time. But the Pages Router is in maintenance mode. Starting a new project on it in 2025 means planning a migration before you've shipped anything.

We use Next.js 16 with Turbopack for development. The dev server is meaningfully faster than webpack. That matters when you're iterating.

Tailwind CSS and shadcn/ui

Chosen over: CSS Modules, styled-components, vanilla CSS, MUI, Chakra UI

Tailwind keeps styling co-located with the component. No context-switching to a separate stylesheet, no theme object defined three files away. Colors, spacing, and type scale come from one source, which keeps visual consistency from drifting as the codebase grows.

shadcn/ui adds a full component vocabulary on top. You run bunx shadcn@latest add <component>, the source lands in your codebase, and you own it completely. The components are built on Radix UI primitives, so focus management, keyboard navigation, and ARIA attributes are handled correctly without extra work. We use @tailwindcss/typography for blog post prose styles.

The API and auth layer: tRPC and Better Auth

tRPC

Chosen over: REST with route handlers, GraphQL, server actions

tRPC gives you end-to-end type safety from server procedure to client call with no code generation step. You define a procedure with a Zod input schema and the client gets full type inference, including errors.

Server actions were the real alternative here. They're simpler to set up, but their ergonomics around error handling, loading states, and optimistic updates are weaker. tRPC integrates cleanly with React Query, which handles caching and background refetching. For a boilerplate that includes a working todo list as a demo, the React Query integration is immediately useful rather than theoretically useful.

Two things to keep in mind: publicProcedure is unauthenticated. Add a rate limit check at the top of any public mutation that sends email or touches tokens. protectedProcedure verifies the session and throws UNAUTHORIZED if there isn't one.

Better Auth

Chosen over: NextAuth (Auth.js), Clerk, custom implementation

Better Auth is newer and less widely known than Auth.js, but it solves the specific things that frustrate people about Auth.js: configuration is explicit TypeScript rather than adapters and callbacks, the session model is straightforward, and email/password flows work without workarounds.

Clerk was the other serious option. It handles the auth UI for you and has excellent developer experience. We chose not to include it because it's a paid SaaS dependency. If Clerk's pricing changes or the service has an outage, your auth goes with it. Better Auth is self-hosted and open source.

requireEmailVerification: true is set and should stay that way. Unverified email addresses create support problems that are slow and annoying to resolve.

The commercial layer: Stripe and Resend

Stripe

Chosen over: Paddle, LemonSqueezy, custom

Stripe is the industry default. Every developer knows it, every accountant knows it, and every country it supports has well-documented compliance requirements. The webhook-based architecture means your database stays consistent even if a customer closes the browser mid-checkout.

The webhook handler checks idempotency first via isNewWebhookEvent(). Skip that check and replayed webhooks will double-process events. Database writes happen inside a transaction; email and analytics calls happen outside. Failures in notifications should not roll back payment state.

Resend

Chosen over: SendGrid, Postmark, Nodemailer/SMTP

Resend has a clean API, good deliverability, a generous free tier, and first-class React Email support. React Email lets you write email templates as React components with @react-email/components, which means the same JSX and TypeScript you're already using everywhere else.

sendEmail() retries three times with exponential backoff. Transactional email providers occasionally return 5xx errors under load, and a single retry loop prevents most user-visible failures. In development with no RESEND_API_KEY, emails log to the console. The console.log in send.ts is intentional.

Observability: PostHog, Sentry, and LogTape

These three tools cover different failure modes. PostHog tells you what users are doing. Sentry tells you what's breaking. LogTape tells you what happened in the moments before it broke.

PostHog

Chosen over: Mixpanel, Amplitude, Google Analytics

PostHog is open source. You can self-host it if privacy regulations or budget require it. The free cloud tier covers most early-stage products. Pageviews, custom events, session recordings, and feature flags ship in one SDK.

Sentry

Chosen over: Datadog, Rollbar, custom logging

Sentry is the standard for JavaScript error tracking. The Next.js SDK instruments server components, client components, and API routes automatically. Errors in production surface with full stack traces, request context, and breadcrumbs: enough information to reproduce and fix most bugs without additional debugging sessions.

LogTape

Chosen over: Pino, Winston, console.log

LogTape is small, has zero dependencies, and outputs colored text in development and JSON in production without configuration. The module-logger pattern (authLogger, billingLogger, and so on) makes it straightforward to filter logs by domain in production. Don't use console.log in production paths.

The smaller decisions

next-intl

Chosen over: next-i18next, react-i18next

next-intl is built specifically for the App Router and integrates cleanly with server components. All routes live under app/[locale]/, but the localePrefix: "never" config means URLs never show a language prefix. The locale is detected from browser headers and stored in a cookie.

Only English is configured right now. Adding a language means adding a locale to i18n.ts and creating a matching messages/{locale}.json.

next-mdx-remote and gray-matter

Chosen over: Contentlayer, Velite, a headless CMS

next-mdx-remote renders Markdown and MDX files at request time on the server. gray-matter parses frontmatter. reading-time estimates read time. Three small libraries, no bundler configuration, no build plugin, no external service.

A headless CMS (Contentful, Sanity, and similar) adds a monthly cost, a third-party dependency, and a content API to learn. For a boilerplate's blog, files in content/posts/ are simpler, version-controlled alongside the code, and editable without an extra login.

The blog is built for search from the start. Each post gets its own generateMetadata call that populates <title>, description, and OpenGraph tags from frontmatter. JSON-LD structured data is injected per post so search engines understand the content type, author, and publication date. An XML sitemap is auto-generated and includes every post URL. You write a Markdown file, push it, and the SEO work is already done.


Every choice here is a tradeoff, and some of them won't match your situation. If you need SQLite for edge deployments, Clerk for managed auth UI, or a CMS for non-developer content editors, this stack can accommodate those swaps. The boundaries between layers are explicit, and the file structure maps one concept to one place: swapping one tool out doesn't require touching six others.

The goal was a stack you can understand fully in a day, run confidently in production, and extend without fighting the architecture.

Use Next.js Launchpad as your starting point

Free and open source. Fork it, customize it, ship it.

View on GitHub