minstudio

Nuxt 4 소개 및 핵심 아키텍처

Nuxt는 Vue.js를 기반으로 웹 애플리케이션을 직관적이고 강력하게 만들 수 있도록 도와주는 풀스택 프레임워크입니다. 특히 Nuxt 4 버전부터는 더욱 진보된 아키텍처와 새로운 폴더 구조를 도입하여 개발자 경험(DX)을 극대화했습니다.

Vue.js 단독으로 사용할 때 겪는 초기 로딩 지연(SPA의 단점)검색엔진 최적화(SEO) 문제서버 사이드 렌더링(SSR)을 통해 완벽하게 해결해 줍니다.

🆚 Vue SPA vs Nuxt SSR 아키텍처 비교 순수 Vue (CSR) 1. 빈 HTML 다운로드 2. 무거운 JS 로딩 (하얀 화면 대기) SEO 봇: "아무 내용도 없네?" 사용자 대기 시간 발생 ⏱️ Nuxt 4 (SSR) 1. 서버에서 완전한 HTML 생성 2. 사용자 즉시 화면 시청 🎉 SEO 봇: "콘텐츠 완벽 수집!" 뒤에서 JS 하이드레이션 진행 💦
💡 알아두세요: Nuxt 4는 설정 파일 조작 없이(Zero-config) TypeScript를 즉시 지원하며, 컴포넌트나 내장 함수들을 파일 상단에 import 할 필요 없이 자동 임포트(Auto-imports)하여 개발 생산성을 비약적으로 높여줍니다.
<!-- ==========================================
// 📂 nuxt.config.ts (Nuxt 4 환경 설정 파일)
// ⚠️ 주의: 실제 .ts 파일에서는 <script> 태그를 작성하지 않습니다.
// ========================================== -->
<script>
// Nuxt 4의 루트 디렉토리에 위치하는 핵심 설정 파일입니다.
export default defineNuxtConfig({
  // 1. Nuxt 4의 핵심! 새로운 폴더 구조 호환성을 위한 설정
  // 향후 기본값이 될 예정이나 명시적으로 지정할 수 있습니다.
  future: {
    compatibilityVersion: 4,
  },

  // 2. 개발 도구 활성화 (브라우저 하단에 강력한 DevTools가 생성됨)
  devtools: { enabled: true },

  // 3. 환경 변수 관리 (프라이빗 토큰과 퍼블릭 설정 분리)
  runtimeConfig: {
    // 여기 적힌 값은 서버(SSR) 환경에서만 접근 가능합니다. (보안 유지)
    apiSecretKey: process.env.NUXT_API_SECRET_KEY || 'default-secret',
    
    // public 안에 적힌 값은 클라이언트(브라우저)에서도 접근 가능합니다.
    public: {
      apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
    }
  }
})
</script>
Nuxt 4 새로운 디렉토리 구조

Nuxt 3까지는 프로젝트 루트 공간에 모든 소스 폴더가 혼재되어 있었습니다. Nuxt 4에서는 소스 코드의 경계를 명확히 하기 위해 모든 프론트엔드 관련 파일을 app/ 디렉토리 내부로 옮기는 혁신적인 구조 변경을 단행했습니다.

📁 Nuxt 4 디렉토리 아키텍처 (app/ 도입) my-nuxt-app/ (Root) package.json, nuxt.config.ts 📂 app/ (Nuxt 4의 핵심 소스 폴더!) 📄 app.vue (메인 진입점) 📂 components/ (자동 임포트됨) 📂 pages/ (URL 주소가 되는 폴더) 📂 layouts/ (공통 UI 껍데기) 📂 assets/ (CSS/SCSS 등 에셋) 📂 public/ favicon.ico, 로고 이미지 등 / URL에 그대로 노출됨 📂 server/ Nitro 백엔드 서버 로직 API 및 미들웨어 관리
<!-- ==========================================
// 📂 app/app.vue (앱의 메인 진입점)
// ========================================== -->
<template>
  <!-- 
    Nuxt 4에서는 src/app.vue 나 루트의 app.vue 가 아닌,
    'app/app.vue' 가 최상단 메인 파일이 됩니다. 
  -->
  <div>
    <!-- 전역적으로 적용될 공통 요소를 여기에 넣을 수 있습니다. -->
    <NuxtRouteAnnouncer />
    
    <!-- 
      <NuxtLayout>은 app/layouts 폴더의 공통 껍데기를 적용합니다. 
      <NuxtPage>는 app/pages 폴더의 개별 화면을 끼워 넣습니다.
    -->
    <NuxtLayout>
      <NuxtPage />
    </NuxtLayout>
  </div>
