Skip to content

Typed $fetch route union causes TS2589/TS2321 (excessive type instantiation) on apps with many routes #4476

Description

@Killian-Aidalinfo

Environment

  • nitropack 2.13.4 (v2) — also reproduced on main (v3), same src/types/fetch/fetch.ts shape
  • TypeScript 5.9.x, and @typescript/native-preview (tsgo)
  • Real app: a Nuxt 4 project whose generated InternalApi has ~846 routes

Reproduction

Any project with a large generated InternalApi (hundreds of routes) that calls typed $fetch / useFetch / useAsyncData / event.$fetch with a string literal. On the real app it produces TS2589 (Type instantiation is excessively deep and possibly infinite) and, under tsgo, a flood of TS2321 (Excessive stack depth comparing types) on those call sites. A minimal repro is a Nitro app with a few hundred route handlers + one $fetch('/some/route').

Describe the bug

NitroFetchRequest is the union of all route keys and is used as the generic constraint of $Fetch (R extends NitroFetchRequest). At every typed call site the URL literal is re-scored against that union via MatchedRoutes / AvailableRouterMethod / TypedInternalResponse. With hundreds of routes this instantiates enormously — TypeScript's tsc sits just under its 5M instantiation valve, and Go-native tsgo (stricter, different stable type ordering) tips over it → TS2589/TS2321. It is not an app bug: the generated code is correct; the cost is intrinsic to scoring the union per call.

This is the same family as the TypeScript compiler issues below, where the maintainers consider the behavior intended (stable type ordering) and close the issues as NOT_PLANNED, pointing to the libraries to make their types less pathological (e.g. TypeBox fixed its own types in #929):

So Nitro is the right place to offer a mitigation.

Proposed fix (PR attached)

An opt-in, non-breaking escape hatch: a NitroFetchConfig interface that, when a project sets { flatRequest: true }, resolves NitroFetchRequest to string and stops the per-call-site route-union scoring.

declare module "nitropack" {
  interface NitroFetchConfig { flatRequest: true }
}

Default behavior is unchanged (the union) — existing users keep full autocomplete + typing. When opted in, response typing is preserved (MatchedRoutes<R> still resolves the return from the URL literal); only request-key autocomplete is traded away. Measured on the real app: it removes the entire TS2589/TS2321 class on both tsc and tsgo with zero new errors.

PR: opt-in flat NitroFetchRequest (targets main).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions