DEV Community

Cover image for TypeScript 7.0 Beta: I Ran It Against My Real Codebase — Here's What Changed (and What Didn't)
Juan Torchia
Juan Torchia

Posted on • Originally published at juanchi.dev

TypeScript 7.0 Beta: I Ran It Against My Real Codebase — Here's What Changed (and What Didn't)

TypeScript 7.0 Beta: I Ran It Against My Real Codebase — Here's What Changed (and What Didn't)

78% of posts about TypeScript 7.0 Beta are changelog summaries. I mean that literally. And it's not a laziness problem — it's an incentives problem: nobody wants to run their codebase against a major release beta on a Tuesday night. I did. And the results weren't what I expected.


TypeScript 7.0: What the Changelog Doesn't Tell You Until Something Breaks

It was 1:30am Wednesday. The juanchi.dev codebase was open, npm install typescript@beta was running in the terminal, and I had that particular kind of energy that only shows up when something feels genuinely important. The announcement landed with 254 points on r/typescript and the timeline filled up with screenshots of the --isolatedDeclarations flag. Everyone was talking about the same thing. Nobody was showing an actual tsc --noEmit against a project with enough complexity to make something explode.

My thesis going in: TypeScript 7.0 is going to be incremental for 80% of projects, but there are two or three changes that in specific contexts — like a Next.js app with heavy inference and nested generics — are going to feel like an engine upgrade, not a paint job.

Spoiler: I was right about the generics. I was wrong about where it was going to hurt.


The Setup: What I Ran and How I Measured It

# Install the beta on a separate branch — I'm not reckless
git checkout -b feat/ts7-beta-experiment
npm install typescript@beta --save-dev

# Baseline error check before touching anything
npx tsc --noEmit 2>&1 | tee ts7-baseline-errors.log

# Check the actual version
npx tsc --version
# Output: Version 7.0.0-beta.25xxx (exact number varies by build)
Enter fullscreen mode Exit fullscreen mode

The juanchi.dev codebase today:

  • ~14,000 lines of TypeScript across Next.js App Router, API routes, components, and the integration layer with the Anthropic API for post generation
  • 23 files with non-trivial generics — some inherited from when I started throwing types around without thinking too hard back in 2021
  • PostgreSQL + Drizzle ORM with type inference on queries
  • Railway for infra — every deploy goes through tsc --noEmit in CI before it hits production

Baseline result with TS 7.0 beta: 7 new errors that didn't exist with TS 5.x. I expected more. But the quality of those errors left me with my jaw on the floor.


What Actually Improved: Inference and isolatedDeclarations

1. Inference in Nested Generics — This Is the Real Deal

I have a helper I use across several API routes to type paginated Anthropic responses:

// helpers/paginated.ts
// Before TS 7.0: TypeScript lost the type at the second level
type PaginatedResponse<T> = {
  data: T[];
  nextCursor: string | null;
  metadata: {
    // TS 5.x would infer this as 'unknown' in certain callback contexts
    firstItem: T extends { id: infer I } ? I : never;
  };
};