</template>

<!-- ==========================================
// 📂 app/components/MyButton.vue (자동 임포트 테스트)
// ========================================== -->
<template>
  <button class="bg-blue-500 text-white px-4 py-2 rounded">
    <!-- 자식 컴포넌트에서 slot을 뚫어 텍스트를 받습니다. -->
    <slot>기본 버튼</slot>
  </button>
</template>

<script setup>
// 이 컴포넌트는 'app/components' 안에 있기 때문에,
// 다른 파일에서 import MyButton from ... 할 필요 없이 바로 
// <MyButton> 태그로 사용 가능합니다! (마법 같은 자동 임포트)
</script>
파일 기반 라우팅과 레이아웃

기존 Vue에서는 복잡한 vue-router 설정 파일을 수동으로 작성해야 했습니다. 반면 Nuxt에서는 app/pages/ 디렉토리에 Vue 파일을 만들기만 하면, 파일 구조가 즉시 브라우저의 URL 주소로 자동 변환됩니다.

🛣️ 폴더 구조가 곧 URL 주소 (파일 기반 라우팅) 📂 app/pages/ index.vue about.vue users/[id].vue 🌐 브라우저 URL / (메인 홈) /about (어바웃) /users/123 (동적)
<!-- ==========================================
// 📂 app/layouts/default.vue (공통 레이아웃 구조)
// ========================================== -->
<template>
  <div class="min-h-screen bg-gray-50 text-gray-900">
    <header class="bg-indigo-600 text-white p-4 shadow-md flex gap-4">
      <!-- <a> 태그 대신 NuxtLink를 사용하여 페이지 전환 시 깜빡임을 방지합니다. -->
      <NuxtLink to="/" class="font-bold hover:text-indigo-200">홈</NuxtLink>
      <NuxtLink to="/about" class="font-bold hover:text-indigo-200">소개</NuxtLink>
      <NuxtLink to="/users/999" class="font-bold hover:text-indigo-200">유저 정보</NuxtLink>
    </header>

    <main class="p-8 max-w-4xl mx-auto">
      <!-- pages 폴더 안의 각 화면이 여기에 쏙 들어옵니다! -->
      <slot />
    </main>
  </div>
</template>

<!-- ==========================================
// 📂 app/pages/users/[id].vue (동적 라우팅 페이지)
// ========================================== -->
<template>
  <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
    <h1 class="text-2xl font-bold text-indigo-700 mb-2">회원 상세 페이지</h1>
    
    <p class="text-lg mt-4">
      URL 주소창에 입력하신 회원 번호는 
      <strong class="text-red-500 bg-red-50 px-2 py-1 rounded">
        {{ route.params.id }}
      </strong> 번 입니다!
    </p>

    <!-- app/components 안의 버튼을 임포트 없이 바로 사용 -->
    <div class="mt-6">
      <MyButton>회원 정보 수정하기</MyButton>
    </div>
  </div>
</template>

<script setup>
// 브라우저의 현재 URL 정보를 꺼내옵니다.
const route = useRoute();
</script>
에셋 관리와 전역 스타일링

Nuxt 4에서는 정적 파일을 다루는 방법이 두 가지로 나뉩니다. 브라우저가 직접 접근해야 하는 파일(예: favicon.ico, robots.txt)은 public/ 폴더에 넣고, 빌드 과정(Sass 컴파일, JS 최소화 등)이 필요한 파일은 app/assets/ 폴더에 넣습니다.

