George Jor

My Go-To Tech Stack for Productivity

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.

Next.js, as a recommended official framework, gives you the whole package: smart build system, file-based routing, full-stack rendering, Backend for Frontend (BFF), and more.

Sure, you can build a React app from scratch with Vite + React Router, but using Next.js lets you focus on actual business logic instead of messing with boilerplate forever.

If you're curious, TanStack Start or Hono (with JSX) is another cool option worth checking out.


2. Styling - Tailwind CSS and shadcn/ui

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.

Curious about this shift? Check Modern Web Paradigms for more!


3. Type-Safe API - oRPC and Zod

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. 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:

  • Built-in Visual Testing
  • Official Vitest integration/add-on
  • End-to-end testing support
  • Interaction testing powered by React Testing Library

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.


6. IDE - AI-Assisted Coding with Cursor

I've been heavily using Cursor (best with Claude 4.6 Opus). While fully autonomous coding may eventually render maintainability obsolete, for the next five years, human-in-the-loop remains critical.

AI is a game-changer, but not a silver bullet. Limitations persist:

  • Context is King: Vague prompts yield vague results. Architect use cases upfront.
  • Atomicity: Break complex tasks into single-purpose instructions to avoid hallucinations.
  • Accountability: AI executes; humans judge. You must own the code review.

My Playbook:

  • Context-First: Use AGENTS.md and SKILL.md for reusable context and specialized capabilities.
  • Prototype-First: Use v0.dev for UI/logic prototyping before integration.
  • Spec-First: TDD lives on. Write tests/specs first to narrow the AI's scope.
  • Modes matter: Use Plan to verify direction, Ask to challenge assumptions, and Debug for visual debugging.
  • Power Features: Leverage @Mentions for docs, custom commands in .cursor/commands, and Browser mode for E2E debugging.
  • Async Workflow: Let the Cloud Agent work while you sleep via Slack integration.

While strong alternatives like Codex, Antigravity and Claude Code exist, Cursor remains the unrivaled AI-first IDE. It delivers the most powerful experience while retaining the familiar mental models developers rely on.


7. Database – PostgreSQL & Prisma

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
"scripts": {
"postinstall": "prisma generate"
}
"scripts": {
"postinstall": "prisma generate"
}
Deployment Friendly Postinstall Script

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.


8. 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.

Useful tools for React Native development:

Expo Skills
A collection of AI agent skills for working with Expo projects and Expo Application Services.
Voltra - Live Activities & Widgets
Voltra lets React Native developers build native Live Activities and widgets on iOS and Android using React components — no Swift or Kotlin required.

9. Desktop - Electron

For cross-platform desktop applications, Electron remains my default choice. It powers industry leaders like VS Code, Slack, Discord, Figma, and Notion. The ecosystem maturity is unmatched: abundant documentation, battle-tested patterns, and seamless integration with existing web stacks. For React developers, the mental model transfers directly, and you can share code between web and desktop effortlessly.

That said, Electron's Chromium-based architecture comes with trade-offs in bundle size and memory usage. For performance-critical or resource-constrained scenarios, Tauri offers a compelling alternative. 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 also provides stronger security isolation and lower memory footprint, making it ideal for lightweight utilities or apps where minimal resource usage matters.

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.


10. 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.


11. 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, @uidotdev/usehooks for battle-tested React hooks, and uuid for unique identifiers.

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.
  • Chart: Recharts delivers composable, declarative, and lightweight charting components tailored for React, striking an ideal balance between simplicity and customization.
  • Drag and Drop: dnd-kit is a lightweight, performant, and accessible drag-and-drop toolkit for React, offering fine-grained control with a modern hooks-based API.

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
trustedOrigins: [
"https://georgejor.com",
"https://*.georgejor.com", // if you have subdomains for staging or development
"http://localhost:*",
],
})
export const auth = betterAuth({
// ... other config
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.
  • Realtime: Upstash offers serverless Redis and Kafka solutions with per-request pricing, making realtime features cost-effective and effortless to implement.
  • 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.
  • 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."
exit 1
fi

response=$(curl -sL --location-trusted -w "\n%{http_code}" \
-H "Authorization: Bearer $CRON_SECRET" \
"$APP_URL/api/cron/backup")

http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')

echo "Response body: $body"

if [ "$http_code" != "200" ]; then
echo "::error::Backup failed with HTTP $http_code"
exit 1
fi

echo "Backup completed successfully (HTTP $http_code)"
# .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."
exit 1
fi

response=$(curl -sL --location-trusted -w "\n%{http_code}" \
-H "Authorization: Bearer $CRON_SECRET" \
"$APP_URL/api/cron/backup")

http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')

echo "Response body: $body"

if [ "$http_code" != "200" ]; then
echo "::error::Backup failed with HTTP $http_code"
exit 1
fi

echo "Backup completed successfully (HTTP $http_code)"
// app/api/cron/db-backup/route.ts
import { NextResponse } from "next/server";

export async function POST(request: Request) {
const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

// Run database backup logic here
// e.g., pg_dump to S3/R2, prune old backups, notify on failure

return NextResponse.json({ success: true });
}
// app/api/cron/db-backup/route.ts
import { NextResponse } from "next/server";

export async function POST(request: Request) {
const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

// Run database backup logic here
// e.g., pg_dump to S3/R2, prune old backups, notify on failure

return NextResponse.json({ success: true });
}
# Generate with: openssl rand -base64 32
CRON_SECRET="your-secret-token-here"
APP_URL="https://your-app.com"
# Generate with: openssl rand -base64 32
CRON_SECRET="your-secret-token-here"
APP_URL="https://your-app.com"
Setting up a daily database backup cron with GitHub Actions and a secured Next.js API route