본문으로 건너뛰기
Previous
Next
Nuxt 3 Complete Guide | Full-Stack Vue.js Framework

Nuxt 3 Complete Guide | Full-Stack Vue.js Framework

Nuxt 3 Complete Guide | Full-Stack Vue.js Framework

이 글의 핵심

Nuxt 3 is the full-stack Vue.js framework — file-based routing, server routes, composables, and automatic SSR/SSG. This guide covers the Nuxt 3 ecosystem with practical examples for building production apps.

What This Guide Covers

Nuxt 3 brings full-stack capabilities to Vue — file-based routing, server API routes, universal data fetching, and automatic SSR/SSG. This guide covers the core features with a practical blog/app example.

Real-world insight: Migrating a Vue SPA to Nuxt 3 cut Time to First Byte by 70% and improved Core Web Vitals scores enough to boost organic traffic by 40% in two months.


Setup

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

App runs on http://localhost:3000.


Project Structure

my-app/
  pages/           ← file-based routing
  components/      ← auto-imported components
  composables/     ← auto-imported composables (useX)
  server/
    api/           ← server API routes
    middleware/    ← server middleware
  layouts/         ← page layouts
  middleware/      ← client/universal route middleware
  plugins/         ← Nuxt plugins
  public/          ← static assets
  nuxt.config.ts

1. File-Based Routing

Create files in pages/ — Nuxt generates routes automatically:

