본문으로 건너뛰기
Previous
Next
Nuxt 3 완벽 가이드 | Vue·SSR·Composables·Nitro·Server Routes

Nuxt 3 완벽 가이드 | Vue·SSR·Composables·Nitro·Server Routes

Nuxt 3 완벽 가이드 | Vue·SSR·Composables·Nitro·Server Routes

이 글의 핵심

Nuxt 3 완벽 가이드에 대해 정리한 개발 블로그 글입니다. Nuxt 3로 풀스택 Vue 앱을 구축하는 완벽 가이드입니다. Auto-imports, Composables, Nitro 엔진, Server Routes, 배포까지 실전 예제로 정리했고, SSR·SSG·CSR 파이프라인,… 개념과 예제 코드를 단계적으로 다루며, 실무·학습에 참고할 수 있도록 구성했습니다. 관련 키워드:…

이 글의 핵심

Nuxt 3로 풀스택 Vue 앱을 구축하는 완벽 가이드입니다. Auto-imports, Composables, Nitro 엔진, Server Routes, 배포까지 실전 예제로 정리했고, SSR·SSG·CSR 파이프라인, unimport·#imports, Nitro 프리셋·.output, 모듈·훅, 프로덕션 운영 패턴까지 내부 동작을 심화해 다룹니다.

실무 경험 공유: Vue SPA를 Nuxt 3로 전환하면서, SEO를 크게 개선하고 초기 로딩 속도를 3배 향상시킨 경험을 공유합니다.

들어가며: “Vue SPA는 SEO가 약해요”

실무 문제 시나리오

시나리오 1: 검색 엔진에 안 잡혀요

SPA는 SEO가 약합니다. Nuxt SSR로 해결합니다. 시나리오 2: 초기 로딩이 느려요

모든 JavaScript를 다운로드해야 합니다. Nuxt는 서버에서 렌더링합니다. 시나리오 3: import가 번거로워요

매번 import 해야 합니다. Nuxt는 자동 import합니다.

1. Nuxt 3란?

핵심 특징

Nuxt 3는 Vue 3 기반 풀스택 프레임워크입니다. 주요 장점:

  • Auto-imports: 자동 import
  • Nitro: 빠른 서버 엔진
  • SSR/SSG: 렌더링 모드 선택
  • Server Routes: API 내장
  • TypeScript: 완벽한 지원

2. 프로젝트 생성

npx nuxi@latest init my-app
cd my-app
npm install
npm run dev

3. 파일 기반 라우팅

페이지

<!-- pages/index.vue -->
<template>
  <div>
    <h1>Home</h1>
    <NuxtLink to="/about">About</NuxtLink>
  </div>
</template>
<!-- pages/about.vue -->
// 실행 예제
<template>
  <div>
    <h1>About</h1>
  </div>
</template>

동적 라우트

<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const slug = route.params.slug;
const { data: post } = await useFetch(`/api/posts/${slug}`);
</script>
<template>
  <article>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
  </article>
</template>

4. Composables

useFetch

<script setup lang="ts">
const { data, pending, error, refresh } = await useFetch('/api/users');
</script>
<template>
  <div>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <ul v-else>
      <li v-for="user in data" :key="user.id">
        {{ user.name }}
      </li>
    </ul>
    <button @click="refresh">Refresh</button>
  </div>
</template>

useAsyncData

<script setup lang="ts">
const { data: posts } = await useAsyncData('posts', () => 
  $fetch('/api/posts')
);
</script>

커스텀 Composable

// composables/useAuth.ts
export const useAuth = () => {
  const user = useState('user', () => null);
  const login = async (email: string, password: string) => {
    const response = await $fetch('/api/login', {
      method: 'POST',
      body: { email, password },
    });
    user.value = response.user;
  };
  const logout = async () => {
    await $fetch('/api/logout', { method: 'POST' });
    user.value = null;
  };
  return { user, login, logout };
};

5. Server Routes

API 엔드포인트