🎨 public vs app/assets 파이프라인 비교 📂 public/ 아무런 가공 없이 있는 그대로 서빙됨 favicon.ico logo.png 접근 방식: <img src="/logo.png"> 📂 app/assets/ Vite 번들러가 가공 및 최적화 진행 main.scss (컴파일) icon.svg (인라인화) 접근 방식: ~/assets/icon.svg
<!-- ==========================================
// 📂 nuxt.config.ts (전역 스타일시트 적용)
// ⚠️ 주의: 실제 .ts 파일에서는 <script> 태그를 작성하지 않습니다.
// ========================================== -->
<script>
export default defineNuxtConfig({
  future: { compatibilityVersion: 4 },
  
  // app/assets/ 폴더에 있는 CSS나 SCSS 파일을 앱 전체에 주입합니다.
  css: [
    '~/assets/css/main.css',
    '~/assets/scss/global.scss'
  ]
})
</script>

<!-- ==========================================
// 📂 app/pages/index.vue (에셋 사용 예제)
// ========================================== -->
<template>
  <div class="p-8">
    <h1 class="text-3xl font-bold">에셋 관리 예제</h1>

    <!-- 1. public 폴더 안의 이미지 (빌드 안 됨, 빠른 서빙) -->
    <div class="my-6">
      <p class="mb-2 text-gray-600">public 폴더의 로고:</p>
      <!-- '/' 경로는 자동으로 public 폴더를 가리킵니다 -->
      <img src="/logo.png" alt="로고" class="w-32" />
    </div>

    <!-- 2. app/assets 폴더 안의 이미지 (빌드 됨, 해시값 부여) -->
    <div class="my-6">
      <p class="mb-2 text-gray-600">assets 폴더의 아이콘:</p>
      <!-- '~/assets' 경로는 빌더(Vite)가 찾아서 처리해 줍니다 -->
      <img src="~/assets/icons/star.svg" alt="별" class="w-16" />
    </div>
  </div>
</template>
최적화된 데이터 패칭 (useFetch)

서버 사이드 렌더링(SSR) 환경에서 일반적인 fetch 함수를 쓰면, 서버에서 한 번 데이터를 부르고 클라이언트 브라우저가 다시 똑같은 데이터를 부르는 이중 호출(Double Fetch) 문제가 발생합니다.

Nuxt 4에서는 이를 막기 위해 useFetch라는 강력한 내장 함수를 제공하여, 서버에서 가져온 데이터를 HTML과 함께 묶어서 클라이언트로 배달해 줍니다.

📡 useFetch의 하이드레이션 폭포수(Waterfall) 흐름 🖥️ Nuxt SSR 서버 1. useFetch() 실행하여 API 호출 2. JSON 응답 데이터를 HTML에 직렬화 HTML + Payload 전송 <script id="__NUXT_DATA__"> 🚀 🌐 클라이언트 브라우저 3. 중복 API 호출 없음! 전달받은 Payload를 그대로 재사용
<!-- ==========================================
// 📂 app/pages/movies.vue (데이터 패칭 예제)
// ========================================== -->
<template>
  <div class="p-6">
    <h1 class="text-2xl font-bold mb-4">인기 영화 목록</h1>

    <!-- 데이터가 로딩 중일 때 보여줄 스켈레톤 UI -->
    <div v-if="status === 'pending'" class="text-blue-500 font-bold">
      데이터를 불러오는 중입니다...
    </div>

    <!-- 에러가 발생했을 때 보여줄 에러 메시지 -->
    <div v-else-if="error" class="text-red-500 font-bold">
      이런! 데이터를 불러오지 못했습니다.
    </div>

    <!-- 로딩이 끝나면 화면에 리스트를 렌더링 -->
    <ul v-else class="grid grid-cols-1 gap-4">
      <!-- data 변수에 API 응답값이 담겨있습니다. -->
      <li 
        v-for="movie in data" 
        :key="movie.id" 
        class="bg-white p-4 shadow rounded"
      >
        <h2 class="text-xl font-bold">{{ movie.title }}</h2>
        <p class="text-gray-600">{{ movie.overview }}</p>
      </li>
    </ul>
  </div>
</template>

