Use Cases (Diperbarui: 2/6/2026)

Claude Code untuk Vue 3: Panduan TypeScript, Pinia, dan Vitest

Gunakan Claude Code untuk Vue 3, TypeScript, form, Pinia, composable, dan Vitest dalam workflow nyata.

Claude Code untuk Vue 3: Panduan TypeScript, Pinia, dan Vitest

Claude Code paling berguna dalam pengembangan Vue 3 ketika dipakai untuk bekerja di dalam repository nyata, bukan hanya membuat komponen sekali pakai. Dalam proyek produksi, pekerjaan yang sering muncul adalah membaca SFC yang sudah ada, menjaga tipe TypeScript, memindahkan logic berulang ke composable, merapikan shared state dengan Pinia, lalu menambah test Vitest sebelum perubahan dirilis.

Vue sangat fleksibel. Fleksibilitas itu membantu, tetapi juga membuat review sulit jika tidak dikendalikan. Satu codebase bisa berisi Options API, Composition API, script setup, Pinia, kebiasaan lama dari Vuex, aturan Nuxt, dan helper lokal. Jika prompt hanya berbunyi “buat form Vue”, Claude Code mungkin menghasilkan kode yang berjalan, tetapi tidak cocok dengan batas arsitektur tim.

Panduan ini membahas Claude Code + Vue 3 + TypeScript dalam lima use case: memperbaiki Vue yang sudah ada, membuat form typed, mengelola state dengan Pinia, mengekstrak composable, dan menambah test Vitest. Untuk dasar teknis, gunakan dokumentasi resmi Vue TypeScript dengan Composition API, Pinia, dan Vitest. Untuk topik terkait, baca juga TypeScript tips dan testing strategy guide.

Asumsi proyek

Workflow ini ditujukan untuk aplikasi Vue 3, TypeScript, Vite, Pinia, dan Vitest. Contohnya dashboard internal, help desk, booking system, approval flow, dan admin panel. Di aplikasi seperti ini, tantangan utamanya biasanya bukan animasi, melainkan form, daftar data, filter, pagination, shared state, dan test.

Sebelum meminta kode, berikan harness kepada Claude Code. Harness berarti kerangka kerja aman untuk agent: file mana yang boleh diedit, aturan apa yang harus diikuti, apa yang tidak boleh diubah, command verifikasi apa yang harus dijalankan, dan risiko apa yang perlu dijelaskan. Tanpa harness, Claude Code bisa menyelesaikan masalah lokal sambil menambahkan any, mengubah teks UI, atau memindahkan logic ke layer yang salah.

SituasiTugas untuk Claude CodeKeputusan manusia
Refactor Vue existingMembaca SFC, menemukan duplikasi, memperkuat tipeKompatibilitas UI dan risiko rilis
FormLocal state, validasi, submit flowPesan error dan kontrak API
PiniaStore types, getters, actions, testsPersistence dan batas tanggung jawab
ComposableFilter, pagination, reusable logicApakah reuse benar-benar ada
TestsNormal case, error case, regressionTarget coverage

Gambaran arsitektur

Ketika contoh kode bertambah, pertanyaan utamanya bukan “apakah Claude Code bisa menulis ini?” Pertanyaannya adalah “tanggung jawab ini seharusnya tinggal di mana?” Batas yang jelas membuat diff hasil Claude Code lebih mudah direview.

flowchart LR
  A[Vue SFC] --> B[Composable]
  A --> C[Pinia Store]
  B --> D[Vitest]
  C --> D
  E[Claude Code Prompt] --> A
  E --> B
  E --> C
  E --> D

SFC menangani tampilan dan event pengguna. Composable menyimpan logic reaktif yang bisa dipakai ulang, seperti filter dan pagination. Pinia store menyimpan state aplikasi yang dipakai beberapa layar. Vitest menjaga behavior yang tidak boleh rusak.

Membuat baseline Vue 3 + TypeScript

Untuk eksperimen baru, mulai dari scaffolding resmi Vue. Untuk repository existing, pastikan typecheck dan unit test saat ini sudah lulus agar perubahan Claude Code bisa dinilai dari baseline yang bersih.

npm create vue@latest support-desk-vue -- --typescript --router --pinia --vitest
cd support-desk-vue
npm install
npm run dev

