For several years I resisted the idea of full-stack TypeScript. I came from architectures with Go or Python on the backend and TypeScript only on the frontend, and the idea of unifying felt like a convenience concession. Over the last two years I’ve worked on several projects where the stack is entirely TypeScript, and my opinions are more nuanced.

The point of this post isn’t to convince anyone to adopt full-stack TypeScript, or to dissuade them. It is to offer an honest review of what I’ve seen work, what’s still a problem, and in what kind of project the choice makes sense.

Key takeaways

  • The shared data model and tools like tRPC eliminate the code-generation layer between frontend and backend.

  • Modern frameworks (Next.js App Router, Remix, SvelteKit) have blurred the client-server line in a way that simplifies medium-sized projects.

  • The testing ecosystem remains fragmented; Vitest is the best starting point for new projects.

  • For CPU-bound services or strict-latency requirements, Go or Rust remain more appropriate than Node.

  • The choice makes sense for small-to-medium product projects with small teams and fast iteration.

The good

The most immediate advantage is the shared data model. When the same User or Product type lives on frontend and backend, and you don’t have to write it twice or sync it with an external generator, cognitive cost drops and misalignment errors vanish.

Tools like tRPC have turned this into much more than shared types. With tRPC, a procedure in the backend is automatically a typed function on the frontend, with full autocompletion, serialization validation, and no external code-generation layer, as the official tRPC documentation[1] covers well.

The second real advantage is the framework ecosystem that assumes full-stack from the start. Next.js with App Router, Remix (now merged with React Router), SvelteKit, Nuxt: all designed to make server functions consumable from the client.

Third, typing quality has improved greatly with TypeScript 5.x: stronger inference, more expressive conditional types, const types. I covered concrete changes like inferred type predicates in TypeScript 5.5, announced on the official TypeScript blog[2]. The type derived from a Zod schema is often more expressive than what you’d write by hand.

The meh

The areas where the balance is more ambiguous:

  • Node.js backend performance: reasonable for IO-bound services, but not brilliant for CPU-bound or strict predictable-latency requirements. Bun and Deno have put healthy pressure on the ecosystem, but most real projects stay on Node for npm compatibility and enterprise support.

  • Dependency management: npm and pnpm have stabilized, but 1,200 packages in node_modules for a modest project is still normal. Each supply-chain attack on popular packages reminds you this is a big surface; npm audit (see the official docs[3]) helps, but doesn’t remove it.

  • Frontend bundle size: with React Server Components and better tree-shaking it has improved, but a project bloats without anyone noticing.

The bad

Some things remain frankly problematic:

  • The testing ecosystem is fragmented. Vitest[4] has stabilized as a serious Jest alternative and is clearly better for new projects, but migrating large projects remains painful. No mocking library has become a clear standard.

  • Frontend-backend friction in truly large services reappears. When the project grows enough, what looked like one codebase splits into two or three, and the benefit of shared types fades.

  • Third-party library TypeScript experience remains uneven. Some libraries are excellently typed; others publish incomplete or incorrect types. Fixing a bad definition means forking, writing local declarations, or living with any.

  • Compilation still hurts. A large TypeScript project compiles slower than its Go or Rust equivalent. TypeScript 5.x improvements (project references[5], incremental) have eased but not eliminated the problem.

When it makes sense

  • Good choice: small-to-medium product projects with small teams, fast iteration, and frontend-backend proximity value. B2B SaaS, internal dashboards, admin tools, early-stage products.

  • Less clear choice: systems with heterogeneous components, heavy backend work, or multiple independent teams.

  • Bad choice: when the real requirement is sustained high performance, low memory, or constrained deployment environments.

What’s coming

I expect consolidation rather than revolution. Bun will keep pressuring Node. Frameworks like Next.js will keep blurring the client/server line. Automatic typing from database schemas (à la Drizzle ORM) will spread.

Full-stack TypeScript has earned a legitimate slot and will probably keep it for years. It’s not the universal solution, but for a real segment of the market it’s a reasonable choice that, well implemented, delivers good results.

This article is also available in Spanish: Full-stack TypeScript en 2025: lo bueno, lo regular, lo malo.

Frequently asked questions

Do I need OpenAPI or Protobuf code generation to share types between frontend and backend in TypeScript?

No. When the same User or Product type lives on frontend and backend you don't have to write it twice or sync it with an external generator. Tools like tRPC go further: a procedure in the backend is automatically a typed function on the frontend, with full autocompletion and serialization validation, with no external code-generation layer. A Zod schema also gives you a type that is often more expressive than what you would write by hand.

Is Node.js enough for a CPU-heavy backend?

For IO-bound services its performance is reasonable, but it is not brilliant for CPU-bound services or strict predictable-latency requirements; there Go or Rust remain more appropriate. Bun and Deno have put healthy pressure on the ecosystem, but most real projects stay on Node for npm compatibility and enterprise support. If the real requirement is sustained high performance, low memory, or constrained deployment environments, full-stack TypeScript is a bad choice.

Which testing tool should I pick for a new TypeScript project?

Vitest: it has stabilized as a serious Jest alternative and is clearly better for new projects. The pain lies in migrating large projects, which remains hard, and in mocking libraries, which have no clear standard. The testing ecosystem is one of the frankly problematic areas of the stack, alongside incomplete or incorrect types in some third-party libraries. Slow compilation of large projects also weighs, eased but not eliminated by project references and incremental builds in TypeScript 5.x.

Sources

  1. official tRPC documentation
  2. official TypeScript blog
  3. official docs
  4. Vitest
  5. project references