<script setup>
// useFetch는 API 주소에 요청을 보내고, 
// 그 결과를 반응형(reactive) 변수 묶음으로 돌려줍니다.
const { data, status, error } = await useFetch('https://api.example.com/movies', {
  // 캐시 효율을 위한 키(key) 지정 가능
  key: 'popular-movies',
  // 서버 응답에서 필요한 데이터만 골라오기 (Pick)
  // pick: ['id', 'title', 'overview'] 
})
</script>
SSR 상태 공유와 SEO 최적화

단순한 Vue 앱에서는 전역 상태를 위해 ref를 파일 최상단에 선언하기도 합니다. 하지만 Nuxt 같은 서버 사이드 렌더링 환경에서는 이렇게 하면 A 사용자의 정보가 B 사용자에게 노출되는 '크로스 리퀘스트(Cross-Request) 오염'이 발생할 수 있습니다.

이를 완벽히 방지하는 Nuxt 4의 전용 상태 훅이 useState입니다. 또한 useSeoMeta를 통해 페이지별로 완벽한 SEO(검색엔진 최적화) 태그를 설정할 수 있습니다.

🛡️ SSR 환경의 안전한 상태 관리 (useState) ❌ 일반 전역 ref (위험) const user = ref('Alice') (메모리에 고정됨) 👤 A 요청 "나는 Bob이야" 👤 B 요청 결과: "안녕, Bob?" (오염) ✅ useState (안전) useState('user', () => 'Alice') (요청마다 격리된 공간 생성) 👤 A 요청 "안녕, Bob" 👤 B 요청 "안녕, Alice"
<!-- ==========================================
// 📂 app/composables/useTheme.ts (상태 공유 훅)
// ⚠️ 주의: 실제 .ts 파일에서는 <script> 태그를 작성하지 않습니다.
// ========================================== -->
<script>
// Nuxt에서는 전역 상태를 위해 useState를 사용합니다.
// 'theme' 이라는 고유 키값을 가지며, 기본값은 'light' 입니다.
export const useTheme = () => useState('theme', () => 'light')
</script>

<!-- ==========================================
// 📂 app/pages/index.vue (SEO와 전역 상태 적용)
// ========================================== -->
<template>
  <!-- 테마 상태값에 따라 배경색을 동적으로 바꿉니다. -->
  <div :class="theme === 'dark' ? 'bg-gray-900 text-white' : 'bg-white text-black'" class="min-h-screen p-8">
    <h1 class="text-3xl font-bold mb-4">SEO 및 상태 예제</h1>
    
    <button 
      @click="theme = theme === 'light' ? 'dark' : 'light'"
      class="bg-indigo-500 text-white px-4 py-2 rounded shadow"
    >
      다크모드 토글 (현재: {{ theme }})
    </button>
  </div>
</template>

<script setup>
// 1. 위에서 만든 useState 훅을 바로 가져다 씁니다. (자동 임포트)
const theme = useTheme()

// 2. 이 페이지의 SEO 메타 태그를 완벽하게 세팅합니다. (서버에서 구워짐)
useSeoMeta({
  title: '멋진 Nuxt 홈페이지',
  description: 'Nuxt 4로 만든 빠르고 안전한 웹사이트입니다.',
  ogImage: 'https://example.com/banner.png',
  twitterCard: 'summary_large_image'
})
</script>
에러 핸들링과 페이지 트랜지션

Nuxt 4에서는 존재하지 않는 페이지를 방문(404)하거나 서버에서 오류(500)가 났을 때 보여줄 전역 에러 화면을 app/error.vue 파일 하나로 완벽하게 통제할 수 있습니다.

또한 페이지 간 부드러운 애니메이션을 적용하고 싶다면 app.vue<NuxtPage>에 트랜지션 속성을 부여하기만 하면 됩니다.

