Nuxt 4 전환 가이드: app 디렉터리 분리와 점진적 마이그레이션

안녕하세요 Lovefield입니다.

Nuxt 3가 등장한 이후 수많은 프로젝트가 성공적으로 구성되었지만, 규모가 커질수록 아쉬웠던 부분 중 하나가 바로 프로젝트 디렉터리 구조였습니다. 루트 레벨에 pages/, components/, composables/가 위치하면서 Docker 설정, CI/CD 파이프라인, 모노레포 설정, 그리고 Nitro 백엔드 코드(server/)가 한 공간에 뒤섞이는 문제가 빈번했습니다.

Nuxt 4에서는 이러한 구조적 한계를 해결하기 위해 app/ 디렉터리 기반 분리가 기본 규격으로 자리 잡았습니다. 이번 글에서는 Nuxt 4의 주요 변경점과 함께, 기존 Nuxt 3 프로젝트를 깨짐 없이 안전하게 전환할 수 있는 점진적 마이그레이션 전략을 정리합니다.

1. 무엇이 달라졌는가? 핵심 변경점

1-1. app/ 디렉터리로의 소스코드 응집

Nuxt 4의 가장 큰 외형적 변화는 프론트엔드 애플리케이션 코드가 모두 app/ 디렉터리 하위로 이동한다는 점입니다.

Plain Text

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

# Nuxt 3 기존 구조
my-nuxt-app/
├── components/
├── composables/
├── layouts/
├── pages/
├── server/
├── app.vue
├── nuxt.config.ts
├── package.json
└── Dockerfile

# Nuxt 4 권장 구조
my-nuxt-app/
├── app/                     # 👈 프론트엔드 애플리케이션 전용 레이어
│   ├── components/
│   ├── composables/
│   ├── layouts/
│   ├── pages/
│   ├── app.vue
│   ├── router.options.ts
│   └── error.vue
├── server/                  # 👈 Nitro 서버 엔진 및 API 전용 레이어
│   ├── api/
│   └── middleware/
├── public/
├── nuxt.config.ts
├── package.json
└── Dockerfile

이 구조 개편 덕분에 다음과 같은 실무적 이점을 얻을 수 있습니다:

  1. 설정/인프라와 UI 코드의 분리: Dockerfile, CI 스크립트, ESLint/Prettier 설정 파일과 실제 뷰 로직이 명확히 격리됩니다.

  2. 풀스택 경계 명확화: server/ 디렉터리는 Nitro 엔진 기반의 독립적인 서버 런타임으로 취급되며, app/은 클라이언트/SSR 렌더링 레이어로 분리되어 코드 유출 및 의존성 꼬임 방지가 수월해집니다.

2. 점진적 마이그레이션: compatibilityVersion 활용

기존 Nuxt 3 프로젝트에서 일거에 파일 위치를 옮기면 Git 히스토리 추적, 상대 경로 참조, 자동 임포트 충돌 등 다양한 사이드 이펙트가 발생합니다. Nuxt 팀은 이를 위해 future.compatibilityVersion 옵션을 제공합니다.

2-1. nuxt.config.ts 설정

먼저 디렉터리를 옮기지 않은 상태에서 Nuxt 3.x 최신 버전을 유지하며 compatibilityVersion: 4를 켭니다.

TypeScript

1

2

3

4

5

6

7

8

9

10

11

12

// nuxt.config.ts
export default defineNuxtConfig({
  // Nuxt 4 호환 모드 활성화
  future: {
    compatibilityVersion: 4,
  },

  typescript: {
    strict: true,
    typeCheck: true,
  },
});

이 플래그를 켜면 Nuxt는 내부 빌드 파이프라인에서 Nuxt 4의 동작 방식을 에뮬레이션합니다. 이때 app/ 디렉터리가 루트에 없으면 자동으로 기존 Nuxt 3 루트 경로를 폴백(Fallback)으로 참조하므로 빌드가 즉시 깨지지 않습니다.

3. 단계별 마이그레이션 절차

1단계: 프론트엔드 디렉터리 이동

server/, public/, node_modules/, 설정 파일들을 제외한 프론트 소스 파일들을 신규 app/ 디렉터리로 이동합니다.

Shell

1

2

mkdir app
git mv components composables layouts pages middleware plugins utils app.vue error.vue app/