Jangan menulis prompt “buat aplikasi Vue modern” saja. Sebutkan package manager, apakah Vue Router dipakai, apakah Pinia adalah standar, dan command test apa yang harus lulus. Di aplikasi nyata, minta Claude Code membaca package.json, vite.config.ts, tsconfig.app.json, src/components, dan src/stores sebelum mengedit.

Use case 1: form SFC typed

Form adalah target awal yang baik karena menggabungkan v-model, props, emit, validasi, saving state, dan store action. Contoh ini bisa ditempatkan di src/components/TicketForm.vue. Kode tidak memutasi props, menyimpan nilai edit di ref lokal, dan mengirim id setelah store selesai menyimpan.

<script setup lang="ts">
import { computed, ref } from 'vue';
import { useTicketStore, type TicketPriority } from '@/stores/ticketStore';

const props = withDefaults(
  defineProps<{
    initialTitle?: string;
    defaultPriority?: TicketPriority;
  }>(),
  {
    initialTitle: '',
    defaultPriority: 'medium',
  },
);

const emit = defineEmits<{
  submitted: [id: string];
}>();

const store = useTicketStore();
const title = ref(props.initialTitle);
const description = ref('');
const priority = ref<TicketPriority>(props.defaultPriority);
const touched = ref(false);

const titleError = computed(() => {
  if (!touched.value) return '';
  if (title.value.trim().length < 5) return 'Use at least 5 characters.';
  return '';
});

const descriptionError = computed(() => {
  if (!touched.value) return '';
  if (description.value.trim().length < 20) return 'Use at least 20 characters.';
  return '';
});

const canSubmit = computed(() => {
  return (
    title.value.trim().length >= 5 &&
    description.value.trim().length >= 20 &&
    !store.isSaving
  );
});

async function submit() {
  touched.value = true;
  if (!canSubmit.value) return;

  const ticket = await store.saveTicket({
    title: title.value.trim(),
    description: description.value.trim(),
    priority: priority.value,
  });

  title.value = '';
  description.value = '';
  priority.value = props.defaultPriority;
  touched.value = false;
  emit('submitted', ticket.id);
}
</script>

<template>
  <form class="ticket-form" @submit.prevent="submit">
    <label>
      Title
      <input v-model="title" name="title" @blur="touched = true" />
    </label>
    <p v-if="titleError" class="error">{{ titleError }}</p>

    <label>
      Description
      <textarea v-model="description" name="description" @blur="touched = true" />
    </label>
    <p v-if="descriptionError" class="error">{{ descriptionError }}</p>

    <label>
      Priority
      <select v-model="priority" name="priority">
        <option value="low">Low</option>
        <option value="medium">Medium</option>
        <option value="high">High</option>
      </select>
    </label>

    <button type="submit" :disabled="!canSubmit">
      {{ store.isSaving ? 'Saving...' : 'Create ticket' }}
    </button>
  </form>
</template>

Saat meminta komponen seperti ini, tulis juga batasannya: jangan mutasi props, jangan tambahkan any, jangan menambah UI library, dan jangan memasukkan detail API ke SFC. Batasan negatif seperti ini mengurangi pekerjaan cleanup.

Use case 2: shared state dengan Pinia

Jika daftar ticket dipakai beberapa layar, form tidak boleh memiliki semua state. Pinia cocok untuk menyimpan list, flag saving, getter open tickets, dan actions.

import { computed, ref } from 'vue';
import { defineStore } from 'pinia';

export type TicketPriority = 'low' | 'medium' | 'high';
export type TicketStatus = 'open' | 'closed';

export interface Ticket {
  id: string;
  title: string;
  description: string;
  priority: TicketPriority;
  status: TicketStatus;
  createdAt: string;
}

export type NewTicketInput = Pick<Ticket, 'title' | 'description' | 'priority'>;

export const useTicketStore = defineStore('tickets', () => {
  const tickets = ref<Ticket[]>([]);
  const isSaving = ref(false);

  const openTickets = computed(() => {
    return tickets.value.filter((ticket) => ticket.status === 'open');
  });

  function addTicket(input: NewTicketInput) {
    const ticket: Ticket = {
      id: crypto.randomUUID(),
      ...input,
      status: 'open',
      createdAt: new Date().toISOString(),
    };

    tickets.value.unshift(ticket);
    return ticket;
  }

  async function saveTicket(input: NewTicketInput) {
    isSaving.value = true;
    try {
      await new Promise((resolve) => setTimeout(resolve, 150));
      return addTicket(input);
    } finally {
      isSaving.value = false;
    }
  }

  function closeTicket(id: string) {
    const ticket = tickets.value.find((item) => item.id === id);
    if (ticket) ticket.status = 'closed';
  }

  return {
    tickets,
    isSaving,
    openTickets,
    addTicket,
    saveTicket,
    closeTicket,
  };
});