// server/api/users.get.ts
export default defineEventHandler(async (event) => {
  const users = await prisma.user.findMany();
  return users;
});
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
  const id = parseInt(event.context.params.id);
  const user = await prisma.user.findUnique({ where: { id } });
  if (!user) {
    throw createError({
      statusCode: 404,
      statusMessage: 'User not found',
    });
  }
  return user;
});
// server/api/users.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  const user = await prisma.user.create({
    data: {
      name: body.name,
      email: body.email,
    },
  });
  return user;
});

6. Middleware

인증 Middleware

// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { user } = useAuth();
  if (!user.value && to.path !== '/login') {
    return navigateTo('/login');
  }
});

사용

<!-- pages/dashboard.vue -->
<script setup lang="ts">
definePageMeta({
  middleware: 'auth',
});
</script>
<template>
  <div>
    <h1>Dashboard</h1>
  </div>
</template>

7. Layouts

아래는 기본 레이아웃 예제 코드입니다.

<!-- layouts/default.vue -->
<template>
  <div>
    <header>
      <nav>
        <NuxtLink to="/">Home</NuxtLink>
        <NuxtLink to="/about">About</NuxtLink>
      </nav>
    </header>
    <main>
      <slot />
    </main>
    <footer>
      <p>&copy; 2026 My App</p>
    </footer>
  </div>
</template>
<!-- pages/index.vue -->
<script setup lang="ts">
definePageMeta({
  layout: 'default',
});
</script>

8. 배포

Static (SSG)

npm run generate

Server (SSR)

npm run build
node .output/server/index.mjs

Docker

FROM node:20-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.output .output
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

9. 렌더링 모드(SSR·SSG·CSR) 내부 동작

Nuxt 3는 한 코드베이스에서 클라이언트·서버 번들을 동시에 구성합니다. nuxt build 시 Vite가 클라이언트 엔트리서버 엔트리(Vue SSR용)를 각각 트리셰이킹하여 산출하고, 런타임에서는 요청 경로에 따라 HTML을 서버에서 조립할지, 정적 HTML만 내보낼지, 브라우저에서만 Vue를 기동할지가 갈립니다.

SSR(서버 사이드 렌더링)

요청이 들어오면 Nitro가 Node(또는 프리셋 런타임)에서 Vue 앱 인스턴스를 만들고, 라우트에 맞는 페이지 컴포넌트를 서버에서 한 번 렌더링합니다. 이때 useAsyncData·useFetch는 기본적으로 서버에서 데이터를 가져와 직렬화 가능한 페이로드(payload) 로 클라이언트에 넘깁니다. 같은 키로 호출된 데이터는 클라이언트 하이드레이션 시 중복 요청을 피하도록 설계되어 있어, 네트워크 탭에서 API가 두 번 찍히지 않는 것이 정상 동작인 경우가 많습니다.

routeRules로 경로별로 ssr: true를 유지하면서도 캐시 헤더나 ISR에 가까운 동작을 조합할 수 있어, 엣지·CDN과의 연계가 SSR 설계의 핵심이 됩니다.

SSG(정적 사이트 생성)와 프리렌더

nuxt generate(내부적으로 Nitro 프리렌더)는 빌드 타임에 HTML과 자산을 뽑아 public·정적 출력 디렉터리에 둡니다. 동적 경로는 nitro.prerender.routes나 크롤링으로 발견된 링크가 있어야 페이지가 생성됩니다. 데이터가 런타임에만 존재하는 API라면, 빌드 시점에 그 API가 접근 가능해야 하며, 그렇지 않으면 빈 페이지나 빌드 실패로 이어집니다. 이는 “SSG가 무조건 저렴하다”가 아니라 데이터 가용성·무효화 전략이 함께 와야 함을 뜻합니다.

CSR(클라이언트 전용)과 SPA 모드