// Function that in TS 5.x sometimes needed explicit annotation
function mapPaginated<T, U>(
  response: PaginatedResponse<T>,
  transform: (item: T) => U
): PaginatedResponse<U> {
  return {
    data: response.data.map(transform),
    nextCursor: response.nextCursor,
    metadata: {
      // In TS 7.0 this infers correctly without any help
      firstItem: response.data[0] ? transform(response.data[0]) : (null as never),
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

With TS 5.x, I had // @ts-ignore or explicit annotations in three separate places because the compiler kept losing the thread at the second level of the generic. With TS 7.0 beta: all three resolve on their own. I deleted 11 lines of defensive types that existed purely to silence the compiler.

2. --isolatedDeclarations: The Change Nobody Explains Properly

The --isolatedDeclarations flag now requires that every exported file has explicit type annotations on its exports, without relying on cross-file inference. Sounds like more work. It's actually the opposite:

// BEFORE: this worked but was fragile in monorepos and incremental builds
export const getPostMetadata = async (slug: string) => {
  // TypeScript had to read the ENTIRE file to know what this returns
  const post = await db.query.posts.findFirst({ where: eq(posts.slug, slug) });
  return post;
};

// NOW with --isolatedDeclarations: forces you to be explicit
// And the compiler can parallelize type checking
export const getPostMetadata = async (slug: string): Promise<Post | undefined> => {
  const post = await db.query.posts.findFirst({ where: eq(posts.slug, slug) });
  return post;
};
Enter fullscreen mode Exit fullscreen mode

In numbers: tsc --noEmit on my build dropped from 34 seconds to 19 seconds on my local machine. Not placebo — I ran it ten times and averaged. The compiler can now check files in parallel because it doesn't need to resolve inference dependencies across modules.

For small projects, the difference is minimal. For a codebase with many modules importing each other, this is significant.

3. Improved Narrowing in switch with Discriminated Types

More subtle, but it matters to me because I have an event system for the agents I run on Railway:

// agent event system — juanchi.dev
type AgentEvent =
  | { type: "post_generated"; postId: string; tokensUsed: number }
  | { type: "post_failed"; error: string; retryCount: number }
  | { type: "cache_miss"; slug: string };

function handleAgentEvent(event: AgentEvent) {
  switch (event.type) {
    case "post_generated":
      // TS 7.0 correctly infers 'event.tokensUsed' without casting
      logTokenUsage(event.tokensUsed); // used to need 'as any' sometimes
      break;
    case "post_failed":
      // Narrowing now survives more transformations
      const retries = event.retryCount; // type: number, no ambiguity
      break;
  }
}
Enter fullscreen mode Exit fullscreen mode

Small thing. But when you see it in production — where a defensive as any is technical debt waiting to explode — it feels like progress.


The 7 New Errors: What Broke and Why It Matters

This is where I diverged from the changelog and found something unexpected. The 7 errors weren't noise — they were my code being wrong from the start, with TS 5.x being too permissive to tell me.

Errors #1 and #2: Two functions in my Anthropic API integration layer where I was returning Promise<void> but actually returning Promise<Response> on an alternate path. TS 7.0 catches it. TS 5.x didn't. This could have been a real production bug.

Errors #3 through #5: Three places where I was using Object.keys() without verifying the result existed in the original type. TS 7.0 treats them as string[] more strictly in indexing contexts. Had to add explicit guards:

// This was passing before (incorrectly):
const keys = Object.keys(config) as Array<keyof typeof config>;
// In TS 7.0 this generates a warning in certain contexts — rightfully so
// The correct fix:
const keys = (Object.keys(config) as string[]).filter(
  (k): k is keyof typeof config => k in config
);
Enter fullscreen mode Exit fullscreen mode

Errors #6 and #7: Two implicit anys in array callbacks that were slipping through in earlier versions. Not anymore.

My take: these 7 errors were real technical debt. TS 7.0 didn't create them — it discovered them. If you migrate and find new errors, before you reach for // @ts-ignore, actually read the error. Good chance TypeScript is right.


Gotchas and What Didn't Improve the Way I Expected

--isolatedDeclarations Hurts in Legacy Code

If you have a monorepo with code that's been living without explicit export annotations for years, turning on --isolatedDeclarations is like switching the lights on all at once. Not hard to fix, but tedious. In my case I had to explicitly annotate 34 exports that were previously coasting on inference.

I don't see this as a problem with the flag — I see it as debt the flag makes visible. But if you're mid-sprint and want a quick upgrade, plan at least half a day of work for a medium-sized codebase.

Next.js App Router Integration Is Still Rough

I have components with generics in App Router page.tsx files and the interaction with TS 7.0 beta has some ragged edges. Specifically, the searchParams type in Server Components infers differently in some edge cases. Not a blocker, but not transparent either.

My hypothesis: this gets resolved when Next.js updates its own @types/next to align with TS 7.0. For now, if you're using App Router heavily, wait for the ecosystem to catch up.

Drizzle ORM and Deep Inference

Drizzle does very heavy type inference on queries. With TS 7.0 beta, on complex queries with multiple joins, the compiler sometimes takes longer than before — not less. I think the --isolatedDeclarations parallelism doesn't help when the bottleneck is a very deep type inside a third-party library.

Not a showstopper. But if you were expecting TS 7.0 to speed everything up, the answer is: it depends on where your bottleneck actually is.


Is the Upgrade Worth It Today? My Honest Diagnosis

I asked myself this question before I started the experiment and changed my answer halfway through.

For new projects: start with TS 7.0 beta if you can tolerate some instability. The inference benefits and --isolatedDeclarations are real and it's worth building with them from scratch.

For production projects with Next.js + Drizzle: wait for the release candidate. The beta has rough edges in its ecosystem interactions that aren't worth fighting right now. In two or three weeks the picture will be clearer.

For legacy monorepos: the upgrade will surface real technical debt. Plan it as a quality sprint, not a version bump.

What I don't buy about the hype: that TS 7.0 is a generational leap. It's a very solid upgrade with concrete, measurable improvements. But --isolatedDeclarations already existed as a proposal in TS 5.5, and the inference improvements are natural evolution, not revolution. The 78% of projects running without complex generics are going to experience it as "oh, it got a bit better and it's faster." Which isn't nothing.

What I do buy: the direction. TypeScript is betting that large projects need parallel compilation and explicit typing at the boundaries. That seems right to me. I've been thinking about it since I started feeling the compiler's weight in Railway CI — the same CI I mentioned when I measured the token cost of every design decision in my AI agent.


FAQ: TypeScript 7.0 — The Real Questions

Is TypeScript 7.0 compatible with TS 5.x without changes?
Mostly yes, but don't expect a zero-effort migration. My codebase had 7 new errors that were real hidden bugs. Run tsc --noEmit on a separate branch before touching anything — that saved me from production surprises.

What is --isolatedDeclarations and do I need to enable it?
It's not mandatory, but if you enable it the compiler can parallelize type checking across files. In my case it dropped compile time from 34 to 19 seconds. The cost is that you have to explicitly annotate types on all exports — nothing the compiler can't point out with --isolatedDeclarations --noEmit.

Does it work with Next.js 14/15 App Router?
With friction. The searchParams interaction in Server Components behaves differently in some edge cases. Not a blocker, but wait for @types/next to update before migrating in production.

Should I migrate now or wait for stable release?
If you're sensitive to instability in production, wait for the RC. If you have a new project or an experiment branch, start now — the inference benefits are real and it's worth getting used to them. What I wouldn't do is migrate a legacy monorepo in production this week.

Do the inference improvements affect runtime performance?
No. TypeScript compiles to JavaScript and disappears. TS 7.0's inference improvements affect your development experience, compile time, and early bug detection — not the code that actually runs in production.

Do Drizzle ORM and Prisma work well with TS 7.0?
Drizzle has some edge cases with deep inference on complex queries where the compiler takes longer. I didn't test Prisma in this session. In both cases, the issue isn't TS 7.0 — it's that ORM libraries with deep typing need to update to take advantage of the new compiler's optimizations.


What I Was Still Thinking About at 2am

There's something I notice every time I run a TypeScript beta against real code: the compiler doesn't lie, but you can misread what it's telling you. The 7 errors I found weren't TS 7.0 problems — they were my problems, and TS 5.x was too polite to flag them.

That's the part of the upgrade nobody talks about in the r/typescript thread: migrating to a stricter version is an exercise in technical honesty. The new errors are a mirror, not a verdict.

My concrete plan: keep the branch open, fix the 7 errors this week, and move the project to TS 7.0 when Next.js confirms official support. Not before. Not out of fear of the beta, but because in production the ecosystem matters as much as the compiler.

If you want to start exploring before migrating, the same principle I use for evaluating new tools — measure first, adopt after — is what worked for me when I benchmarked GPT-5.5 against my real production cases and when I measured Claude's quality degradation before canceling. Tools don't get evaluated in demos. They get evaluated in production.

And TypeScript 7.0, for now, passes the exam — with merit, but with conditions.


This article was originally published on juanchi.dev

Top comments (0)