10 промтов для Vue.js и Nuxt: компоненты, composables и роутинг

Введение

Vue.js и его метафреймворк Nuxt остаются одними из самых востребованных инструментов для создания современных веб-приложений в 2026 году. По данным опроса State of JS 2025, Vue.js удерживает долю около 18% среди фронтенд-фреймворков, а Nuxt занимает лидирующие позиции среди метафреймворков для SSR (серверного рендеринга). Однако даже опытные разработчики часто тратят часы на рутинные задачи: написание boilerplate-кода для компонентов, настройку composables или организацию роутинга.

Здесь на помощь приходят промты — специально сформулированные запросы к AI, которые генерируют готовый код, документацию или архитектурные решения. В этой статье я собрал 10 промтов, которые покрывают ключевые сценарии работы с Vue.js и Nuxt: от создания компонентов с TypeScript до настройки Pinia store и динамических маршрутов. Каждый промт содержит задачу, сам запрос и пример результата.

1. Базовые промты для компонентов Vue.js

Промт 1: Генерация компонента с TypeScript и props

Задача: Создать компонент кнопки с поддержкой различных вариантов (primary, secondary, outline) и TypeScript-типизацией.

Промт:

Напиши Vue 3 компонент на Composition API с TypeScript для кнопки. Компонент должен принимать props: variant (primary, secondary, outline), size (sm, md, lg), disabled (boolean). Используй scoped стили и emit для события click. Добавь обработку состояния loading.

Пример результата:

<template>
  <button
    :class="['btn', `btn--${variant}`, `btn--${size}`, { 'btn--loading': loading }]"
    :disabled="disabled || loading"
    @click="$emit('click', $event)"
  >
    <span v-if="loading" class="btn__spinner"></span>
    <slot />
  </button>
</template>

<script setup lang="ts">
interface Props {
  variant?: 'primary' | 'secondary' | 'outline';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  loading?: boolean;
}

withDefaults(defineProps<Props>(), {
  variant: 'primary',
  size: 'md',
  disabled: false,
  loading: false
});

defineEmits(['click']);
</script>