pages/
  index.vue          → /
  about.vue          → /about
  blog/
    index.vue        → /blog
    [slug].vue       → /blog/:slug
  users/
    [id]/
      index.vue      → /users/:id
      posts.vue      → /users/:id/posts
  [...404].vue       → catch-all / 404
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`)
</script>

<template>
  <article>
    <h1>{{ post?.title }}</h1>
    <p>{{ post?.body }}</p>
  </article>
</template>

2. Server Routes (API)

Create files in server/api/ — Nuxt generates server endpoints:

// server/api/posts/index.get.ts
export default defineEventHandler(async (event) => {
  // Access DB, external API, etc.
  return [
    { id: 1, title: 'Nuxt 3 Guide', slug: 'nuxt-3-guide' },
    { id: 2, title: 'Vue Composables', slug: 'vue-composables' },
  ]
})

// server/api/posts/[slug].get.ts
export default defineEventHandler(async (event) => {
  const slug = getRouterParam(event, 'slug')
  // Fetch from DB...
  return { id: 1, title: 'Nuxt 3 Guide', slug, body: '...' }
})

// server/api/posts/index.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  // Validate and save...
  return { created: body }
})

File naming convention: [name].[method].ts — GET, POST, PUT, DELETE, PATCH.


3. Data Fetching

<script setup lang="ts">
// useFetch: SSR-aware, cached, auto-typed
const { data, pending, error, refresh } = await useFetch('/api/posts')

// $fetch: manual fetch (works client and server side)
const post = await $fetch('/api/posts/my-slug')

// useAsyncData: custom async logic
const { data: user } = await useAsyncData('user', () =>
  $fetch(`/api/users/${userId}`)
)

// lazy loading (don't block navigation)
const { data: comments, pending } = useLazyFetch('/api/comments')
</script>

<template>
  <div>
    <div v-if="pending">Loading...</div>
    <ul v-else>
      <li v-for="post in data" :key="post.id">{{ post.title }}</li>
    </ul>
  </div>
</template>

useFetch automatically shares data between server and client — no duplicate requests.


4. Composables

Composables in composables/ are auto-imported anywhere in your app:

// composables/useAuth.ts
export function useAuth() {
  const user = useState<User | null>('auth-user', () => null)
  const isLoggedIn = computed(() => user.value !== null)

  async function login(email: string, password: string) {
    const data = await $fetch('/api/auth/login', {
      method: 'POST',
      body: { email, password },
    })
    user.value = data.user
    await navigateTo('/dashboard')
  }

  async function logout() {
    await $fetch('/api/auth/logout', { method: 'POST' })
    user.value = null
    await navigateTo('/login')
  }

  return { user, isLoggedIn, login, logout }
}
<script setup lang="ts">
// No import needed — auto-imported
const { user, isLoggedIn, logout } = useAuth()
</script>

5. Layouts

<!-- layouts/default.vue -->
<template>
  <div>
    <AppHeader />
    <main>
      <slot />  <!-- page content goes here -->
    </main>
    <AppFooter />
  </div>
</template>

<!-- layouts/dashboard.vue -->
<template>
  <div class="dashboard">
    <Sidebar />
    <main><slot /></main>
  </div>
</template>
<!-- pages/dashboard/index.vue -->
<script setup lang="ts">
definePageMeta({ layout: 'dashboard' })
</script>

6. Middleware

// middleware/auth.ts (client + server)
export default defineNuxtRouteMiddleware((to, from) => {
  const { isLoggedIn } = useAuth()
  if (!isLoggedIn.value && to.path !== '/login') {
    return navigateTo('/login')
  }
})
<!-- pages/dashboard/index.vue -->
<script setup lang="ts">
definePageMeta({ middleware: 'auth' })
</script>

Server middleware (runs on every request):

// server/middleware/logger.ts
export default defineEventHandler((event) => {
  console.log(`[${new Date().toISOString()}] ${event.method} ${event.path}`)
})

7. State Management

Nuxt’s useState shares state between server and client:

// composables/useCounter.ts
export const useCounter = () => useState('counter', () => 0)
<script setup lang="ts">
const count = useCounter()
</script>

<template>
  <button @click="count++">Count: {{ count }}</button>
</template>

For complex state, use Pinia (Nuxt’s recommended store):

npx nuxi module add pinia
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])
  const total = computed(() => items.value.reduce((sum, i) => sum + i.price, 0))

  function addItem(item: CartItem) {
    items.value.push(item)
  }

  return { items, total, addItem }
})

8. SEO and Meta

<script setup lang="ts">
useSeoMeta({
  title: 'My Page Title',
  ogTitle: 'My Page Title',
  description: 'This is a description of my page.. Nuxt 3 Complete Guide에 대한 완전한 가이드입니다. 실전 예제와 함께 핵심 개념부터 고급 활용까지 다룹니다.',
  ogDescription: 'This is a description of my page.',
  ogImage: 'https://example.com/image.png',
  twitterCard: 'summary_large_image',
})

// Dynamic meta
const { data: post } = await useFetch(`/api/posts/${slug}`)
useSeoMeta({
  title: () => post.value?.title,
  description: () => post.value?.excerpt,
})
</script>

9. nuxt.config.ts

export default defineNuxtConfig({
  // Rendering mode
  ssr: true,  // SSR (default)

  // Nitro server config
  nitro: {
    preset: 'cloudflare-pages',  // or 'vercel', 'netlify', etc.
  },

  // Runtime config (env vars)
  runtimeConfig: {
    // Server-only (secret)
    databaseUrl: process.env.DATABASE_URL,
    // Public (exposed to client)
    public: {
      apiBase: process.env.API_BASE_URL || '/api',
    },
  },

  // Modules
  modules: ['@pinia/nuxt', '@nuxtjs/tailwindcss', '@nuxt/image'],

  // TypeScript
  typescript: { strict: true },
})

Access runtime config:

const config = useRuntimeConfig()
console.log(config.public.apiBase)     // client or server
console.log(config.databaseUrl)        // server only

10. Deployment

# Build for production
npm run build

# Deploy to Node.js server
node .output/server/index.mjs

# Deploy to Cloudflare Pages
NITRO_PRESET=cloudflare-pages npm run build
# Upload .output/public to Cloudflare Pages

# Static generation (SSG)
npm run generate
# Upload .output/public anywhere (S3, GitHub Pages, etc.)

Key Takeaways

FeatureHow
RoutingFile-based in pages/
Server APIFiles in server/api/
Data fetchinguseFetch, useAsyncData, $fetch
StateuseState (built-in), Pinia (recommended)
Auto-importscomponents/, composables/ — no imports needed
SEOuseSeoMeta()
DeploymentAny platform via Nitro presets

Nuxt 3’s key strength is reducing boilerplate — auto-imports, file-based routing, and built-in SSR mean you focus on features, not configuration. If you know Vue 3, Nuxt 3 is a natural upgrade for any project that needs SEO or a backend.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Build full-stack apps with Nuxt 3. Covers file-based routing, server routes, composables, Nitro server, data fetching, a… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

Nuxt, Vue, JavaScript, SSR, Full-Stack, TypeScript 등으로 검색하시면 이 글이 도움이 됩니다.