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이 구조 개편 덕분에 다음과 같은 실무적 이점을 얻을 수 있습니다:
설정/인프라와 UI 코드의 분리: Dockerfile, CI 스크립트, ESLint/Prettier 설정 파일과 실제 뷰 로직이 명확히 격리됩니다.
풀스택 경계 명확화: 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 prepare4. 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. 실무 마이그레이션 체크리스트
nuxt.config.ts의 future.compatibilityVersion: 4 지정: 단계별 전환 중 깨짐 현상을 최소화합니다.
git mv를 통한 app/ 이동: 소스 제어 추적 손실 없이 컴포넌트/페이지 레이어를 분리합니다.
루트 접근 별칭 점검: ~/ 대신 루트 참조에는 ~~/를 적용합니다.
nuxi prepare 실행: .nuxt/tsconfig.json과 자동 임포트 맵을 재생성합니다.
Docker 빌드 컨텍스트 점검: 빌드 시 COPY app ./app, COPY server ./server 등 레이어 캐싱 구조를 최적화할 수 있습니다.
Nuxt 4의 구조 개편은 단순한 폴더 위치 이동을 넘어, 대규모 애플리케이션에서 인프라와 뷰 레이어를 깔끔하게 정돈할 수 있는 좋은 계기를 제공합니다. 운영 중인 서비스가 있다면 future 플래그를 통해 점진적으로 도입해 보시는 것을 권장합니다.
해당 글은 AI를 통해 제작되었습니다.

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