<style scoped>
.btn { display: inline-flex; align-items: center; gap: 8px; }
.btn--primary { background: #3498db; color: white; }
.btn--secondary { background: #2ecc71; color: white; }
.btn--outline { border: 2px solid #3498db; color: #3498db; background: transparent; }
.btn--sm { padding: 4px 12px; font-size: 14px; }
.btn--md { padding: 8px 16px; font-size: 16px; }
.btn--lg { padding: 12px 24px; font-size: 18px; }
.btn--loading { opacity: 0.7; cursor: not-allowed; }
.btn__spinner { width: 16px; height: 16px; border: 2px solid white; border-top-color: transparent; border-radius: 50%; animation: spin 0.6s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>

Промт 2: Создание composable для работы с API

Задача: Написать composable для выполнения HTTP-запросов с обработкой загрузки, ошибок и отмены запроса.

Промт:

Создай useFetch composable для Vue 3 с TypeScript. Он должен принимать URL и опции (method, headers, body). Возвращать: data, error, isLoading, execute (функция для повторного запроса). Используй AbortController для отмены запроса. Добавь автоматическую очистку при unmount компонента.

Пример результата:

import { ref, onUnmounted } from 'vue';

interface UseFetchOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
  headers?: Record<string, string>;
  body?: unknown;
}

export function useFetch<T = unknown>(url: string, options?: UseFetchOptions) {
  const data = ref<T | null>(null);
  const error = ref<string | null>(null);
  const isLoading = ref(false);

  let abortController: AbortController | null = null;

  const execute = async () => {
    isLoading.value = true;
    error.value = null;
    abortController = new AbortController();

    try {
      const response = await fetch(url, {
        method: options?.method || 'GET',
        headers: { 'Content-Type': 'application/json', ...options?.headers },
        body: options?.body ? JSON.stringify(options.body) : undefined,
        signal: abortController.signal
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      data.value = await response.json();
    } catch (err) {
      if (err instanceof Error && err.name !== 'AbortError') {
        error.value = err.message;
      }
    } finally {
      isLoading.value = false;
    }
  };

  onUnmounted(() => {
    abortController?.abort();
  });

  return { data, error, isLoading, execute };
}

2. Промты для Nuxt

Промт 3: Настройка динамических маршрутов с middleware

Задача: Создать защищённый маршрут в Nuxt 3 с проверкой аутентификации через middleware и динамическими параметрами.

Промт:

Напиши middleware для Nuxt 3, который проверяет наличие токена в localStorage. Если токена нет  редирект на /login. Также создай динамический маршрут users/[id].vue, который получает ID пользователя из route.params и отображает его профиль. Используй useFetch для загрузки данных.

Пример результата:

Файл middleware/auth.ts:

export default defineNuxtRouteMiddleware((to, from) => {
  const token = localStorage.getItem('auth_token');

  if (!token) {
    return navigateTo('/login');
  }

  // Можно добавить проверку срока действия токена
  // const decoded = jwtDecode(token);
  // if (decoded.exp < Date.now() / 1000) { ... }
});

Файл pages/users/[id].vue:

<template>
  <div>
    <h1>Профиль пользователя</h1>
    <p v-if="pending">Загрузка...</p>
    <p v-else-if="error">Ошибка: {{ error.message }}</p>
    <div v-else>
      <p>Имя: {{ user.name }}</p>
      <p>Email: {{ user.email }}</p>
    </div>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  middleware: 'auth'
});

const route = useRoute();
const { data: user, pending, error } = await useFetch(`/api/users/${route.params.id}`);
</script>

Промт 4: Генерация Pinia store для Nuxt

Задача: Создать store для управления корзиной товаров с использованием Pinia.

Промт:

Напиши Pinia store для корзины в Nuxt 3. Store должен содержать: items (массив), addItem, removeItem, clearCart, totalPrice (computed). Используй persist: true для сохранения состояния в localStorage. Добавь action для синхронизации с сервером через API.

Пример результата:

import { defineStore } from 'pinia';

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [] as Array<{ id: number; name: string; price: number; quantity: number }>,
  }),

  getters: {
    totalPrice: (state) => {
      return state.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    },
    itemCount: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0),
  },

  actions: {
    addItem(product: { id: number; name: string; price: number }) {
      const existing = this.items.find(item => item.id === product.id);
      if (existing) {
        existing.quantity++;
      } else {
        this.items.push({ ...product, quantity: 1 });
      }
    },

    removeItem(productId: number) {
      this.items = this.items.filter(item => item.id !== productId);
    },

    clearCart() {
      this.items = [];
    },

    async syncWithServer() {
      try {
        await $fetch('/api/cart/sync', {
          method: 'POST',
          body: { items: this.items }
        });
      } catch (err) {
        console.error('Sync failed:', err);
      }
    }
  },

  persist: {
    storage: persistedState.localStorage,
    key: 'cart-storage'
  }
});

3. Экспертные промты

Промт 5: Композит для виртуального скролла

Задача: Реализовать виртуальный скролл для списка из 10 000 элементов без потери производительности.

Промт:

Создай composable useVirtualScroll для Vue 3. Он должен принимать: items (массив), itemHeight (число), containerHeight (число). Возвращать: visibleItems (только видимые элементы), totalHeight, scrollTop. Используй IntersectionObserver для подгрузки.

Пример результата (упрощённо):

import { ref, computed, onMounted, onUnmounted } from 'vue';

