Главная / 💚 Vue / Nuxt

💚 Vue 3 & Nuxt 3

Работа с Vue 3 (Composition API) и Nuxt 3 (SSR) через Claude Code: chrome-devtools MCP, SSR-правила, Pinia, VeeValidate, Vitest и Playwright e2e тесты.

🌐 chrome-devtools MCP — UI инспекция

Для работы над UI-компонентами Claude Code использует chrome-devtools MCP вместо чтения HTML/CSS файлов вслепую. Это позволяет видеть реальный DOM, CSS и консоль.

🚫
НИКОГДА не смешивать chrome-devtools и Playwright в одной сессии! chrome-devtools — для UI-разработки. Playwright — только для e2e тестов в отдельной сессии. Смешение приводит к конфликту портов и зависанию.
// .mcp.json для Nuxt/Vue проекта { "mcpServers": { "chrome-devtools": { "command": "npx", "args": ["-y", "chrome-devtools-mcp@latest"], "_comment": "UI-работа: инспекция, снимки, консоль. НЕ смешивать с playwright!" } } }

Workflow UI разработки

// 1. Запустить dev сервер: mcp__docker__docker_exec({ container: "nuxt-frontend-1", command: "pnpm dev" }) // или напрямую: pnpm dev (порт 3000) // 2. Открыть Chrome с dev tools MCP // chrome --remote-debugging-port=9222 // 3. Инспекция DOM через MCP: mcp__chrome-devtools__inspect({ selector: ".payment-form" }) // 4. Скриншот для проверки: mcp__chrome-devtools__screenshot({ path: "E:/tmp/payment-form.png" }) // 5. Консольные ошибки: mcp__chrome-devtools__console_errors()

⚡ Vue 3 Composition API — стандарты

Правильная структура SFC

<!-- components/PaymentForm.vue --> <script setup lang="ts"> // Порядок: imports → defineProps → defineEmits → реактивность → computed → methods → lifecycle import { ref, computed, onMounted } from 'vue' import { usePaymentStore } from '~/stores/payment' // TypeScript interface для props interface Props { initialAmount?: number currency: string } const props = defineProps<Props>() const emit = defineEmits<{ submit: [amount: number] cancel: [] }>() const store = usePaymentStore() const amount = ref(props.initialAmount ?? 0) const formattedAmount = computed(() => new Intl.NumberFormat('ru-RU', { style: 'currency', currency: props.currency }).format(amount.value) ) function handleSubmit() { emit('submit', amount.value) } onMounted(() => { store.fetchRates() }) </script> <template> <!-- CSS только Tailwind — никаких кастомных классов --> <form @submit.prevent="handleSubmit" class="flex flex-col gap-4"> <input v-model="amount" type="number" class="border rounded px-3 py-2" /> <span class="text-gray-500">{{ formattedAmount }}</span> <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded">Оплатить</button> </form> </template>

🌐 SSR — критические правила Nuxt 3

Нарушение SSR
// Это упадёт на сервере! const token = localStorage.getItem('token') const w = window.innerWidth const ua = navigator.userAgent
SSR-безопасно
// С guard для клиента: if (import.meta.client) { const token = localStorage.getItem('token') } // или через useRuntimeConfig: const config = useRuntimeConfig()
ПравилоПлохоХорошо
Browser API localStorage, window, document Обернуть в if (import.meta.client) {}
Env переменные process.env.API_URL useRuntimeConfig().apiUrl
Composables Прямые fetch без хука useFetch() или useAsyncData()
Lifecycle hooks mounted для данных onMounted только для browser-specific логики

🍍 Pinia — store паттерн

// stores/payment.ts import { defineStore } from 'pinia' import type { Payment } from '~/types' // Composition API синтаксис (не Options API!) export const usePaymentStore = defineStore('payment', () => { const payments = ref<Payment[]>([]) const loading = ref(false) const total = computed(() => payments.value.reduce((sum, p) => sum + p.amount, 0) ) async function fetchPayments() { loading.value = true try { payments.value = await $fetch('/api/v1/payments') } finally { loading.value = false } } return { payments, loading, total, fetchPayments } })

🧪 Vitest — тестирование компонентов

// tests/PaymentForm.test.ts import { describe, it, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import PaymentForm from '~/components/PaymentForm.vue' describe('PaymentForm', () => { it('emits submit with correct amount', async () => { // Arrange const wrapper = mount(PaymentForm, { props: { currency: 'RUB', initialAmount: 1000 } }) // Act await wrapper.find('form').trigger('submit') // Assert expect(wrapper.emitted('submit')?.[0]).toEqual([1000]) }) })
💡
Playwright — только для e2e. Vitest + @vue/test-utils — для unit/component тестов. Playwright запускается в отдельной сессии Claude, без chrome-devtools MCP.

📏 ESLint Flat Config

// eslint.config.js — flat config (v9+) import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' import pluginVue from 'eslint-plugin-vue' export default [ ...pluginVue.configs['flat/recommended'], ...vueTsConfigs.recommended, { rules: { 'vue/component-name-in-template-casing': ['error', 'PascalCase'], 'vue/no-v-html': 'error', // XSS защита '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/explicit-function-return-type': 'off', } } ]