@su-record/vibe 2.4.19 โ†’ 2.4.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,521 +0,0 @@
1
- # ๐ŸŸข TypeScript + Nuxt 3 ํ’ˆ์งˆ ๊ทœ์น™
2
-
3
- ## ํ•ต์‹ฌ ์›์น™ (Vue์—์„œ ์ƒ์†)
4
-
5
- ```markdown
6
- โœ… ๋‹จ์ผ ์ฑ…์ž„ (SRP)
7
- โœ… ์ค‘๋ณต ์ œ๊ฑฐ (DRY)
8
- โœ… ์žฌ์‚ฌ์šฉ์„ฑ
9
- โœ… ๋‚ฎ์€ ๋ณต์žก๋„
10
- โœ… ํ•จ์ˆ˜ โ‰ค 30์ค„, Template โ‰ค 100์ค„
11
- โœ… ์ค‘์ฒฉ โ‰ค 3๋‹จ๊ณ„
12
- โœ… Composition API + script setup
13
- ```
14
-
15
- ## Nuxt 3 ํŠนํ™” ๊ทœ์น™
16
-
17
- ### 1. Auto-imports ํ™œ์šฉ
18
-
19
- ```typescript
20
- // โœ… Nuxt 3๋Š” ์ž๋™ import (๋ช…์‹œ์  import ๋ถˆํ•„์š”)
21
- <script setup lang="ts">
22
- // ref, computed, watch ๋“ฑ Vue API ์ž๋™ import
23
- const count = ref(0);
24
- const doubled = computed(() => count.value * 2);
25
-
26
- // useFetch, useAsyncData ๋“ฑ Nuxt composables ์ž๋™ import
27
- const { data } = await useFetch('/api/users');
28
-
29
- // components/ ํด๋”์˜ ์ปดํฌ๋„ŒํŠธ ์ž๋™ import
30
- // <UserCard /> ๋ฐ”๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
31
- </script>
32
-
33
- // โŒ ๋ถˆํ•„์š”ํ•œ import
34
- import { ref, computed } from 'vue';
35
- import { useFetch } from '#app';
36
- ```
37
-
38
- ### 2. Server API Routes
39
-
40
- ```typescript
41
- // โœ… server/api/users/index.get.ts (GET /api/users)
42
- export default defineEventHandler(async (event) => {
43
- const users = await prisma.user.findMany();
44
- return users;
45
- });
46
-
47
- // โœ… server/api/users/index.post.ts (POST /api/users)
48
- export default defineEventHandler(async (event) => {
49
- const body = await readBody(event);
50
-
51
- // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
52
- if (!body.email || !body.name) {
53
- throw createError({
54
- statusCode: 400,
55
- message: '์ด๋ฉ”์ผ๊ณผ ์ด๋ฆ„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค',
56
- });
57
- }
58
-
59
- const user = await prisma.user.create({ data: body });
60
- return user;
61
- });
62
-
63
- // โœ… server/api/users/[id].get.ts (GET /api/users/:id)
64
- export default defineEventHandler(async (event) => {
65
- const id = getRouterParam(event, 'id');
66
-
67
- const user = await prisma.user.findUnique({ where: { id } });
68
-
69
- if (!user) {
70
- throw createError({
71
- statusCode: 404,
72
- message: '์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค',
73
- });
74
- }
75
-
76
- return user;
77
- });
78
-
79
- // โœ… server/api/users/[id].put.ts (PUT /api/users/:id)
80
- export default defineEventHandler(async (event) => {
81
- const id = getRouterParam(event, 'id');
82
- const body = await readBody(event);
83
-
84
- const user = await prisma.user.update({
85
- where: { id },
86
- data: body,
87
- });
88
-
89
- return user;
90
- });
91
-
92
- // โœ… server/api/users/[id].delete.ts (DELETE /api/users/:id)
93
- export default defineEventHandler(async (event) => {
94
- const id = getRouterParam(event, 'id');
95
- await prisma.user.delete({ where: { id } });
96
- return { success: true };
97
- });
98
- ```
99
-
100
- ### 3. Data Fetching (SSR ์ง€์›)
101
-
102
- ```typescript
103
- // โœ… useFetch - ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ ํŽ˜์นญ
104
- <script setup lang="ts">
105
- const { data: user, pending, error, refresh } = await useFetch<User>(
106
- `/api/users/${props.userId}`
107
- );
108
-
109
- // ์˜ต์…˜ ์‚ฌ์šฉ
110
- const { data: posts } = await useFetch('/api/posts', {
111
- query: { limit: 10, offset: 0 },
112
- headers: { 'X-Custom': 'value' },
113
- pick: ['id', 'title'], // ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์„ ํƒ
114
- transform: (data) => data.items, // ์‘๋‹ต ๋ณ€ํ™˜
115
- });
116
- </script>
117
-
118
- // โœ… useAsyncData - ์ปค์Šคํ…€ ํŽ˜์นญ ๋กœ์ง
119
- <script setup lang="ts">
120
- const { data, pending } = await useAsyncData(
121
- 'user-posts', // ์บ์‹œ ํ‚ค
122
- () => $fetch(`/api/users/${props.userId}/posts`),
123
- {
124
- default: () => [], // ๊ธฐ๋ณธ๊ฐ’
125
- lazy: true, // ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ ์‹คํ–‰
126
- server: false, // SSR ๋น„ํ™œ์„ฑํ™”
127
- }
128
- );
129
- </script>
130
-
131
- // โœ… useLazyFetch - ์ง€์—ฐ ๋กœ๋”ฉ (Suspense ์—†์ด)
132
- <script setup lang="ts">
133
- const { data, pending } = useLazyFetch('/api/heavy-data');
134
-
135
- // pending ์ƒํƒœ ์ฒ˜๋ฆฌ
136
- </script>
137
- <template>
138
- <div v-if="pending">๋กœ๋”ฉ ์ค‘...</div>
139
- <div v-else>{{ data }}</div>
140
- </template>
141
- ```
142
-
143
- ### 4. State Management
144
-
145
- ```typescript
146
- // โœ… useState - ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ๊ณต์œ  ์ƒํƒœ
147
- <script setup lang="ts">
148
- // ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ณต์œ ๋˜๋Š” ์ƒํƒœ
149
- const counter = useState('counter', () => 0);
150
-
151
- function increment() {
152
- counter.value++;
153
- }
154
- </script>
155
-
156
- // โœ… Pinia Store (๋ณต์žกํ•œ ์ƒํƒœ)
157
- // stores/user.ts
158
- export const useUserStore = defineStore('user', () => {
159
- const user = ref<User | null>(null);
160
- const isLoggedIn = computed(() => !!user.value);
161
-
162
- async function login(credentials: LoginCredentials) {
163
- const data = await $fetch('/api/auth/login', {
164
- method: 'POST',
165
- body: credentials,
166
- });
167
- user.value = data.user;
168
- }
169
-
170
- function logout() {
171
- user.value = null;
172
- navigateTo('/login');
173
- }
174
-
175
- return { user, isLoggedIn, login, logout };
176
- });
177
- ```
178
-
179
- ### 5. Middleware
180
-
181
- ```typescript
182
- // โœ… middleware/auth.ts (Named middleware)
183
- export default defineNuxtRouteMiddleware((to, from) => {
184
- const { isLoggedIn } = useUserStore();
185
-
186
- // ๋กœ๊ทธ์ธ ํ•„์š”ํ•œ ํŽ˜์ด์ง€ ๋ณดํ˜ธ
187
- if (!isLoggedIn && to.meta.requiresAuth) {
188
- return navigateTo('/login');
189
- }
190
- });
191
-
192
- // ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉ
193
- <script setup lang="ts">
194
- definePageMeta({
195
- middleware: 'auth',
196
- requiresAuth: true,
197
- });
198
- </script>
199
-
200
- // โœ… middleware/auth.global.ts (Global middleware)
201
- export default defineNuxtRouteMiddleware((to, from) => {
202
- // ๋ชจ๋“  ๋ผ์šฐํŠธ์— ์ ์šฉ
203
- });
204
-
205
- // โœ… Server middleware
206
- // server/middleware/auth.ts
207
- export default defineEventHandler((event) => {
208
- const token = getCookie(event, 'auth-token');
209
-
210
- if (!token && event.path.startsWith('/api/protected')) {
211
- throw createError({
212
- statusCode: 401,
213
- message: '์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค',
214
- });
215
- }
216
- });
217
- ```
218
-
219
- ### 6. Layouts & Pages
220
-
221
- ```typescript
222
- // โœ… layouts/default.vue
223
- <template>
224
- <div class="layout">
225
- <AppHeader />
226
- <main>
227
- <slot />
228
- </main>
229
- <AppFooter />
230
- </div>
231
- </template>
232
-
233
- // โœ… layouts/admin.vue
234
- <template>
235
- <div class="admin-layout">
236
- <AdminSidebar />
237
- <main>
238
- <slot />
239
- </main>
240
- </div>
241
- </template>
242
-
243
- // โœ… pages/admin/index.vue
244
- <script setup lang="ts">
245
- definePageMeta({
246
- layout: 'admin',
247
- middleware: ['auth', 'admin-only'],
248
- });
249
- </script>
250
-
251
- // โœ… pages/users/[id].vue (๋™์  ๋ผ์šฐํŠธ)
252
- <script setup lang="ts">
253
- const route = useRoute();
254
- const userId = route.params.id;
255
-
256
- const { data: user } = await useFetch(`/api/users/${userId}`);
257
- </script>
258
-
259
- // โœ… pages/posts/[...slug].vue (Catch-all ๋ผ์šฐํŠธ)
260
- <script setup lang="ts">
261
- const route = useRoute();
262
- const slugParts = route.params.slug; // ['a', 'b', 'c']
263
- </script>
264
- ```
265
-
266
- ### 7. SEO & Meta
267
-
268
- ```typescript
269
- // โœ… ํŽ˜์ด์ง€๋ณ„ ๋ฉ”ํƒ€ ์„ค์ •
270
- <script setup lang="ts">
271
- const { data: post } = await useFetch(`/api/posts/${route.params.id}`);
272
-
273
- useHead({
274
- title: post.value?.title,
275
- meta: [
276
- { name: 'description', content: post.value?.summary },
277
- { property: 'og:title', content: post.value?.title },
278
- { property: 'og:image', content: post.value?.thumbnail },
279
- ],
280
- });
281
-
282
- // ๋˜๋Š” useSeoMeta
283
- useSeoMeta({
284
- title: post.value?.title,
285
- ogTitle: post.value?.title,
286
- description: post.value?.summary,
287
- ogDescription: post.value?.summary,
288
- ogImage: post.value?.thumbnail,
289
- });
290
- </script>
291
-
292
- // โœ… nuxt.config.ts ์ „์—ญ ์„ค์ •
293
- export default defineNuxtConfig({
294
- app: {
295
- head: {
296
- title: 'My App',
297
- meta: [
298
- { name: 'description', content: 'My awesome app' },
299
- ],
300
- link: [
301
- { rel: 'icon', href: '/favicon.ico' },
302
- ],
303
- },
304
- },
305
- });
306
- ```
307
-
308
- ### 8. Plugins & Modules
309
-
310
- ```typescript
311
- // โœ… plugins/api.ts
312
- export default defineNuxtPlugin(() => {
313
- const api = $fetch.create({
314
- baseURL: '/api',
315
- onRequest({ options }) {
316
- const token = useCookie('auth-token');
317
- if (token.value) {
318
- options.headers = {
319
- ...options.headers,
320
- Authorization: `Bearer ${token.value}`,
321
- };
322
- }
323
- },
324
- onResponseError({ response }) {
325
- if (response.status === 401) {
326
- navigateTo('/login');
327
- }
328
- },
329
- });
330
-
331
- return {
332
- provide: { api },
333
- };
334
- });
335
-
336
- // ์‚ฌ์šฉ
337
- const { $api } = useNuxtApp();
338
- const users = await $api('/users');
339
-
340
- // โœ… plugins/dayjs.client.ts (ํด๋ผ์ด์–ธํŠธ ์ „์šฉ)
341
- import dayjs from 'dayjs';
342
- import relativeTime from 'dayjs/plugin/relativeTime';
343
-
344
- export default defineNuxtPlugin(() => {
345
- dayjs.extend(relativeTime);
346
- return { provide: { dayjs } };
347
- });
348
- ```
349
-
350
- ### 9. Composables
351
-
352
- ```typescript
353
- // โœ… composables/useAuth.ts
354
- export function useAuth() {
355
- const user = useState<User | null>('auth-user', () => null);
356
- const isLoggedIn = computed(() => !!user.value);
357
-
358
- async function login(email: string, password: string) {
359
- const data = await $fetch('/api/auth/login', {
360
- method: 'POST',
361
- body: { email, password },
362
- });
363
- user.value = data.user;
364
- }
365
-
366
- async function logout() {
367
- await $fetch('/api/auth/logout', { method: 'POST' });
368
- user.value = null;
369
- await navigateTo('/login');
370
- }
371
-
372
- return { user, isLoggedIn, login, logout };
373
- }
374
-
375
- // โœ… composables/usePagination.ts
376
- export function usePagination<T>(
377
- fetchFn: (page: number) => Promise<{ items: T[]; total: number }>
378
- ) {
379
- const items = ref<T[]>([]);
380
- const page = ref(1);
381
- const total = ref(0);
382
- const isLoading = ref(false);
383
-
384
- const hasMore = computed(() => items.value.length < total.value);
385
-
386
- async function loadMore() {
387
- if (isLoading.value || !hasMore.value) return;
388
-
389
- isLoading.value = true;
390
- const data = await fetchFn(page.value);
391
- items.value.push(...data.items);
392
- total.value = data.total;
393
- page.value++;
394
- isLoading.value = false;
395
- }
396
-
397
- return { items, isLoading, hasMore, loadMore };
398
- }
399
- ```
400
-
401
- ### 10. Error Handling
402
-
403
- ```typescript
404
- // โœ… error.vue (์ „์—ญ ์—๋Ÿฌ ํŽ˜์ด์ง€)
405
- <script setup lang="ts">
406
- const props = defineProps<{
407
- error: {
408
- statusCode: number;
409
- message: string;
410
- };
411
- }>();
412
-
413
- const handleError = () => clearError({ redirect: '/' });
414
- </script>
415
-
416
- <template>
417
- <div class="error-page">
418
- <h1>{{ error.statusCode }}</h1>
419
- <p>{{ error.message }}</p>
420
- <button @click="handleError">ํ™ˆ์œผ๋กœ</button>
421
- </div>
422
- </template>
423
-
424
- // โœ… ์ปดํฌ๋„ŒํŠธ ๋ ˆ๋ฒจ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
425
- <script setup lang="ts">
426
- const { data, error } = await useFetch('/api/data');
427
-
428
- if (error.value) {
429
- throw createError({
430
- statusCode: error.value.statusCode,
431
- message: error.value.message,
432
- });
433
- }
434
- </script>
435
-
436
- // โœ… NuxtErrorBoundary ์‚ฌ์šฉ
437
- <template>
438
- <NuxtErrorBoundary @error="logError">
439
- <SomeComponent />
440
- <template #error="{ error, clearError }">
441
- <p>์˜ค๋ฅ˜ ๋ฐœ์ƒ: {{ error.message }}</p>
442
- <button @click="clearError">๋‹ค์‹œ ์‹œ๋„</button>
443
- </template>
444
- </NuxtErrorBoundary>
445
- </template>
446
- ```
447
-
448
- ## ํŒŒ์ผ ๊ตฌ์กฐ (Nuxt 3)
449
-
450
- ```
451
- project/
452
- โ”œโ”€โ”€ .nuxt/ # ๋นŒ๋“œ ์‚ฐ์ถœ๋ฌผ (git ์ œ์™ธ)
453
- โ”œโ”€โ”€ assets/ # ๋นŒ๋“œ์— ํฌํ•จ๋˜๋Š” ์—์…‹
454
- โ”œโ”€โ”€ components/ # ์ž๋™ import ์ปดํฌ๋„ŒํŠธ
455
- โ”‚ โ”œโ”€โ”€ ui/ # ๊ธฐ๋ณธ UI ์ปดํฌ๋„ŒํŠธ
456
- โ”‚ โ”œโ”€โ”€ features/ # ๊ธฐ๋Šฅ๋ณ„ ์ปดํฌ๋„ŒํŠธ
457
- โ”‚ โ””โ”€โ”€ App*.vue # ์•ฑ ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ
458
- โ”œโ”€โ”€ composables/ # ์ž๋™ import composables
459
- โ”œโ”€โ”€ layouts/ # ๋ ˆ์ด์•„์›ƒ
460
- โ”œโ”€โ”€ middleware/ # ๋ผ์šฐํŠธ ๋ฏธ๋“ค์›จ์–ด
461
- โ”œโ”€โ”€ pages/ # ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…
462
- โ”œโ”€โ”€ plugins/ # Nuxt ํ”Œ๋Ÿฌ๊ทธ์ธ
463
- โ”œโ”€โ”€ public/ # ์ •์  ํŒŒ์ผ
464
- โ”œโ”€โ”€ server/
465
- โ”‚ โ”œโ”€โ”€ api/ # API ๋ผ์šฐํŠธ
466
- โ”‚ โ”œโ”€โ”€ middleware/ # ์„œ๋ฒ„ ๋ฏธ๋“ค์›จ์–ด
467
- โ”‚ โ””โ”€โ”€ utils/ # ์„œ๋ฒ„ ์œ ํ‹ธ๋ฆฌํ‹ฐ
468
- โ”œโ”€โ”€ stores/ # Pinia ์Šคํ† ์–ด
469
- โ”œโ”€โ”€ types/ # TypeScript ํƒ€์ž…
470
- โ”œโ”€โ”€ utils/ # ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜
471
- โ”œโ”€โ”€ app.vue # ์•ฑ ๋ฃจํŠธ
472
- โ”œโ”€โ”€ nuxt.config.ts # Nuxt ์„ค์ •
473
- โ””โ”€โ”€ tsconfig.json # TypeScript ์„ค์ •
474
- ```
475
-
476
- ## ์•ˆํ‹ฐํŒจํ„ด
477
-
478
- ```typescript
479
- // โŒ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ง์ ‘ DB ์ ‘๊ทผ
480
- <script setup>
481
- import { PrismaClient } from '@prisma/client';
482
- const prisma = new PrismaClient(); // ํด๋ผ์ด์–ธํŠธ์—์„œ ์‹คํ–‰ ๋ถˆ๊ฐ€
483
- </script>
484
-
485
- // โœ… Server API ํ†ตํ•ด ์ ‘๊ทผ
486
- const { data } = await useFetch('/api/users');
487
-
488
- // โŒ useFetch๋ฅผ ์กฐ๊ฑด๋ถ€๋กœ ์‚ฌ์šฉ
489
- if (someCondition) {
490
- const { data } = await useFetch('/api/data'); // ์—๋Ÿฌ ๋ฐœ์ƒ
491
- }
492
-
493
- // โœ… enabled ์˜ต์…˜ ์‚ฌ์šฉ
494
- const { data } = await useFetch('/api/data', {
495
- immediate: someCondition,
496
- });
497
-
498
- // โŒ navigateTo๋ฅผ setup ๋ฐ–์—์„œ ์‚ฌ์šฉ
499
- function handleClick() {
500
- navigateTo('/page'); // ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ๋น„๊ถŒ์žฅ
501
- }
502
-
503
- // โœ… useRouter ์‚ฌ์šฉ
504
- const router = useRouter();
505
- function handleClick() {
506
- router.push('/page');
507
- }
508
- ```
509
-
510
- ## ์ฒดํฌ๋ฆฌ์ŠคํŠธ
511
-
512
- - [ ] Auto-imports ํ™œ์šฉ (๋ถˆํ•„์š”ํ•œ import ์ œ๊ฑฐ)
513
- - [ ] Server API ํŒŒ์ผ ๋„ค์ด๋ฐ ๊ทœ์น™ ์ค€์ˆ˜ (*.get.ts, *.post.ts)
514
- - [ ] useFetch/useAsyncData๋กœ SSR ์ง€์› ๋ฐ์ดํ„ฐ ํŽ˜์นญ
515
- - [ ] useState๋กœ ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ ๊ณต์œ 
516
- - [ ] definePageMeta๋กœ ํŽ˜์ด์ง€๋ณ„ ๋ฉ”ํƒ€ ์„ค์ •
517
- - [ ] ๋ฏธ๋“ค์›จ์–ด๋กœ ๋ผ์šฐํŠธ ๋ณดํ˜ธ
518
- - [ ] NuxtErrorBoundary๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
519
- - [ ] useHead/useSeoMeta๋กœ SEO ์ตœ์ ํ™”
520
- - [ ] Composables๋กœ ๋กœ์ง ์žฌ์‚ฌ์šฉ
521
- - [ ] TypeScript ์—„๊ฒฉ ๋ชจ๋“œ ์‚ฌ์šฉ