export function useVirtualScroll<T>(items: T[], itemHeight: number, containerHeight: number) {
  const scrollTop = ref(0);
  const containerRef = ref<HTMLElement | null>(null);

  const visibleItems = computed(() => {
    const startIndex = Math.floor(scrollTop.value / itemHeight);
    const endIndex = Math.min(startIndex + Math.ceil(containerHeight / itemHeight) + 1, items.length);
    return items.slice(startIndex, endIndex).map((item, index) => ({
      item,
      style: { transform: `translateY(${(startIndex + index) * itemHeight}px)` }
    }));
  });

  const totalHeight = computed(() => items.length * itemHeight);

  const handleScroll = () => {
    if (containerRef.value) {
      scrollTop.value = containerRef.value.scrollTop;
    }
  };

  onMounted(() => {
    containerRef.value?.addEventListener('scroll', handleScroll);
  });

  onUnmounted(() => {
    containerRef.value?.removeEventListener('scroll', handleScroll);
  });

  return { containerRef, visibleItems, totalHeight };
}

Промт 6: Оптимизация производительности с keep-alive

Задача: Настроить keep-alive для сохранения состояния компонентов при навигации.

Промт:

Напиши пример использования <KeepAlive> в Nuxt 3 для страниц табов. Каждый таб — отдельный компонент, который должен сохранять своё состояние (скролл, введённые данные) при переключении. Добавь обработку onActivated и onDeactivated.

Пример результата:

<template>
  <div>
    <div class="tabs">
      <button v-for="tab in tabs" :key="tab.id" @click="activeTab = tab.id">
        {{ tab.label }}
      </button>
    </div>

    <KeepAlive :include="['TabProfile', 'TabSettings']">
      <component :is="activeComponent" />
    </KeepAlive>
  </div>
</template>

<script setup lang="ts">
const tabs = [
  { id: 'profile', label: 'Профиль', component: defineAsyncComponent(() => import('~/components/TabProfile.vue')) },
  { id: 'settings', label: 'Настройки', component: defineAsyncComponent(() => import('~/components/TabSettings.vue')) },
  { id: 'notifications', label: 'Уведомления', component: defineAsyncComponent(() => import('~/components/TabNotifications.vue')) }
];

const activeTab = ref('profile');
const activeComponent = computed(() => tabs.find(t => t.id === activeTab.value)?.component);
</script>

4. Промты для тестирования и деплоя

Промт 7: Написание unit-тестов для composable

Задача: Написать тесты для useFetch composable с использованием Vitest.

Промт:

Напиши unit-тесты для composable useFetch из промта 2. Используй Vitest и @vue/test-utils. Протестируй: успешный запрос, ошибку, отмену запроса, повторный вызов execute.

Пример результата:

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useFetch } from './useFetch';

describe('useFetch', () => {
  beforeEach(() => {
    vi.restoreAllMocks();
  });

  it('должен возвращать данные при успешном запросе', async () => {
    const mockData = { id: 1, name: 'Test' };
    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve(mockData)
    });

    const { data, execute } = useFetch('/api/test');
    await execute();

    expect(data.value).toEqual(mockData);
  });

  it('должен устанавливать ошибку при неудачном запросе', async () => {
    global.fetch = vi.fn().mockResolvedValue({
      ok: false,
      status: 404
    });

    const { error, execute } = useFetch('/api/nonexistent');
    await execute();

    expect(error.value).toBe('HTTP error! status: 404');
  });
});

5. Практические советы

Промт 8: Интеграция с внешним API

Задача: Подключить Vue.js приложение к REST API (например, JSONPlaceholder).

Промт:

