목록으로
AI Skills로 Vue 개발하기

AI Skills로 Vue 개발하기 (5) - Nuxt 3로 풀스택 전환하기

2026년 02월 19일13 min read
#Vue#Claude Code#AI#Nuxt#SSR#Nitro

목차 보기
시리즈 목차 보기5 / 6

이전 편 요약

4편에서는 Vite 설정 최적화와 Vitest 테스트 환경을 구성했다. Build-tooling 스킬이 테스트 패턴과 Pinia 격리 같은 세부사항을 자동으로 잡아주면서, 코드 품질을 유지하는 데 들어가는 비용이 줄었다. 이번 편에서는 Vue SPA를 Nuxt 3로 확장하고, Design 스킬을 활용해 UI 품질을 높이는 과정을 다룬다.

Vue SPA에서 Nuxt로

Nuxt 3는 Vue 3 기반의 풀스택 프레임워크다. Vue SPA만으로 충분한 프로젝트도 있지만, 특정 시점에 한계가 찾아온다. SEO가 필요하거나, 초기 로딩 속도가 중요하거나, 서버 사이드에서 데이터를 처리해야 할 때다. 이런 요구사항이 하나라도 생기면 SSR을 고려하게 되는데, Nuxt는 그 전환 비용을 최소화해준다.

Nuxt 스킬은 디렉토리 규약, 데이터 페칭 패턴, 서버 라우트, 배포 전략을 가이드한다. "Nuxt 프로젝트로 전환해줘"라고 하면 스킬이 기존 코드 구조를 분석해서 Nuxt 디렉토리 규약에 맞는 재배치 방안을 제시해주는 식이다.

Nuxt의 auto-imports가 가장 먼저 체감되는 차이였다. composables/ 폴더에 파일을 넣으면 import 문 없이 어디서든 사용할 수 있다. components/ 폴더도 마찬가지다. 1~4편에서 작성했던 수십 줄의 import 구문이 사라지는 경험은 생각보다 쾌적했다.

Nuxt 디렉토리 규약

1~4편에서 만든 코드가 Nuxt 구조로 재배치되면 아래와 같은 형태가 된다.

project/
  pages/
    index.vue
    login.vue
    dashboard.vue
  components/
    DataTable.vue
    StatCard.vue
  composables/
    useTableSort.ts
  stores/
    auth.ts
    product.ts
  server/
    api/
      auth/
        login.post.ts
      dashboard/
        stats.get.ts
        recent.get.ts
  nuxt.config.ts

가장 큰 변화는 router/index.ts가 사라진다는 점이다. Nuxt에서는 pages/ 디렉토리의 파일 구조가 곧 라우팅이다. pages/dashboard.vue를 만들면 /dashboard 경로가 자동 생성된다. 3편에서 작성했던 라우트 정의와 lazy import 설정이 통째로 불필요해진다.

composables/ 디렉토리에 넣은 파일은 auto-import 대상이 된다. 2편의 useTableSort를 이 폴더에 넣으면 어떤 컴포넌트에서든 import 없이 바로 호출할 수 있다. 단, auto-import는 편리하지만 남용하면 코드의 의존 관계가 불투명해지는 단점이 있다. 팀 규모가 커질수록 명시적 import를 유지할지 auto-import를 쓸지 합의가 필요하다.

Nitro 서버 API

Nuxt 3의 서버 엔진인 Nitro는 server/api/ 디렉토리에 파일을 만드는 것만으로 백엔드 엔드포인트를 생성한다. 파일명에 HTTP 메서드를 붙이는 규칙이 있다. login.post.ts는 POST 요청, stats.get.ts는 GET 요청을 처리한다.

3편에서 클라이언트 사이드에 있던 인증 로직을 서버로 이동시킬 수 있다. JWT 생성, 비밀번호 검증, httpOnly 쿠키 설정 같은 민감한 처리가 브라우저에서 완전히 분리되는 것이다.