> Tip: 파일 히스토리를 보존하려면 단순 드래그 앤 드롭 대신 git mv 명령어를 사용하는 것이 좋습니다.

2단계: 경로 별칭(Aliases) 해결

Nuxt 4에서는 기본 별칭의 해석 위치가 다음과 같이 정립되었습니다:

  • ~ 또는 @: 기본적으로 app/ 디렉터리를 가리킵니다.

  • ~~ 또는 @@: 프로젝트 루트(Root)를 가리킵니다.

만약 기존 코드에서 루트의 특정 자산이나 외부 설정을 ~/로 참조하고 있었다면, ~~/로 수정해야 합니다.

TypeScript

1

2

3

4

5

6

7

8

// ❌ 기존 루트 파일 참조 (Nuxt 4에서는 app/package.json을 찾아 실패할 수 있음)
import pkg from '~/package.json';

// ✅ 프로젝트 루트 참조 시 @@ 또는 ~~ 사용
import pkg from '~~/package.json';

// ✅ app/ 내부 컴포넌트나 모듈 참조 시 @ 또는 ~ 사용
import BaseButton from '@/components/common/BaseButton.vue';

3단계: tsconfig.json 확인 및 타입 재생성

디렉터리 이동 후 Nuxt의 자동 생성 타입 정의를 새로 고쳐주어야 IDE 및 빌더가 새 경로를 정상 인식합니다.

Shell

1

npx nuxi prepare

4. Nitro Server와의 분리 및 최적화

Nuxt 4에서는 server/ 디렉터리의 타입 번들링과 런타임이 한층 더 독립적으로 컴파일됩니다.

공통 타입 공유 시 주의점

프론트엔드(app/)와 백엔드(server/)가 DTO(Data Transfer Object) 인터페이스를 공유할 때, 기존에는 types/ 디렉터리에 두고 양쪽에서 무심코 임포트하곤 했습니다.

Nuxt 4 권장 패턴은 다음과 같습니다:

  • 순수 TypeScript type / interface: shared/types/와 같은 루트 디렉터리에 두고 양쪽에서 import type으로만 가져옵니다. 런타임 코드가 섞이지 않도록 주의합니다.

  • 유효성 검사 라이브러리(Zod, Valibot 등): 런타임 번들 크기 최적화를 위해 클라이언트와 서버에서 번들링되는 청크를 명확히 분리합니다.

TypeScript

1

2

3

4

5

6

// shared/types/user.ts
export interface UserDTO {
  id: string;
  nickname: string;
  role: 'admin' | 'member';
}

TypeScript

1

2

3

4

5

6

7

8

9

10

// server/api/user.get.ts
import type { UserDTO } from '~~/shared/types/user';

export default defineEventHandler((): UserDTO => {
  return {
    id: 'u-1001',
    nickname: 'Lovefield',
    role: 'admin',
  };
});

5. 실무 마이그레이션 체크리스트

  1. nuxt.config.ts의 future.compatibilityVersion: 4 지정: 단계별 전환 중 깨짐 현상을 최소화합니다.

  2. git mv를 통한 app/ 이동: 소스 제어 추적 손실 없이 컴포넌트/페이지 레이어를 분리합니다.

  3. 루트 접근 별칭 점검: ~/ 대신 루트 참조에는 ~~/를 적용합니다.

  4. nuxi prepare 실행: .nuxt/tsconfig.json과 자동 임포트 맵을 재생성합니다.

  5. Docker 빌드 컨텍스트 점검: 빌드 시 COPY app ./app, COPY server ./server 등 레이어 캐싱 구조를 최적화할 수 있습니다.

Nuxt 4의 구조 개편은 단순한 폴더 위치 이동을 넘어, 대규모 애플리케이션에서 인프라와 뷰 레이어를 깔끔하게 정돈할 수 있는 좋은 계기를 제공합니다. 운영 중인 서비스가 있다면 future 플래그를 통해 점진적으로 도입해 보시는 것을 권장합니다.

해당 글은 AI를 통해 제작되었습니다.

Lovefield

Web Front-End developer

하고싶은게 많고, 나만의 서비스를 만들고 싶은 변태스러운 개발자입니다.

컨테이너 취약점 제로를 향해: Chainguard(cgr.dev)와 Docker Hardened Images(dhi.io) 비교

Docker

v5.0