🚨 전역 에러 바운더리 흐름 (app/error.vue) 정상 동작 app/pages/about.vue 404 / 500 에러 발생! 🚫 최상단 에러 화면 표출 (app/error.vue) 에러 코드를 분석하여 예쁜 사과 화면을 보여줍니다. clearError() 호출 홈 화면(/)으로 안전하게 이동
<!-- ==========================================
// 📂 app/error.vue (전역 에러 처리 컴포넌트)
// ========================================== -->
<template>
  <div class="h-screen flex flex-col items-center justify-center bg-gray-50 text-center">
    <!-- 에러 정보 객체(error)를 자동으로 전달받습니다. -->
    <h1 class="text-9xl font-bold text-red-500">{{ error.statusCode }}</h1>
    
    <p class="text-2xl mt-4 font-semibold text-gray-800">
      {{ error.statusCode === 404 ? '페이지를 찾을 수 없습니다.' : '서버에 문제가 발생했습니다.' }}
    </p>

    <p class="text-gray-500 mt-2">{{ error.message }}</p>

    <!-- 에러 상태를 초기화하고 홈으로 돌아가는 버튼 -->
    <button @click="handleError" class="mt-8 bg-blue-600 text-white px-6 py-3 rounded-full hover:bg-blue-700">
      홈으로 돌아가기
    </button>
  </div>
</template>

<script setup>
// 이 페이지는 Nuxt가 에러 발생 시 자동으로 렌더링하며 error props를 내려줍니다.
const props = defineProps({
  error: Object
})

const handleError = () => {
  // clearError 훅을 실행하면 현재 에러 상태를 지우고, 지정된 경로로 리다이렉트합니다.
  clearError({ redirect: '/' })
}
</script>

<!-- ==========================================
// 📂 app/app.vue (페이지 전환 애니메이션 적용)
// ========================================== -->
<template>
  <NuxtLayout>
    <!-- transition 속성을 켜주면, 라우팅 시 자동으로 애니메이션 클래스가 붙습니다! -->
    <NuxtPage :transition="{
      name: 'page', // .page-enter-active 같은 CSS 클래스가 사용됨
      mode: 'out-in' // 이전 화면이 완전히 사라진 후 새 화면이 나타남
    }" />
  </NuxtLayout>
</template>
Nitro 풀스택 서버 엔진과 배포

Nuxt의 진정한 마법은 백엔드를 따로 구축할 필요가 없다는 것입니다! 내장된 Nitro(니트로) 엔진 덕분에, server/api/ 폴더에 파일을 만들기만 하면 즉시 무적의 백엔드 API 서버가 완성됩니다.

배포 시 npm run build를 실행하면, 프론트엔드 코드와 Nitro 백엔드 코드가 가볍고 완벽하게 압축된 단 하나의 .output 폴더로 묶여 나옵니다.

⚡ Nitro 풀스택 아키텍처 및 배포 (.output) 🎨 프론트엔드 (Vue) app/pages/index.vue useFetch('/api/hello') Request Response ⚙️ 백엔드 (Nitro API) server/api/hello.ts DB 조회 로직 등 수행 npm run build 실행 (번들링) 📦 .output/ 폴더 하나로 완성! Node.js, Vercel, AWS 어디든 그대로 배포 가능
<!-- ==========================================
// 📂 server/api/hello.ts (백엔드 API 컨트롤러)
// ⚠️ 주의: 실제 .ts 파일에서는 <script> 태그를 작성하지 않습니다.
// ========================================== -->
<script>
// 이 파일은 프론트엔드가 아닌, Node.js 서버 환경에서 실행됩니다!
// 파일 이름이 'hello'이므로 URL 주소는 '/api/hello' 가 됩니다.
export default defineEventHandler((event) => {
  // 원한다면 이 안에서 MySQL, MongoDB, Oracle DB 등에 접속해서 
  // 데이터를 가져오는 코드를 작성할 수 있습니다.

  // 반환(return)된 객체는 자동으로 JSON 형태로 클라이언트에 전송됩니다.
  return {
    message: '안녕하세요! 서버에서 보낸 메시지입니다.',
    timestamp: new Date().toISOString()
  }
})
</script>

<!-- ==========================================
// 📂 app/pages/index.vue (프론트에서 API 호출)
// ========================================== -->
<template>
  <div class="p-8 text-center bg-gray-50 rounded-xl mt-10">
    <h1 class="text-3xl font-bold text-gray-800 mb-6">풀스택 API 테스트</h1>
    
    <!-- 서버에서 응답한 JSON 데이터 전체가 깔끔하게 출력됩니다. -->
    <pre class="bg-black text-green-400 p-4 rounded text-left overflow-x-auto">
      {{ data }}
    </pre>
  </div>
