홈

© 2026 Ki Chang. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

소개JSON Formatter개인정보처리방침이용약관

© 2026 Ki Chang. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

소개|JSON Formatter|개인정보처리방침|이용약관

TypeScript verbatimModuleSyntax 마이그레이션 실전 가이드

정기창·2026년 1월 11일

왜 verbatimModuleSyntax인가

TypeScript로 프로젝트를 진행하다 보면 한 가지 애매한 지점을 만나게 됩니다. import 구문만 보고는 그게 타입인지 값인지 알 수 없다는 것입니다.

import { useState, useEffect, ReactNode } from 'react';

이 코드에서 useState와 useEffect는 실제로 사용되는 함수(값)이고, ReactNode는 타입입니다. 하지만 구문만 봐서는 구분이 안 됩니다. 컴파일 후에야 타입은 사라지고 값만 남습니다.

TypeScript 5.0에서 도입된 verbatimModuleSyntax는 이 문제를 해결합니다. 타입 전용 import에는 반드시 type 키워드를 붙이도록 강제합니다.

import { useState, useEffect } from 'react';
import type { ReactNode } from 'react';

이제 코드만 봐도 무엇이 타입이고 무엇이 값인지 명확합니다.

기대 효과

이 옵션을 적용하면 몇 가지 이점이 있습니다.

  • 타입 안전성 향상: 타입과 값의 구분이 명확해집니다
  • 번들 최적화: 번들러가 타입 전용 import를 확실히 제거할 수 있습니다
  • 코드 명확성: import 구문만 보고도 타입인지 값인지 파악할 수 있습니다
  • 미래 호환성: TypeScript의 권장 방향과 일치합니다

특히 번들 최적화 측면에서 이점이 큽니다. 번들러가 타입을 확실히 제거할 수 있어, 타입인 줄 알고 제거했는데 실제로는 값이었다면 발생할 수 있는 런타임 에러를 방지할 수 있습니다.

마이그레이션 현황 분석

이 프로젝트의 모노레포에 verbatimModuleSyntax를 적용하기로 했습니다. 먼저 각 패키지별로 호환성 테스트를 진행했습니다.

패키지 오류 수 파일 수 상태
shared-types 0 0 즉시 적용 가능
admin 15 11 수정 필요
web 17 10 수정 필요
blog-mcp 1 1 수정 필요
합계 33 22 -

shared-types 패키지는 이미 type 키워드를 사용한 export 패턴을 적용하고 있어서 즉시 적용이 가능했습니다. 나머지 패키지들은 수정이 필요했습니다.

backend는 왜 제외했나

위 목록에서 backend 패키지가 빠져 있습니다. NestJS는 module: "commonjs"를 사용하는데, verbatimModuleSyntax는 CommonJS와 호환되지 않습니다.

NestJS 공식 입장도 CommonJS가 표준이고, ESM으로 전환하려면 대규모 코드 수정이 필요합니다. 마이그레이션 비용 대비 효과가 크지 않아 backend는 현 상태를 유지하기로 했습니다.

수정 패턴

실제 마이그레이션에서 사용한 패턴들을 정리해보겠습니다.

패턴 1: 타입 전용 import 분리

가장 기본적인 패턴입니다. 타입만 import하는 경우 import type을 사용합니다.

// 변경 전
import { Metadata } from 'next';

// 변경 후
import type { Metadata } from 'next';

패턴 2: inline type 키워드

값과 타입을 함께 import할 때는 inline type 키워드가 편리합니다.

// 변경 전
import { fetchBlogPosts, BlogPost } from '@/lib/api';

// 변경 후
import { fetchBlogPosts, type BlogPost } from '@/lib/api';

이 방식은 import 구문을 분리하지 않아도 되어서 코드를 더 깔끔하게 유지할 수 있습니다.

패턴 3: React 타입

React에서 자주 쓰는 타입들도 마찬가지입니다.

// 변경 전
import { HTMLAttributes, ButtonHTMLAttributes } from 'react';

// 변경 후
import type { HTMLAttributes, ButtonHTMLAttributes } from 'react';

패턴 4: re-export

타입을 re-export할 때도 type 키워드가 필요합니다.

// 변경 전 - 오류 발생
export { SomeType } from './types';

// 변경 후
export type { SomeType } from './types';

실제 마이그레이션 과정

패키지별로 순차적으로 진행했습니다. 의존성이 적은 패키지부터 시작하는 것이 안전합니다.

Phase 1: shared-types

이미 타입 키워드를 사용하고 있어서 tsconfig.json에 옵션만 추가하면 됐습니다.

