TypeScript 6.0 마이그레이션 — ts5to6로 baseUrl 제거, rootDir 고정, strict 에러 대응을 한 번에 처리하는 방법
솔직히 말하면, TypeScript 6.0 릴리스 노트를 처음 읽었을 때 눈을 세 번 비볐습니다. tsconfig 기본값이 한 번에 아홉 개나 바뀌다니. baseUrl은 deprecated 처리되고, strict는 기본값이 true로 뒤집히고, module은 commonjs에서 esnext로 올라서고. 그런데 막상 파고들어 보니, 가장 파괴적인 두 가지 변경은 도구가 처리해줬고 실제로 손 써야 하는 범위는 생각보다 훨씬 좁았습니다.
이 글은 TypeScript를 실무에 사용 중인 개발자를 대상으로, TypeScript 컴파일러 팀에서 module resolution을 담당하는 Andrew Branch가 만든 ts5to6 CLI 도구가 자동으로 처리해주는 것과 직접 챙겨야 하는 것을 구분해, 실제 마이그레이션 순서를 따라가며 살펴봅니다.
TypeScript 7.0 Beta가 2026년 4월에 이미 드롭됐고, 7.0부터는 Go 기반 컴파일러로 전환됩니다. 6.0 마이그레이션을 건너뛰면 7.0 전환 때 점진적 이전이 아닌 전면 재작업이 기다립니다. 타이밍이 지금 딱 맞습니다.
핵심 개념
TypeScript 6.0이 바꾼 기본값 9가지
2026년 3월 23일 공식 릴리스된 TypeScript 6.0은 JavaScript 기반 TypeScript 컴파일러의 마지막 버전입니다. 7.0부터는 Go로 완전히 재작성된 컴파일러가 투입됩니다. 팀이 오랫동안 미뤄왔던 기본값 정리를 이번에 한꺼번에 단행한 게 이번 버전의 가장 큰 특징입니다.
| 옵션 | TS 5.x 기본값 | TS 6.0 기본값 |
|---|---|---|
strict |
false |
true |
module |
commonjs |
esnext |
target |
es3 |
es2025 |
moduleResolution |
node10 |
bundler |
esModuleInterop |
false |
true |
rootDir |
입력 파일 공통 디렉터리 | tsconfig.json 위치 디렉터리 |
types |
모든 @types/* |
[] (빈 배열) |
noUncheckedSideEffectImports |
false |
true |
libReplacement |
true |
false |
저한테 제일 문제였던 건 strict, rootDir, baseUrl 세 가지였습니다. 나머지도 영향이 없지는 않아서, 표에 있는 noUncheckedSideEffectImports는 import './polyfill' 같은 사이드 이펙트 전용 임포트에 직접 영향을 미칩니다. polyfill이나 CSS 임포트를 쓰고 있다면 별도로 확인이 필요합니다. libReplacement: false는 커스텀 lib 파일을 교체해 쓰는 특수한 경우에만 영향이 있어서, 대부분의 프로젝트에서는 체감하기 어렵습니다.
baseUrl deprecated — 6.0에서 바로 동작이 멈추는 건 아닙니다
이 부분은 저도 처음에 오해했는데, 정확히 짚고 넘어가는 게 좋겠습니다. TypeScript 6.0에서 baseUrl은 공식 deprecated 처리되어 경고가 발생하지만, 기능 자체는 아직 동작합니다. 완전 제거는 TypeScript 7.0 예정입니다. "지금 당장 모든 임포트가 깨진다"가 아니라, "지금 경고가 뜨고 7.0에서 완전히 제거된다"는 타임라인입니다.
baseUrl이란? tsconfig에서"baseUrl": "./src"로 설정하면import { foo } from 'components/Button'처럼 절대 경로 스타일 임포트가 가능했습니다. 6.0부터 이 방식은 deprecated이고, 명시적인paths설정으로 전환해야 합니다.
그래도 지금 마이그레이션을 권장하는 이유는 단순합니다. 7.0으로 넘어갈 때 baseUrl이 남아 있으면 그게 더 큰 부담이 됩니다.
rootDir 기본값 변경이 빌드를 조용히 부수는 방식
이건 에러 메시지도 없이 배포 후에야 발견되는 유형의 문제라 더 성가십니다.
TS 5.x에서는 rootDir을 명시하지 않으면 입력 파일들의 공통 상위 디렉터리를 자동으로 추론했습니다. 소스가 src/ 아래 있으면 rootDir이 src/로 잡혀서 빌드 결과가 dist/index.js처럼 나왔죠. TS 6.0에서는 rootDir 기본값이 tsconfig.json 파일이 놓인 디렉터리로 바뀝니다.
# TS 5.x (rootDir 미지정): dist/index.js
# TS 6.0 (rootDir 미지정): dist/src/index.js ← 경로가 달라집니다배포 스크립트나 런타임 진입점이 dist/index.js를 참조하고 있다면 조용히 터집니다.
types: [] 기본값 — 전역 ambient types만 해당됩니다
@types/*란?@types/node,@types/react같이 JavaScript 라이브러리에 TypeScript 타입 정의를 제공하는 패키지들입니다.
여기서 중요한 구분이 있습니다. types: []로 바뀐다고 해서 @types/* 패키지 전체가 차단되는 건 아닙니다. 전역으로 자동 포함되던 ambient types만 비활성화됩니다. import type { Foo } from 'some-lib'처럼 직접 임포트하는 타입은 영향을 받지 않습니다.
실제로 영향받는 케이스는 process, Buffer 같은 Node.js 전역 타입입니다. 이전에는 @types/node가 설치만 돼 있으면 자동으로 전역에서 사용 가능했는데, 이제는 tsconfig에 "types": ["node"]를 명시해야 합니다.
실전 적용
단계 1: --fixBaseUrl로 baseUrl 제거 자동화
ts5to6는 npx로 바로 실행할 수 있습니다.
# 현재 디렉터리의 tsconfig.json에서 baseUrl 제거 및 paths 재작성
npx @andrewbranch/ts5to6 --fixBaseUrl .
# 커스텀 tsconfig 파일 지정
npx @andrewbranch/ts5to6 --fixBaseUrl ./tsconfig.app.json도구가 실제로 어떻게 변환하는지 보면 이해가 빠릅니다.
변환 전 (tsconfig.json):
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"components/*": ["components/*"],
"utils/*": ["utils/*"]
}
}
}변환 후 (tsconfig.json):
{
"compilerOptions": {
"paths": {
"components/*": ["./src/components/*"],
"utils/*": ["./src/utils/*"]
}
}
}baseUrl을 경로 접두사로 사용하고 있었다면 각 paths 엔트리에 ./src/가 자동으로 붙습니다. baseUrl만 설정하고 paths를 전혀 쓰지 않았던 경우엔 와일드카드 catch-all 엔트리도 자동으로 추가됩니다.
| 변환 항목 | 도구 처리 여부 |
|---|---|
baseUrl 필드 제거 |
자동 처리 |
paths 경로 앞에 baseUrl 값 붙이기 |
자동 처리 |
| catch-all paths 엔트리 추가 | 자동 처리 |
공유 base tsconfig 상속 구조의 paths 충돌 |
수동 대응 필요 |
모노레포 환경에서는 project references 체인과 extends 상속을 따라 하위 tsconfig들도 모두 일괄 처리됩니다. pnpm workspaces나 Turborepo 구조에서도 별도 설정 없이 동작합니다.
단계 2: --fixRootDir로 빌드 출력 경로 보존
# rootDir을 TS 5.x 추론 결과와 동일하게 명시적으로 고정
npx @andrewbranch/ts5to6 --fixRootDir .
# 모노레포에서 특정 패키지 tsconfig 지정
npx @andrewbranch/ts5to6 --fixRootDir ./packages/core/tsconfig.json도구는 기존 TS 5.x가 추론했을 rootDir 값을 계산해서 tsconfig에 명시적으로 삽입합니다. 빌드 출력 구조가 그대로 유지됩니다.
주의:
--fixBaseUrl과--fixRootDir은 동시에 실행할 수 없습니다. 각각 별도로 실행해야 하고, 순서는--fixBaseUrl먼저,--fixRootDir다음이 안전합니다. 각 단계마다git diff로 변경 내용을 눈으로 확인해두면 나중에 문제가 생겼을 때 원인을 추적하기 훨씬 쉽습니다.
단계 3: strict: true 기본값 대응
strict가 기본값 true가 되면서 기존에 타입 어노테이션 없이 작성하던 코드들이 오류를 뿜기 시작합니다. 실무에서 자주 맞닥뜨리는 패턴 두 가지입니다.
// src/utils.ts — TS 5.x에서 허용되던 패턴들
function greet(name) { // noImplicitAny — 파라미터 타입 없음
return `Hello ${name}`;
}
const val = maybeNull.toUpperCase(); // strictNullChecks — null 가능성 무시// src/utils.ts — TS 6.0 strict: true 대응
function greet(name: string) {
return `Hello ${name}`;
}
const val = maybeNull?.toUpperCase() ?? '';오류 수가 많아서 한 번에 다 고치기 어렵다면 임시 완충 전략을 쓸 수 있습니다.
// tsconfig.json
{
"compilerOptions": {
"ignoreDeprecations": "6.0",
"strict": false
}
}이렇게 설정하면 폐기 경고를 일시적으로 억제하고 기존 동작을 유지할 수 있습니다. 단, TypeScript 7.0에서는 ignoreDeprecations 자체가 지원되지 않으므로 근본 마이그레이션을 무기한 미룰 수는 없습니다.
단계 4: esModuleInterop 기본값 변경으로 달라지는 import
esModuleInterop이 기본값 true가 되면서 CommonJS 모듈의 import 방식이 바뀝니다.
// src/server.ts — TS 5.x 방식 (esModuleInterop: false 기본값)
import * as express from "express";// src/server.ts — TS 6.0 방식 (esModuleInterop: true 기본값)
import express from "express";이 변경은 Vite, esbuild, Rollup 기반 프로젝트와의 호환성을 높여줍니다. React의 경우 React 17 이상의 자동 JSX 트랜스폼을 사용하는 환경이라면 import React from "react" 방식이 안전하게 동작하지만, 그렇지 않은 환경에서는 moduleResolution과 번들러 설정을 함께 검토하는 것이 좋습니다. 레거시 CommonJS 프로젝트에서는 moduleResolution 변경과 맞물려 추가 확인이 필요한 경우가 있습니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
baseUrl·paths 자동 재작성 |
수백 개 파일을 수동으로 손볼 필요 없이 CLI 한 줄로 처리됩니다 |
| 모노레포 일괄 처리 | project references, extends 체인을 따라 하위 tsconfig 전체에 적용됩니다 |
strict: true 기본값 |
신규 프로젝트에서 별도 설정 없이 strictNullChecks, noImplicitAny가 활성화됩니다 |
| ESM 기본값 정합 | Vite, esbuild, Rollup 기반 프로젝트는 추가 설정 없이 최적 상태가 됩니다 |
이 중에서 실제로 체감이 가장 컸던 건 두 번째였습니다. 모노레포에서 tsconfig가 열두 개쯤 되는 프로젝트였는데, 도구 하나가 전부 처리해준다는 게 이 변경 작업에서 거의 유일한 구원이었습니다.
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
--fixBaseUrl·--fixRootDir 동시 실행 불가 |
두 옵션을 한 번에 쓸 수 없습니다 | 각각 순차 실행 후 빌드 검증 |
공유 base tsconfig paths 상속 문제 |
베이스에서 paths 정의, 자식에서 baseUrl 설정하는 구조는 자동 처리 안 됩니다 |
수동으로 경로 기준 재조정 |
| 레거시 CJS 프로젝트 복잡도 | module: commonjs 유지 시 moduleResolution·esModuleInterop 충돌 발생 가능 |
module, moduleResolution 명시적 고정 |
strict: false 누락 |
기존에 타입 검사 느슨하게 쓰던 프로젝트에서 오류 수십~수백 개 발생 가능 | "strict": false 명시 또는 점진적 수정 |
types: [] 기본값 |
전역 자동 포함되던 ambient types가 이제 포함되지 않습니다 | "types": ["node"] 등 명시적으로 지정 |
noUncheckedSideEffectImports 기본 활성화 |
import './polyfill' 같은 사이드 이펙트 임포트에 영향을 미칩니다 |
해당 임포트 파일의 타입 정의 확인 필요 |
ignoreDeprecations는 임시방편 |
TypeScript 7.0에서 해당 설정 자체가 작동하지 않습니다 | 최종적으로는 근본 마이그레이션 완료 필요 |
실무에서 가장 흔한 실수
저는 1번을 직접 경험했는데, 생각보다 많은 분들이 같은 순서로 실수하시는 것 같습니다.
ts5to6를 실행하기 전에 TypeScript를 6.0으로 올리는 것 — 저도 이걸 먼저 했다가 오류가 한꺼번에 쏟아져서 되돌렸습니다. 도구를 먼저 실행해서 tsconfig를 정리한 뒤 버전을 올리는 순서가 훨씬 안전합니다.types: []변경을 인지하지 못하고 Node.js 전역 타입이 사라진 걸 모듈 오류로 착각하는 것 —process,Buffer등이 갑자기 오류가 난다면"types": ["node"]누락을 먼저 확인해보는 게 좋습니다. 원인 찾는 데 생각보다 시간을 씁니다.- 공유 베이스 tsconfig가 있는 모노레포에서 자식 tsconfig만 확인하고 끝내는 것 — 베이스 tsconfig의
paths정의와 자식의baseUrl조합은ts5to6가 자동으로 처리하지 못하므로, 모노레포 구조라면 베이스 tsconfig도 별도로 검토해보는 것이 좋습니다.
마치며
도구가 80%를 처리하고, 나머지 20%가 실수를 만든다 — 순서를 지키는 것이 이 마이그레이션의 전부입니다.
지금 바로 시작해볼 수 있는 3단계를 정리했습니다.
-
tsconfig 현황 파악부터 — 프로젝트 루트에서 현재 설정을 확인해보면 영향 범위가 보입니다.
bash# 단일 프로젝트 grep -E '"baseUrl"|"rootDir"|"strict"' tsconfig.json # 모노레포 (재귀 탐색) find . -name 'tsconfig*.json' | xargs grep -l '"baseUrl"\|"rootDir"\|"strict"' -
ts5to6를 순차 실행 —npx @andrewbranch/ts5to6 --fixBaseUrl .후 빌드 검증, 이어서npx @andrewbranch/ts5to6 --fixRootDir .후 다시 빌드 검증하는 흐름이 안전합니다. 각 단계마다git diff로 변경 내용을 눈으로 확인해두면 문제 추적이 쉽습니다. -
strict오류 점진적 수정 — 한꺼번에 다 고치기 어렵다면"strict": false를 임시로 명시한 뒤, ts-strictify 같은 서드파티 도구를 활용해 파일 단위로 순차 적용하는 방식도 있습니다. 이때 파일 상단에// @ts-strict주석을 붙이는 방식은 공식 TypeScript 기능이 아닌 서드파티 도구의 컨벤션이므로, 사용 전 해당 도구 설치 여부를 확인해두는 것이 좋습니다.