</template>

<script setup>
// 위에서 만든 /api/hello 주소로 데이터 패칭을 시도합니다.
const { data } = await useFetch('/api/hello')
</script>
강력한 Auto-imports와 Composables 활용

Nuxt의 가장 큰 마법 중 하나는 Auto-imports(자동 임포트)입니다. composables/utils/ 폴더에 파일을 생성하고 함수를 export하기만 하면, 애플리케이션 어디에서든 import 문 없이 즉시 사용할 수 있습니다.

이 기능은 Vue의 ref, computed 같은 내장 함수뿐만 아니라 여러분이 직접 만든 커스텀 함수에도 동일하게 적용되어, 코드 상단의 지저분한 import 선언문들을 완전히 제거해 줍니다.

✨ Auto-imports: Import 없는 마법의 공급망 composables/ 📄 useAuth.ts 📄 useMouse.ts (상태 기반 로직) utils/ 📄 formatDate.ts (순수 유틸 함수) Nuxt 엔진 자동 수집 및 배급 ⚙️ app.vue import { ref } from 'vue' import { useMouse } const x = ref(0) const auth = useAuth() formatDate(new Date()) Import 100% 생략!
<!-- ==========================================
// 📂 composables/useMouse.ts
// 파일명이 카멜케이스이면 함수명과 일치시키는 것이 좋습니다.
// ========================================== -->
<script>
// 외부 라이브러리의 컴포저블을 래핑할 때도 유용합니다.
export const useMouse = () => {
  // ref, onMounted, onUnmounted 도 별도 import 없이 바로 사용 가능!
  const x = ref(0)
  const y = ref(0)

  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}
</script>

<!-- ==========================================
// 📂 app.vue (최상위 컴포넌트)
// ========================================== -->
<script>
<template>
  <div class="p-8">
    <h1 class="text-2xl font-bold mb-4">마우스 위치 추적기</h1>
    
    <!-- useMouse에서 반환된 반응형 상태를 바로 바인딩 -->
    <p class="text-xl">X: <span class="text-emerald-500">{{ x }}</span></p>
    <p class="text-xl">Y: <span class="text-emerald-500">{{ y }}</span></p>
  </div>
</template>

<script setup>
// 💡 마법: import { ref } from 'vue' 도 없고, 
// import { useMouse } from '~/composables/useMouse' 도 없습니다!
// Nuxt가 빌드 시점에 자동으로 추적하여 연결해 줍니다.
const { x, y } = useMouse()
</script>
</script>
RouteRules를 이용한 하이브리드 렌더링(Hybrid Rendering) 전략

Nuxt는 단순한 SSR 프레임워크가 아닙니다. 하이브리드 렌더링(Hybrid Rendering)을 통해 nuxt.config.tsrouteRules 옵션 하나만으로 경로(URL)마다 완전히 다른 렌더링 엔진을 가동할 수 있습니다.

실시간으로 변하는 메인 화면은 SSR로, SEO가 중요하지 않고 반응성이 생명인 어드민 패널은 SPA 모드로, 한번 작성되면 잘 바뀌지 않는 블로그 포스트는 빌드 시 정적 생성(Prerender)으로, 성능을 극대화하고 서버 비용을 절약하세요.