Di production, saveTicket akan memanggil API client yang typed. Namun lebih aman untuk memvalidasi bentuk store lebih dulu, lalu menambahkan network call, lalu error handling. Setiap diff menjadi lebih kecil.

Use case 3: ekstraksi composable

Composable adalah function yang berisi logic reaktif Vue dan bisa dipakai ulang. Secara sederhana, composable adalah logic layar yang dipisahkan dari tampilan. Search, filter prioritas, dan pagination cocok untuk diekstrak.

import { computed, ref, watch, type Ref } from 'vue';
import type { Ticket, TicketPriority } from '@/stores/ticketStore';

export function useFilteredTickets(tickets: Ref<Ticket[]>) {
  const query = ref('');
  const selectedPriority = ref<TicketPriority | 'all'>('all');
  const currentPage = ref(1);
  const pageSize = ref(10);

  const filteredTickets = computed(() => {
    const normalizedQuery = query.value.trim().toLowerCase();

    return tickets.value.filter((ticket) => {
      const matchesQuery =
        normalizedQuery.length === 0 ||
        ticket.title.toLowerCase().includes(normalizedQuery) ||
        ticket.description.toLowerCase().includes(normalizedQuery);

      const matchesPriority =
        selectedPriority.value === 'all' ||
        ticket.priority === selectedPriority.value;

      return matchesQuery && matchesPriority;
    });
  });

  const totalPages = computed(() => {
    return Math.max(1, Math.ceil(filteredTickets.value.length / pageSize.value));
  });

  const pagedTickets = computed(() => {
    const start = (currentPage.value - 1) * pageSize.value;
    return filteredTickets.value.slice(start, start + pageSize.value);
  });

  watch([query, selectedPriority], () => {
    currentPage.value = 1;
  });

  return {
    query,
    selectedPriority,
    currentPage,
    pageSize,
    filteredTickets,
    pagedTickets,
    totalPages,
  };
}

watch di sini hanya punya satu tugas: mengembalikan halaman ke 1 saat filter berubah. Nilai turunan lain tetap memakai computed. Aturan ini sebaiknya tertulis di prompt: computed untuk derivasi, watch untuk side effect eksplisit.

Use case 4: test dengan Vitest

Ketika implementasi makin cepat, regression test makin penting. Mulai dari store karena dekat dengan behavior bisnis dan lebih stabil daripada test DOM.

import { createPinia, setActivePinia } from 'pinia';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useTicketStore } from './ticketStore';

describe('useTicketStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
    vi.stubGlobal('crypto', {
      randomUUID: () => 'ticket-1',
    });
  });

  it('adds a new open ticket', () => {
    const store = useTicketStore();

    const ticket = store.addTicket({
      title: 'Cannot submit expense form',
      description: 'The submit button stays disabled after all fields are filled.',
      priority: 'high',
    });

    expect(ticket.id).toBe('ticket-1');
    expect(store.tickets).toHaveLength(1);
    expect(store.openTickets).toHaveLength(1);
  });

  it('closes an existing ticket', () => {
    const store = useTicketStore();
    const ticket = store.addTicket({
      title: 'Profile image is broken',
      description: 'The image URL returns 404 on the account settings page.',
      priority: 'medium',
    });

    store.closeTicket(ticket.id);

    expect(store.openTickets).toHaveLength(0);
    expect(store.tickets[0]?.status).toBe('closed');
  });
});

Jangan hanya meminta “tambah test”. Sebutkan behavior yang penting: input terlalu pendek, input valid, saving state, sukses, gagal, dan perubahan state Pinia. Test seperti ini melindungi pekerjaan nyata.

Prompt untuk refactor Vue existing

Untuk codebase yang sudah ada, hindari rewrite besar. Minta Claude Code membaca komponen dan menulis risiko, lalu perbaiki tipe, ekstrak satu composable, rapikan Pinia bila perlu, dan tambahkan test.