ssr: false(앱 전역 또는 routeRules·페이지 메타)를 쓰면 해당 범위는 브라우저에서만 Vue 앱이 부팅됩니다. SEO가 필요 없는 관리자 콘솔, 지도·WebGL 등 브라우저 API에 강하게 묶인 화면에 적합합니다. 다만 초기 HTML은 거의 비어 있으므로 검색·SNS 크롤러 대응이 약해지고, 체감 FCP는 번들 다운로드 이후로 밀립니다.

하이브리드와 routeRules의 역할

Nuxt 3는 경로마다 ssr / prerender / headers / redirect 등을 선언해 한 프로젝트 안에서 모드 혼합이 가능합니다. 이는 “프레임워크가 단일 렌더링 모드만 지원한다”는 구식 가정과 달리, 엣지 캐시·오리진·정적 자산을 역할별로 쪼개는 실무 패턴과 맞닿아 있습니다.


10. Auto-imports 메커니즘

Auto-import는 “편의 기능”이 아니라 빌드 파이프라인 상의 계약입니다. Nuxt는 unimport 계열의 해석기로, 설정된 디렉터리(예: components/, composables/, utils/)를 스캔해 이름→심볼 매핑을 만듭니다. 소스 변환 단계에서 실제 import 문이 삽입되거나 가상 모듈 #imports를 통해 노출되며, TypeScript는 별도의 타입 스텁으로 자동 완성이 맞춰집니다.

충돌·우선순위

같은 이름의 composable과 Vue API가 겹치면 의도치 않은 그림자(shadowing) 가 납니다. 팀 규칙으로 use 접두사, 디렉터리 네이밍, 혹은 imports.presets·imports.dirs를 명시해 스캔 순서를 고정하는 편이 안전합니다. 외부 패키지를 자동 등록할 때는 imports 옵션으로 범위를 제한해 번들과 타입 노이즈를 줄입니다.

트리셰이킹과 비용

자동 import가 모든 심볼을 번들에 넣는 것은 아닙니다. 사용된 것만 클라이언트/서버 각각의 롤업 그래프에 들어가도록 설계되어 있으나, 서버 전용 코드가 클라이언트로 새는 실수(예: 비밀을 composable에 하드코딩)는 막지 못합니다. 민감 로직은 server/·runtimeConfig·환경 변수로 분리해야 합니다.


11. Nitro 서버 엔진 아키텍처

Nitro는 Nuxt 3의 서버 층 전부를 담당합니다. 개발 시에는 Vite와 통합된 경량 서버로 동작하고, 프로덕션 빌드에서는 Rollup으로 서버 엔트리·라우트·미들웨어를 하나의 실행 가능한 출력(.output/server/index.mjs 등)으로 묶습니다.

출력물과 프리셋

.output 아래에는 서버 번들, 퍼블릭 자산, 필요 시 Nitro 라우트 매니페스트가 들어갑니다. nitro.preset에 따라 타깃이 달라집니다. 예를 들어 node-server는 Node 프로세스에서 listen하고, cloudflare_pages·vercel 등은 플랫폼이 기대하는 핸들러 형태로 추출됩니다. 즉 “Nuxt를 배포한다”는 것은 동일한 라우트 정의를 다른 런타임 어댑터로 내보낸다는 의미에 가깝습니다.

서버 라우트와 미들웨어 스택

server/api, server/middleware, server/routes의 파일 기반 라우팅은 빌드 시 단일 라우터 테이블로 합쳐집니다. defineEventHandler는 Web 표준 Request/Response에 가까운 H3 이벤트 모델 위에서 동작하며, $fetch는 서버·클라이언트 모두에서 동일한 API로 내부 호출을 최적화(같은 프로세스 내 직접 호출 등)할 수 있습니다.

캐시·스토리지·드라이버

Nitro는 경로별 캐시, 스토리지 추상화, 일부 프리셋에서의 에지 키-값 연계를 제공합니다. 프로덕션에서는 “메모리 캐시가 인스턴스마다 따로다” 같은 수평 확장 가정을 항상 염두에 두어야 합니다.


12. Nuxt 모듈과 훅 시스템

