nuxt-expert

star 5

Nuxt 3/4: Pages, composables, server routes, data fetching (useFetch/useAsyncData), Nitro, auto-imports, modules.

mikailustuner By mikailustuner schedule Updated 6/3/2026

name: nuxt-expert description: "Nuxt 3/4: Pages, composables, server routes, data fetching (useFetch/useAsyncData), Nitro, auto-imports, modules." triggers: extensions: [".vue", ".ts"] filenames: ["nuxt.config.ts", "nuxt.config.js"] directories: ["pages/", "composables/", "server/api/"] keywords: ["nuxt", "useFetch", "useAsyncData", "defineNuxtConfig", "NuxtPage", "NuxtLayout", "useRoute", "navigateTo"] auto_load_when: "Building a Nuxt application" agent: frontend-ops tools: ["Read", "Write", "Bash"]

Nuxt 3/4 Patterns

Version: Nuxt 3.x | Focus: Pages, composables, server routes, data fetching, modules


1. Project Structure

nuxt-app/
├── pages/           ← file-based routing
│   ├── index.vue
│   └── posts/
│       ├── index.vue        (/posts)
│       └── [id].vue         (/posts/:id)
├── components/      ← auto-imported
├── composables/     ← auto-imported (useXxx)
├── server/
│   ├── api/         ← API routes (Nitro)
│   └── middleware/
├── layouts/         ← page layouts
├── middleware/      ← route middleware
├── plugins/         ← Nuxt plugins
└── nuxt.config.ts

2. Data Fetching

<!-- pages/posts/[id].vue -->
<script setup lang="ts">
const route = useRoute();

// useFetch: auto-fetches, SSR-compatible, deduplicates
const { data: post, error, refresh } = await useFetch(`/api/posts/${route.params.id}`, {
  key: `post-${route.params.id}`,   // cache key
  pick: ['id', 'title', 'body'],    // only fetch needed fields
});

// useAsyncData: for custom logic
const { data: user } = await useAsyncData('user', () => $fetch('/api/me'));

// Client-only fetch (no SSR)
const { data } = useFetch('/api/dashboard', { server: false });
</script>

<template>
  <div>
    <h1>{{ post?.title }}</h1>
    <p>{{ post?.body }}</p>
    <button @click="refresh">Refresh</button>
  </div>
</template>

3. Server Routes (Nitro)

// server/api/posts/index.get.ts
import { defineEventHandler, getQuery } from 'h3';
export default defineEventHandler(async (event) => {
  const query = getQuery(event);
  return db.post.findMany({ skip: Number(query.page) * 20, take: 20 });
});

// server/api/posts/[id].put.ts
import { defineEventHandler, readBody, getRouterParam } from 'h3';
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id');
  const body = await readBody(event);
  return db.post.update({ where: { id }, data: body });
});

// server/api/auth/login.post.ts
export default defineEventHandler(async (event) => {
  const { email, password } = await readBody(event);
  // auth logic...
  setCookie(event, 'session', sessionToken, { httpOnly: true, secure: true });
  return { ok: true };
});

4. Composables (Auto-Imported)

// composables/useCounter.ts
export function useCounter(initial = 0) {
  const count = ref(initial);
  const increment = () => count.value++;
  const decrement = () => count.value--;
  return { count: readonly(count), increment, decrement };
}

// composables/useUser.ts
export function useUser() {
  // useCookie is auto-imported
  const token = useCookie<string>('auth_token');
  const { data: user } = useFetch('/api/me', {
    headers: computed(() => ({ Authorization: `Bearer ${token.value}` })),
    watch: [token], // re-fetch when token changes
  });
  return { user, token };
}

5. Route Middleware

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

// pages/dashboard.vue — apply middleware
definePageMeta({ middleware: ['auth'] });

6. nuxt.config.ts

export default defineNuxtConfig({
  modules: ['@nuxtjs/tailwindcss', '@nuxt/image', '@pinia/nuxt', 'nuxt-auth-utils'],
  runtimeConfig: {
    // Server-only (env vars)
    dbUrl: process.env.DATABASE_URL,
    // Public (accessible in browser)
    public: { apiBase: '/api' },
  },
  nitro: {
    preset: 'vercel',  // or 'node-server', 'cloudflare-pages'
  },
  experimental: { typedPages: true },
});

Quick Reference

Need API
Fetch data (SSR) useFetch('/api/...')
Custom async logic useAsyncData('key', () => ...)
API endpoint server/api/[name].[method].ts
Route params useRoute().params.id
Query params useRoute().query.page
Redirect navigateTo('/path')
Cookies useCookie('name')
State useState('key', () => defaultVal)

Anti-Patterns

❌ Using axios/fetch directly in components for initial data
✅ useFetch handles SSR, deduplication, and caching automatically

❌ Not specifying key in useFetch inside loops
✅ Always unique key: useFetch(url, { key: `item-${id}` })

❌ Server routes in pages/ directory
✅ Server routes must be in server/api/ (Nitro)

❌ Mutating useRoute() directly for navigation
✅ navigateTo('/path') or router.push() for programmatic nav

🌍 Universal Language Support

  • Turkish Native: This skill natively supports Turkish. If the user prompt is in Turkish, all analysis, formatting, and output MUST be entirely in Turkish.
Install via CLI
npx skills add https://github.com/mikailustuner/OmniRule --skill nuxt-expert
Repository Details
star Stars 5
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
mikailustuner
mikailustuner Explore all skills →