Создай страницу в Nuxt 3, которая загружает и отображает список постов из JSONPlaceholder (https://jsonplaceholder.typicode.com/posts). Используй useFetch с серверным рендерингом. Добавь пагинацию и поиск по заголовку.

Пример результата:

<template>
  <div>
    <input v-model="searchQuery" placeholder="Поиск по заголовку..." />

    <div v-for="post in filteredPosts" :key="post.id" class="post">
      <h3>{{ post.title }}</h3>
      <p>{{ post.body }}</p>
    </div>

    <button @click="page--" :disabled="page === 1">Назад</button>
    <span>Страница {{ page }}</span>
    <button @click="page++">Вперёд</button>
  </div>
</template>

<script setup lang="ts">
const page = ref(1);
const searchQuery = ref('');

const { data: posts } = await useFetch('https://jsonplaceholder.typicode.com/posts', {
  query: { _page: page, _limit: 10 }
});

const filteredPosts = computed(() => {
  if (!searchQuery.value) return posts.value;
  return posts.value?.filter(post => 
    post.title.toLowerCase().includes(searchQuery.value.toLowerCase())
  );
});
</script>

Промт 9: Использование provide/inject для глобального состояния

Задача: Передать глобальные настройки темы из корневого компонента во все дочерние.

Промт:

Реализуй provide/inject для темы в Nuxt 3. Корневой компонент предоставляет объект theme с цветами. Дочерние компоненты получают его через inject. Добавь возможность переключать тему (светлая/тёмная).

Пример результата:
Файл app.vue:

<template>
  <div :class="['app', `theme--${currentTheme}`]">
    <NuxtPage />
    <button @click="toggleTheme">Переключить тему</button>
  </div>
</template>

<script setup lang="ts">
const currentTheme = ref('light');

const theme = computed(() => ({
  colors: currentTheme.value === 'light' 
    ? { background: '#fff', text: '#333' }
    : { background: '#333', text: '#fff' }
}));

provide('theme', theme);

const toggleTheme = () => {
  currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light';
};
</script>

Промт 10: Деплой Nuxt приложения на Vercel

Задача: Настроить деплой Nuxt 3 приложения на Vercel с автоматическими сборками.

Промт:

Опиши конфигурацию для деплоя Nuxt 3 на Vercel. Включи настройки для serverless функций, переменные окружения и автоматический деплой из GitHub.

Пример результата:
Файл nuxt.config.ts:

export default defineNuxtConfig({
  nitro: {
    preset: 'vercel'
  },
  runtimeConfig: {
    public: {
      apiBase: process.env.API_BASE_URL || '/api'
    }
  }
});

Файл vercel.json:

{
  "buildCommand": "nuxt build",
  "outputDirectory": "dist",
  "framework": "nuxtjs",
  "regions": ["iad1"],
  "env": {
    "NUXT_PUBLIC_API_BASE": "/api"
  }
}

Заключение

Эти 10 промтов покрывают 80% типовых задач, с которыми сталкивается разработчик на Vue.js и Nuxt. Используйте их как отправную точку: адаптируйте под свои проекты, добавляйте типы, тесты и документацию. Помните, что AI — это инструмент, который ускоряет написание boilerplate-кода, но не заменяет понимания архитектуры.

Для работы с реальными API (например, JSONPlaceholder или вашими собственными) важно правильно настроить проксирование запросов и обработку ошибок. ASI Biont поддерживает подключение к JSONPlaceholder через API — подробнее на asibiont.com/courses.

Следите за обновлениями Vue.js и Nuxt — экосистема активно развивается, и новые версии приносят ещё больше возможностей для оптимизации и упрощения разработки.

← Все статьи

Комментарии

Читайте также

Meta тихо запускает vibe-coded игровое приложение Pocket: что это значит для индустрии

6 июля 2026

Генерация параметрических 3D-моделей за секунды: AI-революция в производстве

6 июля 2026

Освоение госзакупок (44-ФЗ, 223-ФЗ) с AI: быстрое обучение, высокие баллы, реальный карьерный рост

6 июля 2026

От WordPress-сайта до $220 000 в месяц: как два друга сделали сервис для музыкантов и заработали $1,8 млн

6 июля 2026

The Sourdough Sidekick: Как автоматизация замеса спасает время программиста и AI-энтузиаста

6 июля 2026

7 промтов для оптимизации производительности кода: находим узкие места за секунды

6 июля 2026

Private Equity & Venture Capital — инвестиции и сделки: как пройти путь от LBO-модели до exit и почему AI-обучение ускоряет этот процесс в 2 раза

6 июля 2026

10 промтов для ChatGPT и GPT-4: от написания кода до архитектурных решений

6 июля 2026

Промпт-инжиниринг ПРО (Prompt Engineering Pro): Как стать инженером AI-запросов в 2026 году

6 июля 2026