nuxt.configmodules 배열에 등록된 모듈은 빌드 초기화 시점에 로드되어 Nuxt의 내부 라이프사이클에 훅을 겁니다. defineNuxtModule로 감싼 모듈은 스키마 검증(meta.configKey 등)과 함께 설치되어, 플러그인·컴포넌트 자동 등록·라우트 주입·Nitro 설정 병합을 한 번에 처리할 수 있습니다.

자주 쓰는 확장 포인트

  • addPlugin / addComponent / addImports: 클라이언트·서버 공통 초기화와 DX 향상.
  • addServerHandler / addRouteMiddleware: API·미들웨어를 코드로 등록(파일 기반과 병행 가능).
  • nitro 설정 훅: CORS, 압축, 라우트 규칙, 프리렌더 대상을 프로그래밍 방식으로 조정.

nuxt.hooks와 Nitro 훅

Nuxt 훅(hooks: { 'build:before': ... } 등)은 번들링·준비 단계에 개입하고, Nitro 훅은 서버 빌드·프리렌더에 개입합니다. 디버깅 시 “왜 이 라우트가 출력에 없지?”는 어느 훅 단계에서 빠졌는지 역추적하는 것이 빠릅니다.

커스텀 모듈을 작성할 때는 부수 효과 최소화옵션 스키마 명시가 유지보수 비용을 좌우합니다. 사내 모듈은 내부 패키지로 버전을 올리며, 앱 여러 개에 재사용하는 경우 peer 의존성을 문서화하는 것이 좋습니다.


13. 프로덕션 Nuxt 패턴

런타임 설정과 비밀

runtimeConfig는 빌드 시 공개/비공개 키가 나뉘어 주입됩니다. API 키·DB URL 등은 서버 전용 키에만 두고, 클라이언트에 노출되는 public에는 브라우저에 줘도 되는 값만 넣습니다. .env는 배포 플랫폼의 시크릿과 동기화하고, 로컬과 프로덕션의 키 이름 불일치로 인한 장애를 방지합니다.

오류·로깅·헬스

error.vue, 전역 에러 훅, 서버 라우트의 createError일관된 HTTP 상태와 메시지를 반환합니다. 로그는 구조화(JSON)로 남기고, GET /health 같은 가벼운 헬스 체크를 Nitro 라우트로 두면 오케스트레이터와 로드밸런서가 안정적으로 판단합니다. PM2·컨테이너 재시작 정책과 맞출 때는 무중단 배포 전략(블루/그린, 롤링)과 세션 저장소를 함께 설계합니다.

캐시·성능·자산

정적 자산은 CDN 캐시 헤더와 파일명 해시에 의존하고, HTML/API는 routeRules·역프록시 캐시·애플리케이션 캐시를 계층화합니다. useAsyncDatagetCachedData·키 전략으로 서버 렌더 단계의 중복 페치를 줄이고, 이미지·폰트는 nuxt/image 등으로 포맷·크기 최적화를 자동화합니다.

관측 가능성

프로덕션에서는 Core Web Vitals, 서버 지표(지연, 오류율), Nitro 라우트별 지연을 함께 봅니다. Nuxt 자체보다 배포 프리셋과 런타임(콜드 스타트, CPU 크레딧, 지역)이 병목인 경우가 많으므로, 성능 이슈는 프론트 번들 크기와 서버/엣지 설정을 동시에 의심하는 것이 좋습니다.


정리 및 체크리스트

핵심 요약

  • Nuxt 3: Vue 3 풀스택 프레임워크
  • Auto-imports: unimport 기반 스캔·가상 모듈·타입 스텁
  • Nitro: Rollup 서버 번들, 프리셋별 출력, H3 이벤트 모델
  • useFetch / useAsyncData: SSR 시 페이로드 직렬화와 클라이언트 중복 제거
  • Server Routes: 파일 기반 API가 단일 라우터 테이블로 합쳐짐
  • SSR / SSG / CSR: routeRules·ssr: false로 경로별 하이브리드
  • 모듈·훅: 빌드/서버 단계 확장과 팀 표준화

