Skip to content
All essays
WebMarch 27, 202515 min

Nuxt 3: The Vue Meta-Framework

Master Nuxt 3 for building server-side rendered Vue applications. Learn file-based routing, server API routes, and auto-imports.

Ü
Ümit Uz
Mobile & Full Stack Developer

Nuxt 3 is a powerful meta-framework built on Vue 3 that provides server-side rendering, file-based routing, and many other features out of the box.

Why Nuxt 3?

Nuxt 3 offers several advantages over plain Vue:

  • Server-Side Rendering (SSR) for better SEO and performance
  • File-based routing - no need to configure routes manually
  • Auto-imports - use components and composables without imports
  • API routes - build full-stack applications
  • Nitro engine - fast and lightweight server

Getting Started

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

Directory Structure

my-nuxt-app/
├── .nuxt/           # Nuxt build files
├── assets/          # CSS, images, fonts
├── components/      # Vue components
├── composables/     # Vue composables
├── layouts/         # Layout components
├── middleware/      # Route middleware
├── pages/           # File-based routing
├── plugins/         # Vue plugins
├── public/          # Static files
├── server/          # Server API routes
└── nuxt.config.ts   # Nuxt configuration

File-Based Routing

Create pages in the pages/ directory:

vue
<!-- pages/index.vue -->
<template>
  <div>
    <h1>Home Page</h1>
  </div>
</template>

<!-- pages/about.vue -->
<template>
  <div>
    <h1>About Us</h1>
  </div>
</template>

<!-- pages/blog/[slug].vue -->
<template>
  <div>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
  </div>
</template>

<script setup>
const { slug } = useRoute().params
const { data: post } = await useFetch(`/api/blog/${slug}`)
</script>

Auto-Imports

Nuxt automatically imports:

  • Vue components
  • Composables from composables/
  • Utility functions from utils/
  • useFetch, useAsyncData, useRoute, useRouter, etc.
vue
<template>
  <div>
    <!-- Component auto-imported from components/Header.vue -->
    <Header />

    <!-- Composable auto-imported from composables/useCounter.js -->
    <button @click="increment">{{ count }}</button>
  </div>
</template>

<script setup>
const { count, increment } = useCounter()
</script>

Data Fetching

useFetch()

vue
<script setup>
const { data: users, pending, error } = await useFetch('/api/users')

// With options
const { data: post } = await useFetch(`/api/posts/${id}`, {
  key: `post-${id}`,
  transform: (data) => data.post,
  watch: [id]
})
</script>

useAsyncData()

vue
<script setup>
const { data, pending } = await useAsyncData('users', () =>
  $fetch('/api/users')
)
</script>

Server API Routes

Create API endpoints in server/api/:

javascript
// server/api/hello.ts
export default defineEventHandler((event) => {
  return { message: 'Hello from Nuxt!' }
})

// server/api/users/[id].ts
export default defineEventHandler((event) => {
  const id = getRouterParam(event, 'id')
  return { user: { id, name: 'John' } }
})

// server/api/post.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  // Process POST request
  return { success: true }
})

Composables

javascript
// composables/useUsers.js
export function useUsers() {
  const { data: users, pending, error } = useFetch('/api/users')

  const getUserById = (id) => {
    return users.value.find(user => user.id === id)
  }

  return {
    users,
    pending,
    error,
    getUserById
  }
}

Middleware

javascript
// middleware/auth.js
export default defineNuxtRouteMiddleware((to, from) => {
  const isAuthenticated = useCookie('token').value

  if (!isAuthenticated) {
    return navigateTo('/login')
  }
})

// Usage in page
<template>
  <div>Protected content</div>
</template>

<script setup>
definePageMeta({
  middleware: 'auth'
})
</script>

Layouts

vue
<!-- layouts/default.vue -->
<template>
  <div>
    <Header />
    <slot />
    <Footer />
  </div>
</template>

<!-- layouts/custom.vue -->
<template>
  <div class="custom-layout">
    <slot />
  </div>
</template>

<!-- Usage in page -->
<template>
  <div>
    <h1>Custom Layout Page</h1>
  </div>
</template>

<script setup>
definePageMeta({
  layout: 'custom'
})
</script>

State Management with Pinia

javascript
// stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    user: null,
    token: null
  }),

  getters: {
    isAuthenticated: (state) => !!state.token
  },

  actions: {
    setUser(user, token) {
      this.user = user
      this.token = token
    },

    logout() {
      this.user = null
      this.token = null
    }
  }
})

Configuration

typescript
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt', '@nuxtjs/tailwindcss'],

  app: {
    head: {
      title: 'My Nuxt App',
      meta: [
        { name: 'description', content: 'My amazing Nuxt app' }
      ]
    }
  },

  runtimeConfig: {
    public: {
      apiBase: '/api'
    },
    private: {
      apiSecret: process.env.API_SECRET
    }
  },

  css: ['~/assets/css/main.css'],

  vite: {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: `@use "@/assets/scss/variables" as *;`
        }
      }
    }
  }
})

Deployment

Build for production:

bash
npm run build

Deploy to Vercel, Netlify, or any Node.js hosting platform.

Nuxt 3 provides a complete solution for building modern Vue applications with SSR, API routes, and excellent developer experience.

Related essays

Next essay
Pinia State Management in Vue 3