{
  "compilerOptions": {
    "verbatimModuleSyntax": true
  }
}

Phase 2: blog-mcp

수정이 필요한 파일은 1개뿐이었습니다. AxiosInstance 타입에 type 키워드를 추가했습니다.

// src/api-client.ts
import axios, { type AxiosInstance } from 'axios';

Phase 3: admin

11개 파일, 15개 오류를 수정했습니다. 주로 React 타입들이었습니다.

// 예: src/components/styled/Button.tsx
import type { ButtonHTMLAttributes } from 'react';

// 예: src/components/ErrorBoundary.tsx
import type { ReactNode } from 'react';

isolatedModules: true는 제거했습니다. verbatimModuleSyntax가 상위 호환 옵션이므로 둘 다 설정하면 중복입니다.

Phase 4: web

10개 파일, 17개 오류를 수정했습니다. Next.js의 Metadata 타입과 커스텀 타입들이 대부분이었습니다.

// app/[locale]/page.tsx
import { fetchBlogPosts, type BlogPost } from '@/lib/api';
import {
  generateAlternates,
  getOgLocale,
  getAlternateOgLocales,
  generateWebSiteJsonLd,
  serializeJsonLd,
  type SupportedLocale,
} from '@/lib/seo';

// app/sitemap.ts
import { type MetadataRoute } from 'next';

isolatedModules와의 관계

기존에 isolatedModules: true를 사용하고 있었다면, verbatimModuleSyntax로 대체할 수 있습니다. verbatimModuleSyntax가 더 엄격하고 정확한 상위 호환 옵션입니다.

둘의 차이점을 설명하자면, isolatedModules는 "이 import가 타입인지 값인지 모르겠으니 조심하라"는 경고 수준이고, verbatimModuleSyntax는 "명확하게 표시하라"는 강제 수준입니다.

주의사항

값과 타입이 같은 이름인 경우

React의 FC처럼 값이자 타입인 경우가 있습니다. 이럴 때는 사용 목적에 맞게 import하면 됩니다.

// 값으로 사용할 때
import { FC } from 'react';

// 타입으로만 사용할 때
import type { FC } from 'react';

롤백 계획

만약 문제가 발생하면 다음과 같이 롤백할 수 있습니다.

  1. verbatimModuleSyntax: true 제거
  2. isolatedModules: true 복원 (admin, web)
  3. 타입 import 수정은 유지해도 됨 (하위 호환)

타입에 type 키워드를 붙인 것 자체는 verbatimModuleSyntax 없이도 유효한 문법이므로, 설정을 롤백해도 코드 수정은 유지할 수 있습니다.

결과

총 22개 파일을 수정하고 4개 패키지에 verbatimModuleSyntax를 적용했습니다. 전체 빌드도 문제없이 통과했습니다.

실제 작업량은 예상보다 적었습니다. 대부분 기계적인 수정이고, TypeScript 컴파일러가 정확히 어디를 고쳐야 하는지 알려주기 때문입니다. 한 번 적용해두면 앞으로 타입과 값의 혼동에서 오는 실수를 방지할 수 있어서 장기적으로 이득입니다.

새 프로젝트를 시작한다면 처음부터 verbatimModuleSyntax: true를 설정하는 것을 권장합니다. 기존 프로젝트라도 ESM을 사용한다면 충분히 마이그레이션할 가치가 있습니다.

TypeScriptverbatimModuleSyntax모노레포마이그레이션

관련 글

Prettier + husky + lint-staged로 팀 코드 스타일 자동화하기

코드 스타일 논쟁을 없애고 git commit 시 자동으로 포매팅되는 환경을 구축한 경험. Prettier 설정부터 husky + lint-staged 연동, git-blame-ignore-revs까지 실제 적용 과정을 정리했습니다.

관련도 86%

NestJS에서 Drizzle ORM을 선택한 이유: TypeORM, Prisma와의 비교

새로운 SaaS 모듈에 MySQL을 도입하면서 TypeORM, Prisma, Drizzle ORM을 비교했습니다. 각 ORM의 장단점과 Drizzle을 선택한 이유를 실제 코드 예시와 함께 정리했습니다.

관련도 71%

소규모 서비스 개발: MSA 대신 모놀리식 아키텍처를 선택하는 것이 합리적일 수 있다는 생각

소규모 서비스 개발 시 MSA 대신 모놀리식 아키텍처가 합리적일 수 있습니다. 낮은 트래픽과 간단한 기능, 효율적인 비용 관리를 위한 현명한 선택 이유를 알아봅니다.

관련도 70%