ts
// server/api/auth/login.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody<{ email: string, password: string }>(event)

  const user = await db.user.findByEmail(body.email)
  if (!user || !await verifyPassword(body.password, user.passwordHash))
    throw createError({ statusCode: 401, message: '인증 실패' })

  const token = generateJWT({ userId: user.id })
  setCookie(event, 'auth-token', token, { httpOnly: true, secure: true })

  return { user: { id: user.id, name: user.name, email: user.email } }
})

defineEventHandler는 Nitro가 제공하는 핸들러 정의 함수다. readBody로 요청 본문을 파싱하고, createError로 에러 응답을 생성하고, setCookie로 쿠키를 설정한다. 전부 Nitro의 내장 유틸리티이므로 별도 패키지 설치가 필요 없다. Express에서 body-parser, cookie-parser를 따로 설치하던 것과 비교하면 초기 설정 비용이 상당히 줄어든다.

useAsyncData와 useFetch

Nuxt의 데이터 페칭 composable은 SSR과 클라이언트 사이드를 자동으로 처리한다. 서버에서 렌더링할 때 데이터를 가져오고, 클라이언트로 전달할 때 중복 요청을 방지하는 hydration 최적화가 내장되어 있다.

useFetchuseAsyncData$fetch를 합친 편의 함수다. 대부분의 경우 useFetch로 충분하지만, API 호출 외의 비동기 작업(예: IndexedDB 조회)에는 useAsyncData를 직접 사용해야 한다. 선택 기준은 단순하다. URL 기반 데이터 페칭이면 useFetch, 그 외 비동기 작업이면 useAsyncData다.

vue
<script setup lang="ts">
// useFetch로 서버 API 호출 - SSR과 클라이언트를 자동 처리
const { data: products, status } = await useFetch('/api/products', {
  query: { category: 'electronics' },
  transform: (response) => response.items,
})
</script>

<template>
  <div>
    <p v-if="status === 'pending'">로딩 중...</p>
    <DataTable v-else :items="products ?? []" :columns="columns" />
  </div>
</template>

transform 옵션으로 응답 데이터를 가공할 수 있다. payload 크기를 줄여서 SSR에서 클라이언트로 전달하는 데이터를 최소화하는 데도 유용하다. 2편에서 만든 DataTable을 그대로 사용할 수 있다는 점도 Nuxt 전환의 이점이다. 컴포넌트 코드를 수정할 필요 없이 데이터 소스만 바뀐다.

Design 스킬과 UI 품질

Claude Code에는 Design 스킬과 Frontend-design 스킬이 있다. 코드를 생성하는 것과 좋은 디자인을 만드는 것은 별개의 문제인데, 이 스킬들이 그 간극을 줄여준다.

AI가 UI를 생성할 때 흔히 빠지는 함정이 있다. 이른바 "AI slop"이라고 부르는 패턴이다. 과도한 보라색 그라디언트, 어디서나 보이는 Inter 폰트, 예측 가능한 카드 레이아웃, 뭉툭한 rounded-xl 모서리. AI로 만든 페이지를 보면 "아, 이거 AI가 만들었구나"라고 바로 알 수 있는 이유가 이 뻔한 디자인 패턴 때문이다.

Design 스킬의 핵심 원칙은 네 단계로 요약된다. Purpose(목적), Tone(톤), Constraints(제약), Differentiation(차별화)이다. 먼저 이 페이지가 왜 존재하는지 정의하고, 어떤 분위기를 전달할지 결정하고, 기술적 제약을 명시하고, 마지막으로 대담한 미학 방향을 선택한다.

스킬이 제안하는 미학 방향은 구체적이다. 극도의 미니멀리즘, Brutalist, Editorial, Swiss Grid 같은 선택지를 제시하고, 각 방향에 맞는 구체적인 CSS 패턴을 적용한다. 배경에 단색을 쓰지 않고 질감, 그래디언트 메시, 노이즈, 기하학적 패턴을 활용하라는 원칙도 포함되어 있다. "예쁘게 만들어줘" 같은 모호한 지시가 아니라 구조화된 디자인 의사결정 프레임워크를 제공하는 것이다.

