2026년 Nuxt 4 완벽 가이드: 새로운 디렉터리 구조와 Nuxt 3 마이그레이션 전략
Nuxt 4에서 도입된 app/ 디렉터리 구조, 싱글톤 데이터 패칭 레이어, shallow reactivity, TypeScript 컨텍스트 분리를 코드 예제와 함께 상세히 분석합니다.

Nuxt 4는 애플리케이션 코드와 설정 파일을 명확하게 분리하는 새로운 디렉터리 구조를 도입했습니다. 2025년 7월에 출시되어 2026년 4월 기준 버전 4.4에 도달한 이 메이저 업데이트는, 싱글톤 데이터 패칭 레이어, shallow reactivity 기본 적용, TypeScript 컨텍스트 분할 등 개발 경험을 크게 개선하는 기능들을 포함하고 있습니다. Nuxt 2에서 Nuxt 3으로의 전환과 비교하면, 이번 업그레이드 경로는 훨씬 매끄럽습니다.
Nuxt 팀은 Codemod와 협력하여 대부분의 마이그레이션 단계를 자동화했습니다. npx codemod@latest nuxt/4/migration-recipe를 실행하면 디렉터리 재구성, 데이터 패칭 업데이트, 더 이상 사용되지 않는 API 교체가 자동으로 처리됩니다.
Nuxt 4의 app/ 디렉터리 구조
Nuxt 4에서는 모든 애플리케이션 소스 코드가 기본적으로 app/ 디렉터리에 배치됩니다. 이 변경은 실제 개발 환경의 문제를 해결하기 위한 것입니다. Linux와 Windows의 파일 감시자(file watcher)는 애플리케이션 코드가 node_modules/, .git/, 설정 파일과 혼재되지 않고 전용 하위 디렉터리에 저장될 때 성능이 크게 향상됩니다.
새로운 디렉터리 레이아웃은 다음과 같습니다.
my-nuxt-app/
├─ app/
│ ├─ assets/
│ ├─ components/
│ ├─ composables/
│ ├─ layouts/
│ ├─ middleware/
│ ├─ pages/
│ ├─ plugins/
│ ├─ utils/
│ ├─ app.vue
│ ├─ app.config.ts
│ └─ error.vue
├─ content/
├─ public/
├─ shared/ # 신규: app과 server 간 공유 코드
├─ server/
└─ nuxt.config.tsshared/ 디렉터리는 주목할 만한 추가 사항입니다. shared/에 배치된 composable이나 유틸리티는 Vue 앱과 Nitro 서버 양쪽에서 자동 임포트됩니다. 유효성 검사 스키마, 타입 정의, 유틸리티 함수를 컨텍스트 간에 공유할 때 수동으로 import 문을 작성할 필요가 없어집니다.
Nuxt 3에서 Nuxt 4로의 단계별 마이그레이션
업그레이드 과정은 하나의 명령어에서 시작합니다. Nuxt는 기존의 플랫 구조를 감지하고 변경 없이 계속 동작하므로, 점진적인 마이그레이션이 가능합니다.
# Nuxt 업그레이드 및 의존성 중복 제거
npx nuxt upgrade --dedupe패키지 업그레이드 후, 애플리케이션 파일을 app/ 디렉터리로 이동합니다.
# 디렉터리 재구성 자동화
npx codemod@latest nuxt/4/file-structure이 codemod는 assets/, components/, composables/, layouts/, middleware/, pages/, plugins/, utils/, app.vue, error.vue, app.config.ts를 app/으로 이동합니다. 루트에 남아야 하는 파일들(nuxt.config.ts, server/, public/, content/)은 그대로 유지됩니다.
디렉터리 재구성을 즉시 진행하지 않으려는 경우, 소스 디렉터리를 명시적으로 설정할 수 있습니다.
export default defineNuxtConfig({
srcDir: '.',
dir: { app: 'app' },
})이 설정을 통해 Nuxt 4가 프로젝트 루트에서 파일을 확인하게 되어, Nuxt 3과 동일한 동작을 유지합니다.
싱글톤 데이터 패칭 레이어와 리액티브 키
Nuxt 4에서는 useAsyncData와 useFetch의 데이터 관리 방식이 근본적으로 변경되었습니다. 동일한 키를 사용하는 여러 컴포넌트가 독립적인 복사본을 유지하는 대신, 하나의 리액티브 참조를 공유하게 됩니다.
export function useProductData(productId: string) {
return useAsyncData(
`product-${productId}`,
() => $fetch(`/api/products/${productId}`),
{
getCachedData: (key, nuxtApp, ctx) => {
// ctx.cause로 패칭 이유를 판별 가능
if (ctx.cause === 'refresh:manual') return undefined
return nuxtApp.payload.data[key]
},
},
)
}이 새로운 데이터 레이어에는 세 가지 핵심 변경 사항이 있습니다.
- 공유 ref: 두 컴포넌트에서
useProductData('abc')를 호출하면 동일한data,error,statusref가 반환됩니다. 한쪽을 업데이트하면 다른 쪽에도 즉시 반영됩니다. - 자동 정리: 특정 키를 사용하는 마지막 컴포넌트가 언마운트되면, 해당 데이터가 메모리에서 해제됩니다.
- 리액티브 키: 키를 computed나 ref로 래핑하면, 값이 변경될 때 자동으로 데이터가 다시 패칭됩니다.
getCachedData 콜백은 이제 cause 속성('initial', 'refresh:hook', 'refresh:manual', 'watch')을 가진 컨텍스트 객체를 받습니다. 이를 통해 캐시 데이터 제공과 새로운 패칭 간의 전환을 세밀하게 제어할 수 있습니다.
useAsyncData/useFetch의 data와 error 기본값이 null에서 undefined로 변경되었습니다. === null 검사를 === undefined로 업데이트하거나 느슨한 동등 비교를 사용해야 합니다.
Shallow Reactivity 기본 적용으로 성능 향상
Nuxt 4에서는 useAsyncData와 useFetch의 data가 ref 대신 shallowRef를 기본으로 사용합니다. Vue가 더 이상 중첩된 모든 속성을 재귀적으로 추적하지 않으므로, 깊이 중첩된 객체나 대규모 배열을 포함하는 API 응답에 대해 측정 가능한 성능 개선을 제공합니다.
<script setup lang="ts">
// data는 기본적으로 shallowRef로 동작
const { data: metrics } = await useFetch('/api/dashboard/metrics')
// 직접적인 속성 변경은 리액티비티를 트리거하지 않음
// metrics.value.visits = 100 // 리렌더링이 발생하지 않음
// 전체 값을 교체하면 업데이트가 트리거됨
metrics.value = { ...metrics.value, visits: 100 }
// 특정 호출에서 딥 리액티비티를 활성화
const { data: settings } = await useFetch('/api/settings', {
deep: true,
})
</script>대시보드, 상품 목록, 기사 페이지 등 대부분의 읽기 전용 데이터 표시에서는 shallow reactivity가 코드 변경 없이 정상적으로 동작합니다. 폼이나 중첩된 속성을 직접 변경하는 인터랙티브 UI에는 deep: true 옵션을 계속 사용할 수 있습니다.
Vue.js / Nuxt.js 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
TypeScript 컨텍스트 분할과 타입 안전성 강화
Nuxt 4에서는 프로젝트 내 각 컨텍스트에 대해 별도의 TypeScript 설정이 생성됩니다.
.nuxt/tsconfig.app.json— Vue 애플리케이션 코드용.nuxt/tsconfig.server.json— Nitro 서버 코드용.nuxt/tsconfig.shared.json— 공유 유틸리티용.nuxt/tsconfig.node.json— 빌드 시점 설정용
이 분리 덕분에 IDE가 클라이언트 코드에서 서버 전용 API를 제안하거나 그 반대의 상황이 발생하지 않습니다. 프로젝트 루트의 tsconfig.json은 네 가지 설정을 모두 참조하는 구조가 됩니다.
{
"files": [],
"references": [
{ "path": "./.nuxt/tsconfig.app.json" },
{ "path": "./.nuxt/tsconfig.server.json" },
{ "path": "./.nuxt/tsconfig.shared.json" },
{ "path": "./.nuxt/tsconfig.node.json" }
]
}CI에서의 타입 검사도 변경됩니다. vue-tsc 명령어는 프로젝트 참조를 올바르게 처리하기 위해 -b 플래그(빌드 모드)가 필요합니다.
# 변경 전 (Nuxt 3)
nuxt prepare && vue-tsc --noEmit
# 변경 후 (Nuxt 4)
nuxt prepare && vue-tsc -b --noEmit또 다른 TypeScript 변경 사항으로, compilerOptions.noUncheckedIndexedAccess가 기본적으로 true로 설정됩니다. 배열 요소나 객체 속성에 인덱스로 접근하면 T | undefined를 반환하게 되어, 컴파일 시점에 잠재적인 런타임 오류를 감지할 수 있습니다.
컴포넌트 이름 정규화와 Vue Router v5
Nuxt 4.3에서는 Vue Router v5로 업그레이드되었으며, unplugin-vue-router에 대한 의존성이 제거되었습니다. 대부분의 애플리케이션에서 이 업그레이드는 투명하게 진행됩니다.
컴포넌트 명명 규칙이 표준화되었습니다. components/dashboard/MetricsCard.vue에 위치한 컴포넌트는 <KeepAlive>, Vue DevTools, 테스트 유틸리티를 포함한 모든 컨텍스트에서 일관되게 DashboardMetricsCard라는 이름을 갖습니다.
<!-- app/pages/dashboard.vue -->
<template>
<NuxtPage :keepalive="{
include: ['DashboardMetricsCard', 'DashboardRecentActivity'],
}" />
</template>컴포넌트 이름 필터를 사용하는 <KeepAlive>가 있는 프로젝트는 이 새로운 명명 규칙에 맞게 업데이트해야 합니다. 컨텍스트에 따라 이름이 달라지던 이전 동작은 더 이상 적용되지 않습니다.
Head 관리의 주요 변경 사항 대응
Nuxt 4에는 Unhead v2가 포함되어 있으며, useHead와 useSeoMeta에서 여러 더 이상 사용되지 않는 속성이 제거되었습니다.
<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
// Unhead v2: vmid, hid, children, body 속성 제거
useSeoMeta({
title: () => product.value?.name ?? 'Product',
ogTitle: () => product.value?.name ?? 'Product',
description: () => product.value?.description ?? '',
ogImage: () => product.value?.imageUrl ?? '',
})
</script>템플릿 파라미터나 별칭 정렬에 의존하는 프로젝트에서는 이를 명시적 플러그인으로 설치해야 합니다.
import { TemplateParamsPlugin, AliasSortingPlugin } from '@unhead/vue/plugins'
export default defineNuxtPlugin({
setup() {
const unhead = injectHead()
unhead.use(TemplateParamsPlugin)
unhead.use(AliasSortingPlugin)
},
})Nuxt 3은 2026년 7월 31일까지 보안 업데이트와 중요한 버그 수정을 계속 받습니다. 해당 날짜 이후 Nuxt 3은 지원 대상에서 제외됩니다. EOL 프레임워크에서 프로덕션 애플리케이션을 운영하는 것을 방지하기 위해 지금부터 마이그레이션 계획을 세우는 것이 권장됩니다.
마이그레이션 체크리스트와 주의 사항
자동 codemod가 대부분의 변경 사항을 처리하지만, 수동 대응이 필요한 항목도 있습니다.
window.__NUXT__제거:useNuxtApp().payload로 교체해야 합니다. 이 전역 객체는 Nuxt 4에서 하이드레이션 후 삭제됩니다.pages:extend훅: 페이지 메타 스캔 후에 실행되는 새로운pages:resolved훅으로 전환해야 합니다.dedupe불리언:refresh({ dedupe: true })를refresh({ dedupe: 'cancel' })로,false를'defer'로 교체합니다.- 인라인 스타일: 기본적으로 Vue 컴포넌트 스타일만 인라인화되며, 글로벌 CSS는 별도 파일로 로드됩니다. Nuxt 3 동작을 복원하려면
features: { inlineStyles: true }를 추가합니다. clearNuxtState:undefined대신 초기값으로 리셋됩니다. 이전 동작이 필요한 경우clearNuxtState('key', { reset: false })를 사용합니다.
프로덕션 애플리케이션을 위한 실용적인 마이그레이션 순서는 다음과 같습니다.
npx nuxt upgrade --dedupe를 실행하고 애플리케이션 빌드가 정상인지 확인- codemod 실행:
npx codemod@latest nuxt/4/migration-recipe - 파일을
app/으로 이동 (file-structure codemod로 자동화) - 데이터 패칭 로직에서
null검사를undefined로 업데이트 - 정규화된 이름으로
<KeepAlive>컴포넌트 테스트 - CI 타입 검사를
vue-tsc -b --noEmit으로 업데이트 - 전체 테스트 스위트를 실행하고
noUncheckedIndexedAccess로 인해 발견된 TypeScript 오류 수정
Vue와 Nuxt 개념을 더 깊이 이해하려면 SharpSkill의 Vue/Nuxt 면접 질문을 참고하거나, Nuxt 4에도 이어지는 렌더링 전략의 배경으로 SSR 및 정적 생성 가이드를 확인해 보시기 바랍니다.
결론
- Nuxt 4.4(2026년 4월 기준 최신 버전)는
app/디렉터리 구조, 싱글톤 데이터 패칭, 분할 TypeScript 컨텍스트를 프로덕션 준비 완료된 기본값으로 확립했습니다 npx codemod@latest nuxt/4/migration-recipe명령어로 디렉터리 재구성, 더 이상 사용되지 않는 API 교체, 데이터 패칭 업데이트가 자동화됩니다shallowRef기반의 shallow reactivity는 대부분의 경우 코드 변경 없이 읽기 중심 페이지의 성능을 개선합니다- 컨텍스트별 개별 TypeScript 설정(app, server, shared, node)은 컨텍스트 간 타입 누출을 제거하고 IDE 자동 완성 정확도를 향상시킵니다
- Nuxt 3은 2026년 7월 31일에 지원이 종료되므로, 그 이전에 마이그레이션을 완료해야 지원되는 버전을 유지할 수 있습니다
- Vue Router v5와 Unhead v2는 더 정돈된 API를 제공하지만, 더 이상 사용되지 않는 속성의 제거로 인해 마이그레이션 시 점검이 필요합니다
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
태그
공유
관련 기사

Vue 3 Pinia vs Vuex 완벽 비교: 2026년 상태 관리 전략과 면접 핵심 질문
Vue 3 생태계에서 Pinia와 Vuex를 비교 분석합니다. Options Store와 Setup Store 패턴, TypeScript 통합, 크로스 스토어 구성, SSR 지원, Vuex에서 Pinia로의 마이그레이션 전략, 그리고 2026년 면접에서 자주 출제되는 상태 관리 질문을 코드 예제와 함께 정리합니다.

Nuxt 3: SSR과 정적 생성 완벽 가이드
Nuxt 3로 SSR과 정적 생성을 마스터하십시오. useFetch부터 route rules까지, Vue.js 애플리케이션의 성능을 최적화하는 방법을 배웁니다.

Vue.js 면접 핵심 질문: 합격을 위한 25문항
Vue.js 면접을 위한 25개의 핵심 질문. 반응성부터 composables까지, 다음 면접에서 빛날 핵심 개념을 정리합니다.