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

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

이 글의 핵심

Nuxt 3로 풀스택 Vue 앱을 구축하는 완벽 가이드입니다. Auto-imports, Composables, Nitro 엔진, Server Routes, 배포까지 실전 예제로 정리했습니다.

실무 경험 공유: 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"]

정리 및 체크리스트

핵심 요약

  • Nuxt 3: Vue 3 풀스택 프레임워크
  • Auto-imports: 자동 import
  • Nitro: 빠른 서버 엔진
  • useFetch: 데이터 페칭
  • Server Routes: API 내장
  • SSR/SSG: 렌더링 모드 선택

구현 체크리스트

  • Nuxt 3 프로젝트 생성
  • 페이지 라우팅 구현
  • useFetch로 데이터 페칭
  • Server Routes 작성
  • Middleware 구현
  • Layouts 설정
  • 배포

같이 보면 좋은 글

  • Vue 3 Composition API 가이드
  • Next.js App Router 가이드
  • SvelteKit 완벽 가이드

이 글에서 다루는 키워드

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

자주 묻는 질문 (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. 네, Nuxt 3는 안정적이며 많은 기업에서 사용합니다.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3