🎛️ routeRules: URL 경로별 하이브리드 렌더링 분기 routeRules (nuxt.config.ts) /admin/** SPA (Client-Side Only) / SSR (기본 동작 모드) /blog/** Prerender (정적 생성) /api/** CORS 설정 및 캐싱
<!-- ==========================================
// 📂 nuxt.config.ts
// Nuxt 설정 파일에서 routeRules 객체를 선언합니다.
// ========================================== -->
<script>
export default defineNuxtConfig({
  routeRules: {
    // 1. 관리자 대시보드: SEO가 필요 없으므로 클라이언트에서만 렌더링 (SPA 모드)
    // 빠른 화면 전환과 서버 부하 감소를 노립니다.
    '/admin/**': { ssr: false },

    // 2. 블로그 게시물: 빌드 시점에 미리 HTML로 찍어냅니다. (SSG/Prerender 모드)
    // 블로그 포스트는 자주 바뀌지 않으므로 초고속 정적 파일 제공이 유리합니다.
    '/blog/**': { prerender: true },

    // 3. 상품 상세 페이지: ISR (SWR - Stale-While-Revalidate) 모드
    // 최초 요청 시 캐시하고, 이후 60초(3600초 등) 동안 캐시된 결과를 즉시 반환합니다.
    '/products/**': { swr: 60 },

    // 4. API 라우트: 다른 도메인에서도 이 API를 호출할 수 있도록 CORS 헤더를 추가합니다.
    '/api/v1/**': { cors: true },

    // 5. 이전 URL 리다이렉션: 구 주소로 접근하면 새 주소로 자동 연결
    '/old-page': { redirect: '/new-page' }
  }
})
</script>
Nuxt Modules 생태계와 라우트 미들웨어(Middleware)

Nuxt는 복잡한 Webpack/Vite 설정을 단 한 줄로 끝내주는 방대한 Module 생태계를 보유하고 있습니다. nuxt.config.tsmodules 배열에 추가하기만 하면 TailwindCSS, Pinia, Nuxt UI 등이 즉시 프로젝트 전체에 세팅됩니다.

또한, 페이지를 이동할 때마다 권한을 검사하는 라우트 미들웨어(Route Middleware)를 제공합니다. middleware/ 폴더에 파일을 작성하면 로그인 여부에 따라 페이지 접근을 완벽히 차단하고 리다이렉트 시킬 수 있습니다.

🛡️ Middleware: 페이지 접근의 문지기 (Guard) 현재 페이지 /home Link Click! 👮 auth.ts 미들웨어 로그인 확인 중... 로그인 페이지 튕김 navigateTo('/login') 마이페이지 통과 /mypage
<!-- ==========================================
// 📂 nuxt.config.ts (모듈 등록)
// ========================================== -->
<script>
export default defineNuxtConfig({
  // 방대한 플러그인 생태계를 단 한 줄로 연결합니다.
  modules: [
    '@nuxtjs/tailwindcss', // 꼬리바람 CSS 자동 설정
    '@pinia/nuxt',         // 전역 상태 관리 (Store)
    '@nuxt/image',         // 이미지 최적화
  ]
})
</script>

<!-- ==========================================
// 📂 middleware/auth.ts (인증 미들웨어)
// ========================================== -->
<script>
export default defineNuxtRouteMiddleware((to, from) => {
  // 예시: 전역 상태나 쿠키에서 인증 토큰을 확인합니다.
  const token = useCookie('auth_token')

  // 사용자가 이동하려는 페이지(to)가 마이페이지인데 토큰이 없다면?
  if (to.path.startsWith('/mypage') && !token.value) {
    // '/login' 페이지로 강제 이동(리다이렉트) 시킵니다.
    return navigateTo('/login')
  }
})
</script>

<!-- ==========================================
// 📂 app/pages/mypage/index.vue (미들웨어 적용)
// ========================================== -->
<script>
<template>
  <div class="p-8">
    <h1 class="text-xl font-bold">비밀 마이페이지 🤫</h1>
  </div>
</template>

<script setup>
// 페이지 레벨에서 특정 미들웨어만 선택적으로 실행되도록 지정합니다.
// 이 페이지에 접근하는 순간 위에서 만든 'auth' 미들웨어가 먼저 검사합니다.
definePageMeta({
  middleware: ['auth']
})
</script>
</script>
Nuxt 4 소개 및 핵심 아키텍처
Nuxt 4 새로운 디렉토리 구조
파일 기반 라우팅과 레이아웃
에셋 관리와 전역 스타일링
최적화된 데이터 패칭 (useFetch)
SSR 상태 공유와 SEO 최적화
에러 핸들링과 페이지 트랜지션
Nitro 풀스택 서버 엔진과 배포
강력한 Auto-imports와 Composables 활용
RouteRules를 이용한 하이브리드 렌더링(Hybrid Rendering) 전략
Nuxt Modules 생태계와 라우트 미들웨어(Middleware)

목차