You are working in a Vue 3 + TypeScript + Pinia project.

Goal:
Refactor the ticket form so validation logic is typed, reusable, and covered by Vitest.

Allowed files:
- src/components/TicketForm.vue
- src/composables/useFilteredTickets.ts
- src/stores/ticketStore.ts
- src/stores/ticketStore.test.ts

Rules:
- Use <script setup lang="ts">.
- Do not mutate props directly.
- Do not introduce any.
- Prefer computed for derived values.
- Use watch only for explicit side effects.
- Keep UI text unchanged unless a validation message is missing.

Verification:
- npm run typecheck
- npm run test:unit

After editing, explain the risk areas and the tests you added.

Prompt ini memisahkan keputusan yang bisa dibantu Claude Code dan keputusan yang harus divalidasi manusia. Ekstraksi, type cleanup, dan draft test bisa dibantu tool. Copy UI, kontrak API, aksesibilitas, dan timing rilis tetap perlu review manusia.

Kesalahan umum

Pertama, mencampur Options API dan Composition API tanpa rencana. Vue 3 mendukung keduanya, tetapi komponen yang berisi data, methods, setup, dan script setup sekaligus sulit direview.

Kedua, salah memahami ref dan reactive. Untuk primitive, array, dan nilai yang sering diganti, ref biasanya lebih mudah. reactive cocok untuk object yang utuh, tetapi destructuring bisa membuat perilaku reaktif membingungkan.

Ketiga, memutasi props. Child component tidak boleh diam-diam mengubah state milik parent. Gunakan ref lokal, emit ke parent, atau defineModel jika sudah menjadi pola tim.

Keempat, terlalu banyak memakai watch. Jika nilai bisa diturunkan, gunakan computed. Simpan watch untuk route sync, reset halaman, API call, atau storage write.

Kelima, membiarkan tipe jatuh ke any. Claude Code bisa menambahkan as any untuk melewati error. Review any, unknown, assertion, dan inferensi array kosong sebelum menerima diff.

Keenam, memecah SFC terlalu kecil. Banyak komponen kecil yang tidak reuse justru menambah props dan emits. Ekstrak logic ke composable lebih dulu, lalu pecah UI ketika reuse atau maknanya jelas.

Ketujuh, test kurang. Kode hasil generate bisa terlihat rapi tetapi gagal di edge case. Untuk form, cover input pendek, submit valid, saving, sukses, gagal, dan perubahan store.

Workflow praktis dan CTA

Di repository nyata, jangan mulai dari “buat seluruh layar”. Minta Claude Code menganalisis risiko komponen saat ini, lakukan satu perubahan kecil, jalankan typecheck dan test, lalu lanjut. Diff kecil membuat review lebih tajam.

Untuk tim, tulis aturan di CLAUDE.md: Vue memakai script setup, shared state memakai Pinia setup store, nilai turunan memakai computed, side effect memakai watch, dan verifikasi memakai npm run typecheck serta npm run test:unit. ClaudeCodeLab dapat membantu menyusun aturan, prompt, dan review checklist melalui training dan konsultasi Claude Code.

Ringkasan

Claude Code dapat mempercepat refactor Vue 3, form, Pinia, composable, dan test. Kualitasnya bergantung pada batas prompt: file yang boleh diedit, style Vue, hal yang tidak boleh berubah, dan cara verifikasi.

Pemula sebaiknya mulai dari satu SFC, satu composable, satu store, dan satu file test. Loop kecil ini membantu memahami mental model Vue sekaligus menghasilkan kode yang dekat dengan produksi.

Hasil saat dicoba

Saya mencoba workflow ini di proyek kecil Vue 3 + TypeScript. Meminta form, store, composable, dan test dalam satu request menghasilkan diff besar dan lebih lama direview. Memisahkan pekerjaan menjadi SFC, Pinia, composable, lalu Vitest membuat masalah lebih mudah ditemukan. Constraint paling berguna adalah “jangan mutasi props”, “jangan tambahkan any”, dan “gunakan computed untuk nilai turunan”. Hasilnya lebih mudah diverifikasi dengan npm run typecheck dan npm run test:unit.

#Claude Code #Vue.js #Nuxt.js #Pinia #Composition API
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.