SW Engineering•2026.01.09
v9 버전일 때, 우리는 대규모 tRPC 라우터를 사용하는 개발자들로부터 타입 체커에 부정적인 영향을 미치기 시작했다는 보고를 받기 시작했습니다. 이는 tRPC 개발의 v9 단계 동안 엄청난 채택이 이루어지면서 겪게 된 새로운 경험이었습니다. 더 많은 개발자가 tRPC로 점점 더 큰 제품을 만들면서 몇 가지 균열이 나타나기 시작했습니다.tsc를 사용한 타입 체크가 느린지 여부tsc --generateTrace ./trace --incremental falsetrace/trace.json 파일이 생성됩니다. 이 파일을 트레이스 분석 앱(저는 Perfetto를 사용합니다)이나 chrome://tracing에서 열 수 있습니다.
src/pages/index.ts가 병목 지점임을 나타냅니다. Duration 필드 아래를 보면 332ms가 걸린 것을 볼 수 있습니다. 타입 체크에 쓰기에는 엄청난 시간입니다! 파란색 checkVariableDeclaration 바는 컴파일러가 대부분의 시간을 하나의 변수에 소비했음을 알려줍니다.
해당 바를 클릭하면 어떤 변수인지 알 수 있습니다:

pos 필드는 파일 텍스트 내 변수의 위치를 나타냅니다. src/pages/index.ts의 해당 위치로 가보니 범인은 utils = trpc.useContext()였습니다!import type { AppRouter } from '~/server/trpc';const trpc = createTRPCReact<AppRouter>();const Home: NextPage = () => { const { data } = trpc.r0.greeting.useQuery({ who: 'from tRPC' }); const utils = trpc.useContext(); utils.r49.greeting.invalidate();};export default Home;useContext와 쿼리 무효화(invalidation)만 보입니다. 겉보기에는 TypeScript 부하가 클 만한 것이 없으므로, 문제는 스택 더 깊은 곳에 있음을 시사합니다. 이 변수 뒤에 있는 타입을 살펴보겠습니다:type DecorateProcedure< TRouter extends AnyRouter, TProcedure extends Procedure<any>, TProcedure extends AnyQueryProcedure,> = { /** * @see https://tanstack.com/query/v4/docs/framework/react/guides/query-invalidation */ invalidate( input?: inferProcedureInput<TProcedure>, filters?: InvalidateQueryFilters, options?: InvalidateOptions, ): Promise<void>; // ... 그리고 다른 모든 React Query 유틸리티에 대해서도 마찬가지입니다.};export type DecoratedProcedureUtilsRecord<TRouter extends AnyRouter> = OmitNeverKeys<{ [TKey in keyof TRouter['_def']['record']]: TRouter['_def']['record'][TKey] extends LegacyV9ProcedureTag ? never : TRouter['_def']['record'][TKey] extends AnyRouter ? DecoratedProcedureUtilsRecord<TRouter['_def']['record'][TKey]> : TRouter['_def']['record'][TKey] extends AnyQueryProcedure ? DecorateProcedure<TRouter, TRouter['_def']['record'][TKey]> : never; }>;invalidateQueries와 같은 React Query 유틸리티로 "장식(decorate)"(메서드 추가)하는 재귀 타입 DecoratedProcedureUtilsRecord를 가지고 있습니다.v9 라우터를 지원하지만, v10 클라이언트는 v9 라우터의 프로시저를 호출할 수 없습니다. 그래서 각 프로시저에 대해 v9 프로시저인지 확인(extends LegacyV9ProcedureTag)하고, 그렇다면 제거합니다. 이 모든 과정은 TypeScript가 수행하기에 많은 작업입니다... 지연 평가(lazy evaluation)되지 않는다면 말이죠.utils.r49.greeting.invalidate만 사용하고 있으므로, TypeScript는 r49 속성(라우터), greeting 속성(프로시저), 그리고 마지막으로 해당 프로시저의 invalidate 함수만 풀면(unwrap) 됩니다. 그 코드에는 다른 타입이 필요하지 않으며, 모든 tRPC 프로시저에 대한 모든 React Query 유틸리티 메서드의 타입을 즉시 찾는 것은 불필요하게 TypeScript를 느리게 만듭니다. TypeScript는 객체의 속성에 대한 타입 평가를 직접 사용될 때까지 미루므로, 이론적으로 위의 타입은 지연 평가를 받아야 합니다... 그렇죠?OmitNeverKeys. 이 타입은 객체에서 값이 never인 키를 제거하는 유틸리티입니다. 이 부분이 v9 프로시저를 제거하여 인텔리센스(Intellisense)에 나타나지 않게 하는 부분입니다.never인지 확인하기 위해 모든 타입을 지금 평가하도록 강제했습니다.v10 API가 레거시 v9 라우터에 더 우아하게 적응할 수 있는 방법을 찾아야 합니다. 새로운 tRPC 프로젝트가 상호운용 모드(interop mode)의 저하된 TypeScript 성능으로 인해 고통받아서는 안 됩니다.v9 프로시저는 v10 프로시저와 다른 엔티티이므로 라이브러리 코드에서 동일한 공간을 공유해서는 안 됩니다. tRPC 서버 측에서 이는 단일 record 필드 대신 라우터의 다른 필드에 타입을 저장하도록 작업해야 함을 의미합니다(위의 DecoratedProcedureUtilsRecord 참조).v9 라우터가 v10 라우터로 변환될 때 자신의 프로시저를 legacy 필드에 주입하도록 변경했습니다.export type V10Router<TProcedureRecord> = { record: TProcedureRecord;};// v9 상호운용 라우터를 v10 라우터로 변환export type MigrateV9Router<TV9Router extends V9Router> = V10Router<{ [TKey in keyof TV9Router['procedures']]: MigrateProcedure< TV9Router['procedures'][TKey] > & LegacyV9ProcedureTag;}>;DecoratedProcedureUtilsRecord 타입을 기억하신다면, 여기서 LegacyV9ProcedureTag를 붙여 타입 수준에서 v9과 v10 프로시저를 구분하고 v9 프로시저가 v10 클라이언트에서 호출되지 않도록 강제했음을 알 수 있습니다.export type V10Router<TProcedureRecord> = { record: TProcedureRecord; // 기본적으로 레거시 프로시저 없음 legacy: {};};export type MigrateV9Router<TV9Router extends V9Router> = { // v9 라우터는 자신의 프로시저를 `legacy` 필드에 주입 legacy: { // v9 클라이언트는 최상위 수준에서 쿼리, 뮤테이션, 서브스크립션을 필터링해야 함 queries: MigrateProcedureRecord<TV9Router['queries']>; mutations: MigrateProcedureRecord<TV9Router['mutations']>; subscriptions: MigrateProcedureRecord<TV9Router['subscriptions']>; };} & V10Router</* 빈 객체, v9 라우터는 전달할 v10 프로시저가 없음 */ {}>;OmitNeverKeys를 제거할 수 있습니다. 라우터의 record 속성 타입은 모든 v10 프로시저를 포함하고, legacy 속성 타입은 모든 v9 프로시저를 포함하게 됩니다. 우리는 더 이상 TypeScript가 거대한 DecoratedProcedureUtilsRecord 타입을 완전히 평가하도록 강제하지 않습니다. 또한 LegacyV9ProcedureTag를 사용한 v9 프로시저 필터링도 제거할 수 있습니다.
아직 댓글이 없습니다.
첫 번째 댓글을 작성해보세요!
![[번역] Claude Code 공식 Plugin 추천 +α (속편)](https://res.cloudinary.com/zenn/image/upload/s--hpvcreCS--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:%25E7%25B6%259A%25E3%2583%25BBClaude%2520Code%25E5%2585%25AC%25E5%25BC%258FPlugin%25E3%2581%25AE%25E3%2581%2599%25E3%2581%2599%25E3%2582%2581%252B%25CE%25B1%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_37:%25E3%2581%25AF%25E3%2581%25B6%25E3%2581%25A1%25E3%2582%2593%2Cx_203%2Cy_121/g_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyL2Q4ZDhlMmQyZTAuanBlZw==%2Cr_max%2Cw_90%2Cx_87%2Cy_95/v1627283836/default/og-base-w1200-v2.png?_a=BACAGSGT)
[번역] Claude Code 공식 Plugin 추천 +α (속편)
Inkyu Oh • AI & ML-ops
![[번역] Claude Code에서 이용 제한에 빨리 걸리지 않게 하는 팁](https://res.cloudinary.com/zenn/image/upload/s--bpMrIbNq--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:Claude%2520Code%25E3%2581%25A7%25E3%2581%2599%25E3%2581%2590%25E5%2588%25B6%25E9%2599%2590%25E3%2581%25AB%25E3%2581%258B%25E3%2581%258B%25E3%2582%2589%25E3%2581%25AA%25E3%2581%2584%25E3%2582%2588%25E3%2581%2586%25E3%2581%25AB%25E3%2581%2599%25E3%2582%258B%25E3%2582%25B3%25E3%2583%2584%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_34:%25E5%2590%2589%25E5%25B2%25A1%25E8%25A3%2595%25E8%25B2%25B4%2Cx_220%2Cy_108/bo_3px_solid_rgb:d6e3ed%2Cg_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyLzUwM2RkNmMyMzkuanBlZw==%2Cr_20%2Cw_90%2Cx_92%2Cy_102/co_rgb:6e7b85%2Cg_south_west%2Cl_text:notosansjp-medium.otf_30:AUN%2520Tech%2520Blog%2Cx_220%2Cy_160/bo_4px_solid_white%2Cg_south_west%2Ch_50%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyLzFmNzI5YzVhODEuanBlZw==%2Cr_max%2Cw_50%2Cx_139%2Cy_84/v1627283836/default/og-base-w1200-v2.png?_a=BACAGSGT)
[번역] Claude Code에서 이용 제한에 빨리 걸리지 않게 하는 팁
Inkyu Oh • AI & ML-ops