구현 체크리스트

  • Nuxt 3 프로젝트 생성
  • 페이지 라우팅 구현
  • useFetch로 데이터 페칭
  • Server Routes 작성
  • Middleware 구현
  • Layouts 설정
  • 렌더링 모드(routeRules·SSG 대상 경로) 확정
  • runtimeConfig로 비밀·공개 설정 분리
  • 배포

같이 보면 좋은 글


이 글에서 다루는 키워드

Nuxt, Vue, Full Stack, SSR, Nitro, Web Framework, TypeScript

내부 동작과 핵심 메커니즘

이 글의 주제는 「Nuxt 3 완벽 가이드 | Vue·SSR·Composables·Nitro·Server Routes」입니다. 앞선 튜토리얼을 구현·런타임 관점에서 다시 압축합니다. 구성 요소 간 책임 분리와 관측 가능한 지점을 기준으로 “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)·동시성이 어디서 터지는가”를 한 장면으로 그리면 장애 분석이 빨라집니다.

처리 파이프라인(개념도)

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]

경계에서의 지연·실패(시퀀스 관점)

sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(프로세스·런타임·게이트웨이)
  participant D as 의존성(외부 API·DB·큐)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)

알고리즘·프로토콜·리소스 관점 체크포인트

  • 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(버퍼 경계, 프로토콜 상태, 트랜잭션 격리, 파일 디스크립터 상한)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 동일 입력에 동일 출력이 보장되는 순수 층과, 시간·네트워크·스레드 스케줄에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합, GC·할당, 캐시 미스처럼 누적 비용을 의심 목록에 넣습니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때(소켓 버퍼, 큐 깊이, 스트림) 어디서 어떤 신호로 속도를 줄일지 정의합니다.

프로덕션 운영 패턴

실서비스에서는 기능과 함께 관측·배포·보안·비용·규제가 동시에 요구됩니다.

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율/지연 분위수(p95/p99), 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시 계층·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션 호환성·플래그가 문서화되어 있는가
용량피크 트래픽·디스크·파일 디스크립터·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 가능한 한 프로덕션에 가깝게 맞추는 것이 재현율을 높입니다.


확장 예시: 엔드투엔드 미니 시나리오

「Nuxt 3 완벽 가이드 | Vue·SSR·Composables·Nitro·Server Routes」을 실제 배포·운영 흐름으로 옮긴 체크리스트형 시나리오입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드 표를 API 또는 이벤트 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 한 화면(로그+메트릭+트레이스)에서 추적한다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지(또는 피처 플래그) 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값이 기대 범위인지 본다.

의사코드 스케치(프레임워크 무관)

handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)        // 경계에서 거절
  authorize(validated, ctx)                  // 권한·테넌트
  result = domainCore(validated)             // 순수에 가까운 규칙
  persistOrEmit(result, idempotentKey)       // I/O: 멱등·재시도 정책
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성 불안정, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정이 로컬과 다름프로필·시크릿·기본값, 지역 리전단일 소스(예: 스키마 검증된 설정)와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

자주 묻는 질문 (FAQ)

Q. Nuxt 2 vs Nuxt 3, 어떤 게 나은가요?

A. Nuxt 3가 훨씬 빠르고 현대적입니다. 새 프로젝트는 Nuxt 3를 사용하세요.

Q. Next.js vs Nuxt, 어떤 게 나은가요?

A. Vue를 선호하면 Nuxt, React를 선호하면 Next.js를 사용하세요. 기능은 비슷합니다.

Q. Vite를 사용하나요?

A. 네, Nuxt 3는 Vite를 기본으로 사용합니다.

Q. 프로덕션에서 사용해도 되나요?

A. 네. 다만 runtimeConfig로 비밀을 분리하고, routeRules로 캐시·렌더 모드를 경로별로 고정하며, 헬스 체크·로그·배포 프리셋(콜드 스타트·리전)까지 함께 설계하는 것이 운영 안정성에 직결됩니다.