스킬 적용 전후 차이

스킬 없이 "대시보드 만들어줘"라고 하면, 흰 배경에 카드 네 개가 나란히 놓이고, 각 카드에 rounded-lg와 shadow-md가 적용된 전형적인 레이아웃이 나온다. 스킬을 적용하면 정보 밀도, 시각적 계층, 여백 비율이 달라진다. 같은 데이터를 보여주더라도 사용자가 먼저 봐야 하는 요소가 무엇인지, 시선이 어디서 어디로 흘러야 하는지를 고려한 레이아웃이 생성된다.

실전: 대시보드 페이지

Nuxt + Nitro + DataTable을 통합한 대시보드 페이지다. 3편의 인증 미들웨어와 2편의 DataTable이 Nuxt 구조 안에서 자연스럽게 합쳐진다.

vue
<script setup lang="ts">
// 인증 미들웨어 적용 - 미로그인 시 /login으로 리다이렉트
definePageMeta({ middleware: 'auth' })

// 서버 API에서 통계 데이터와 최근 항목을 병렬 조회
const { data: stats } = await useFetch('/api/dashboard/stats')
const { data: recentItems } = await useFetch('/api/dashboard/recent')
</script>

<template>
  <div class="grid grid-cols-3 gap-6 p-8">
    <StatCard
      v-for="stat in stats"
      :key="stat.label"
      :label="stat.label"
      :value="stat.value"
      :trend="stat.trend"
    />
    <div class="col-span-3">
      <DataTable :items="recentItems ?? []" :columns="dashboardColumns" />
    </div>
  </div>
</template>

definePageMeta로 미들웨어를 선언하면, 3편에서 router.beforeEach로 처리하던 인증 가드가 페이지 단위로 분리된다. 라우터 파일에 모든 가드 로직을 몰아넣을 필요 없이, 각 페이지가 자신의 접근 제어를 선언하는 구조다.

useFetch 호출이 두 개 있지만 Nuxt가 내부적으로 병렬 처리한다. SSR 시점에 두 API를 동시에 호출하고, 결과를 HTML에 직렬화해서 클라이언트에 전달한다. 클라이언트에서는 이미 받은 데이터를 재사용하므로 추가 네트워크 요청이 발생하지 않는다.

시리즈 코드의 흐름

다섯 편에 걸쳐 코드가 어떻게 발전했는지 정리하면 이렇다.

1편에서 프로젝트 기반을 잡았다. ESLint, TypeScript, pnpm 설정이 여기서 결정됐다. 2편에서 DataTable과 useTableSort를 만들면서 컴포넌트-composable 분리 패턴이 확립됐다. 3편에서 Pinia store와 Router 가드로 상태 관리와 인증 흐름을 구성했다. 4편에서 Vite와 Vitest로 빌드와 테스트 환경을 갖췄다.

이번 5편에서 이 모든 코드가 Nuxt 안으로 들어왔다. router/index.ts는 pages/ 디렉토리 규약으로 대체됐고, 클라이언트 사이드 인증은 서버 API로 이동했고, 직접 작성하던 import 문은 auto-imports가 처리한다. 각 편에서 만든 코드의 핵심 로직은 변하지 않았지만, 구조적인 보일러플레이트가 상당히 줄어들었다.

정리

Vue SPA를 Nuxt 3로 확장하면서 파일 기반 라우팅, Nitro 서버 API, 데이터 페칭 composable을 적용했다. Design 스킬로 AI 생성 UI의 품질을 끌어올리는 방법도 살펴봤다. Nuxt 스킬이 디렉토리 규약과 데이터 페칭 패턴을 가이드해주면서, 프레임워크 전환에 드는 학습 비용이 크게 줄었다.

마지막 편에서는 VitePress 문서화와 스킬 시스템 전체 회고를 다룬다.

검색...

검색...