A good tech stack is like the right weapon — without the proper tools, you risk inefficiency.
After years of experimentation, I've settled on a stack that maximizes productivity without locking me in — ensuring flexibility for whatever comes next.
React for the frontend, Node.js for the backend, TypeScript everywhere, and a combination of modern tools that work seamlessly together.
1. Full-Stack Framework - Next.js
React is basically just a UI library that's slowly becoming full-stack.
Sure, you can build a React app from scratch with Vite + React Router, but as this experiment of stripping away Next.js revealed, framework conventions act as a common protocol for human-AI collaboration — constraining agent output variability and giving everyone (including your coding agent) the same rules to operate under.
CSS Modules often feel like they give clean Separation of Concerns, but in reality they lead to specificity fights, naming headaches, huge CSS bundles, and constant context switching between JS and CSS — which kills productivity.
Early utility-class tools before 2015 flopped because of weak ecosystems and poor customization, often ending up as messy "spaghetti" code without real component thinking.
Fast forward to now: Tailwind CSS dominates and keeps pulling further ahead. Its utility-first style, combined with component-driven patterns, has completely changed how we approach styling in React.
Pair it with shadcn/ui and things get even better:
Behavior → handled by headless/primitive components (pure logic, no style)
Appearance → controlled atomically with Tailwind classes
This clean separation lets you scale design systems easily, without crazy overrides or deep CSS hacks, while keeping productivity high.
Type-Safe API has become essential for modern full-stack development. oRPC paired with Zod delivers one of the cleanest and most practical end-to-end type safety solutions in the TypeScript ecosystem.
Define a single Zod schema on the server — it powers runtime validation, generates frontend TypeScript types, and keeps API docs up to date. This eliminates type drift, duplication, and fragile assumptions, giving both frontend and backend mathematically enforced consistency.
Alternatively, combining OpenAPI specs with openapi-typescript achieves similar type safety. It auto-generates TypeScript definitions from OpenAPI schemas, keeping the frontend in sync with backend contracts while boosting IDE feedback and reducing runtime errors.
While tRPC remains a popular choice, many have recently recognized that oRPC is not only cleaner and more low-friction, but also evolving at a much faster pace — positioning it as a more future-ready alternative.
4. State - TanStack Query and Zustand
In modern React applications, state is typically categorized into four main types: local state, URL state, global state, and remote state (server-side data).
The goal is to choose the right tool for each type to keep the codebase clean, performant, and maintainable.
For local state, React's built-in useState combined with prop drilling remains the simplest and most straightforward solution in many cases.
For URL state (search params, filters, pagination), nuqs provides type-safe URL search parameter management with React state semantics. It keeps your UI in sync with the URL, enabling shareable and bookmarkable application states.
For global state, Zustand provides a lightweight, high-performance alternative to Redux. It avoids unnecessary re-renders and eliminates much of the boilerplate that made Redux feel overwhelming for many developers today.
For remote state (data fetched from APIs), TanStack Query (formerly React Query) excels, offering a powerful set of features including:
Automatic caching
Infinite scrolling / pagination support
Optimistic updates
Built-in loading, error, and retry handling
and more...
These capabilities dramatically reduce boilerplate while keeping your UI fast and in sync with the server.
The best tool always depends on your application's scale and requirements — sometimes "useState" alone is sufficient, while larger projects benefit greatly from Zustand and TanStack Query.
5. Realtime & Interactive UI - Upstash and TanStack DB
Upstash Realtime is a fully serverless, globally distributed Pub/Sub service built for low-latency realtime messaging. It allows you to publish messages to channels and have connected clients instantly receive updates via WebSocket or HTTP streaming.
TanStack DB is a reactive client-first store that extends TanStack Query with collections, live queries, and optimistic mutations. It greatly simplifies optimistic updates and centralizes frontend CRUD operations, making state easier to debug with the concept of collections — typed sets of objects that serve as the single source of truth for your UI data.
import { queryCollectionOptions } from "@tanstack/query-db-collection";
import { createCollection } from "@tanstack/react-db";
import { pick, pickBy } from "es-toolkit";
import type { NoteCategoryModel } from "@/features/note/types";
import orpc from "@/lib/orpc";
import { getClientSideQueryClient } from "@/lib/orpc/integrations/tanstack-query/query-client-provider";
Defining a collection with optimistic mutations and consuming it with a live query
Interactive UI patterns like drag-and-drop reordering benefit directly from this stack. dnd-kit handles the interaction layer with a lightweight, accessible, hooks-based API, while TanStack DB's optimistic mutations ensure order changes apply instantly on the client and sync reliably to the server — no manual cache invalidation required.
6. Testing - Vitest and Storybook
Testing is one of the areas most likely to be profoundly transformed by AI assistance in the near future. Its repetitive nature, heavy boilerplate, and the constant maintenance burden when code evolves make it an ideal candidate for AI-driven generation and upkeep.
Currently, Vitest has rapidly gained significant momentum and is increasingly replacing Jest in modern projects. The main advantages include:
Substantially faster execution speed
Much simpler and lighter setup
Modern features such as native browser mode
Excellent TypeScript integration, especially when combined with utilities like expectTypeOf and type-safe schema matching via expect.schemaMatching (commonly used with Zod)
Meanwhile, the latest versions of Storybook have introduced several powerful testing capabilities in a unified, isolated environment:
Together, these developments enable a more streamlined, faster, and type-safe testing experience while significantly reducing the maintenance cost traditionally associated with comprehensive frontend test suites.
7. Development - Cursor's Agents Window
These days, the choice of IDE matters less than it once did. Tools like OpenAI’s Codex and Anthropic’s Claude Code have become the default playbook for AI-assisted coding. Cursor’s new Agents Window is quickly closing the gap. Even with some regional restrictions, Cursor remains my go-to choice for development.
AI is a game-changer, but not a silver bullet. As mentioned in How Software Engineering Is Evolving, bridging the context, ensuring Atomicity among Agents, bringing human's judgement are still essential.
My Highlights of exercution flows:
Context-First: Use AGENTS.md and SKILL.md for reusable context and specialized capabilities.
Prototype-First: Use v0.dev or Pencil for UI/logic prototyping before integration to narrow down the ideal UI state.
Plan-First: Use Plan to verify direction, Ask to challenge assumptions, and Debug for visual debugging.
Async Workflow: Let the Cloud Agent work while you sleep via Slack integration.
PostgreSQL remains the top choice for full-stack projects needing robust relational data, ACID compliance, and advanced query features (JSONB, CTEs, window functions). It outshines NoSQL for complex relationships and long-term consistency.
Prisma, a next-gen ORM, streamlines PostgreSQL development with:
Clear, type-safe schema and relations
Powerful middleware for query flows
Consistent, AI-friendly query results
Intuitive Prisma Studio and cloud tools
Agent Skills that give AI coding agents accurate, version-specific Prisma knowledge
For ultimate SQL control, Drizzle ORM is a strong lightweight alternative.
In 2026, PostgreSQL + Prisma remains the go-to stack for JavaScript/TypeScript teams prioritizing productivity, type safety, and maintainability.
Additional Tips:
"scripts": {
"postinstall": "prisma generate"
}
"scripts": {
"postinstall": "prisma generate"
}
Ensures Prisma Client is generated after every install
import { PrismaClient } from "@prisma/client";
import { z } from "zod";
const UserSchema = z.object({
name: z.string().min(2),
email: z.email(),
});
const prisma = new PrismaClient().$extends({
query: {
user: {
create({ args, query }) {
args.data = UserSchema.parse(args.data);
return query(args);
},
},
},
});
// Bad data never reaches your database
await prisma.user.create({
data: { name: "A", email: "bad" },
}); // × ZodError
import { PrismaClient } from "@prisma/client";
import { z } from "zod";
const UserSchema = z.object({
name: z.string().min(2),
email: z.email(),
});
const prisma = new PrismaClient().$extends({
query: {
user: {
create({ args, query }) {
args.data = UserSchema.parse(args.data);
return query(args);
},
},
},
});
// Bad data never reaches your database
await prisma.user.create({
data: { name: "A", email: "bad" },
}); // × ZodError
Using Prisma's $extends to validate data with Zod before it reaches the database
import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@prisma/client";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
// Simple stdout logging
const prisma = new PrismaClient({
adapter,
log: ["query", "info", "warn", "error"],
});
// Advanced event-based logging
const prisma = new PrismaClient({
adapter,
log: [
{ emit: "event", level: "query" },
{ emit: "stdout", level: "error" },
],
});
// Subscribe to query events
prisma.$on("query", (e) => {
console.log(`Query: ${e.query}`);
console.log(`Duration: ${e.duration}ms`);
console.log(`Params: ${e.params}`);
});
// Output:
// Query: SELECT * FROM "User" WHERE "id" = $1
// Duration: 3ms
// Params: [123]
import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@prisma/client";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
// Simple stdout logging
const prisma = new PrismaClient({
adapter,
log: ["query", "info", "warn", "error"],
});
// Advanced event-based logging
const prisma = new PrismaClient({
adapter,
log: [
{ emit: "event", level: "query" },
{ emit: "stdout", level: "error" },
],
});
// Subscribe to query events
prisma.$on("query", (e) => {
console.log(`Query: ${e.query}`);
console.log(`Duration: ${e.duration}ms`);
console.log(`Params: ${e.params}`);
});
// Output:
// Query: SELECT * FROM "User" WHERE "id" = $1
// Duration: 3ms
// Params: [123]
Setting up the pg driver adapter with stdout and event-based query logging
9. Mobile - React Native and Expo
React Native is now considered fully mature. As a leading cross-platform solution, it powers major apps such as Microsoft, Tesla, Discord, Coinbase, Bloomberg, and over 20% of the Top 100 Finance apps on the App Store.
The key advantage for me: staying fully in the React ecosystem for seamless code reuse, shared mental models, and easy web-to-mobile transitions. Alternatives like Flutter perform well in benchmarks, but none match React Native's JavaScript/TypeScript depth and community for React developers.
I pair it with Expo — the batteries-included "Next.js of mobile," officially recommended for most new projects. Expo streamlines config and packages; EAS handles cloud builds, submissions, and instant OTA updates (no App Store review needed). This enables fast iteration and hot reloads — perfect for a typical CRUD apps. Expo is my go-to unless heavy custom native modules are required.
For cross-platform desktop applications, Tauri is now my default choice. Built with Rust and leveraging the OS's native webview, Tauri apps can be as small as 600KB compared to Electron's ~150MB baseline. It provides stronger security isolation, a lower memory footprint, and near-native performance — while still letting you build the UI with familiar web technologies. For React developers, the mental model transfers directly, and the Rust backend offers powerful system-level capabilities without the overhead of bundling an entire Chromium instance.
That said, Electron remains a strong option when ecosystem maturity is the priority. It powers industry leaders like VS Code, Slack, Discord, Figma, and Notion. Its abundant documentation, battle-tested patterns, and seamless integration with existing web stacks make it a safe bet for complex applications where the broader plugin ecosystem and community support outweigh the resource trade-offs.
For macOS-first applications where native look and feel is paramount, React Native macOS by Microsoft delivers truly native UI components rather than web views. It's particularly well-suited for apps that need deep system integration or want to feel indistinguishable from native macOS apps, while still benefiting from the React paradigm and code sharing with iOS.
11. Orchestration - GitHub, Vercel, Linear, and Slack
This is my recommended stack for seamless development — tools that automate flows, sync teams, and accelerate from idea to deployment.
GitHub: The foundation for code and collaboration. I use repositories for version control, and GitHub Actions for automated workflows. PRs trigger everything else.
Vercel: Instant deployment. Every GitHub push creates preview URLs, edge-hosted production deploys go live in seconds, and zero-config setup keeps things frictionless. For different needs, I also reach for Railway when I need managed backends, databases, or cron jobs without touching a cloud console, and Fly.io for latency-sensitive workloads that benefit from multi-region edge compute.
Linear: Lightweight, keyboard-first issue tracking. It syncs bidirectionally with GitHub (branch → issue linking), organizes sprints cleanly, and feels faster than heavier alternatives.
Slack: The real-time glue. Bots notify on deployments (Vercel), PR reviews (GitHub), and issue updates (Linear), keeping discussions focused and reducing context-switching.
Together, they create a tight loop: plan in Linear → code in GitHub → preview/deploy on Vercel → discuss in Slack. Minimal overhead, maximum velocity.
12. Others
Balancing modernity with maturity, this stack prioritizes type safety, lightweight architecture, and headless design to build future-proof applications.
DX Essentials
Format and Lint: Biome is selected for its Rust-powered speed, zero-config philosophy, and unified formatting/linting that significantly outperforms traditional ESLint + Prettier setups.
Set up format on save with Biome
Set up format on save with Biome
"files": {
"includes": [
"!src/components/ui"
]
}
"files": {
"includes": [
"!src/components/ui"
]
}
"scripts": {
"lint": "biome check --write",
"build": "pnpm run lint && next build"
}
"scripts": {
"lint": "biome check --write",
"build": "pnpm run lint && next build"
}
Excluding shadcn/ui files from linting and applying automatic formatting on linting
Utility Belt: A curated set of essential utilities — es-toolkit for modern lodash-style helpers, date-fns (or Luxon) for date manipulation, AI SDK for LLM integrations (with React Native AI for mobile), @uidotdev/usehooks for battle-tested React hooks, ts-pattern for exhaustive, type-safe pattern matching, and uuid for unique identifiers.
Pre-commit Hooks: prek is a Rust-powered, drop-in replacement for pre-commit — faster, no Python dependency, with parallel execution and monorepo support built in.
UI Components
Table: TanStack Table is the go-to headless table solution, offering full type-safety, composability, and unmatched flexibility for any data grid requirements.
Form: TanStack Form (or React Hook Form) is chosen for their minimal re-renders, excellent performance, and best-in-class developer ergonomics in form management.
Editor: TipTap provides a modern, extensible, and fully headless rich-text editing experience built on ProseMirror, with strong TypeScript support and a thriving ecosystem. For React Native, TenTap brings the same TipTap/ProseMirror architecture to mobile with a typed, customizable API — enabling shared editor logic across web and native.
Chart: Recharts delivers composable, declarative, and lightweight charting components tailored for React, striking an ideal balance between simplicity and customization.
Integrated Backend Services
Authentication: Better Auth is a modern, framework-agnostic authentication library with strong TypeScript support and sensible defaults, positioned as a forward-looking alternative to legacy solutions.
auth.ts
export const auth = betterAuth({
// ... other config
advanced: {
cookiePrefix: "georgejor.com", // set a prefix for the cookie to avoid conflicts with other cookies, especially multiple localhosts applications require signing in separately
},
trustedOrigins: [
"https://georgejor.com",
"https://*.georgejor.com", // if you have subdomains for staging or development
"http://localhost:*",
],
})
export const auth = betterAuth({
// ... other config
advanced: {
cookiePrefix: "georgejor.com", // set a prefix for the cookie to avoid conflicts with other cookies, especially multiple localhosts applications require signing in separately
},
trustedOrigins: [
"https://georgejor.com",
"https://*.georgejor.com", // if you have subdomains for staging or development
"http://localhost:*",
],
})
You can skip the environment variable BETTER_AUTH_URL with trustedOrigins.
i18n: next-intl offers type-safe, framework-aware internationalization specifically optimized for Next.js App Router, ensuring seamless integration and excellent developer experience.
Email: React Email provides a modern, component-based approach to crafting cross-platform emails, paired with Resend for reliable, developer-friendly transactional email delivery.
File Upload and Storage: better-upload simplifies file uploads with built-in validation and progress tracking, paired with Cloudflare R2 for cost-effective, S3-compatible object storage with zero egress fees.
Observability & Scalability
APM: Sentry provides comprehensive error tracking, logging, performance monitoring, and session replay, with deep Next.js integration and mature ecosystem support.
Security: AI-assisted reviews paired with battle-tested open-source tools like Shannon are usually the right balance: you benefit from fast-moving community improvements, avoid reinventing the wheel, and still keep humans in the loop for judgment.
Caching: Redis remains the gold-standard for high-performance caching and session storage, with excellent client libraries and proven reliability at scale.
Async job: Workflow DevKit enables durable, type-safe background workflows with minimal boilerplate, making reliable asynchronous processing straightforward in Next.js applications.
Cron job: GitHub Actions with schedule triggers provide a simple, free, and infrastructure-less solution for recurring tasks — no separate cron service needed. Combined with a secure API route, it keeps scheduled jobs version-controlled, observable, and tightly integrated with your deployment pipeline.
Set up a GitHub Actions cron workflow that runs daily at midnight UTC
and calls a secured Next.js API route for database backup.
Use a shared CRON_SECRET for authentication.
Set up a GitHub Actions cron workflow that runs daily at midnight UTC
and calls a secured Next.js API route for database backup.
Use a shared CRON_SECRET for authentication.
# .github/workflows/cron-backup.yml
name: Daily Backup
on:
schedule:
# Runs daily at midnight UTC
- cron: "0 0 * * *"
workflow_dispatch: # Allow manual trigger from GitHub UI
jobs:
backup:
runs-on: ubuntu-latest
steps:
- name: Trigger backup cron
env:
APP_URL: ${{ secrets.APP_URL }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
run: |
if [ -z "$APP_URL" ]; then
echo "::error::APP_URL secret is not set. Add it in GitHub repo Settings > Secrets."
exit 1
fi
if [ -z "$CRON_SECRET" ]; then
echo "::error::CRON_SECRET secret is not set. Add it in GitHub repo Settings > Secrets."