vercel-react-best-practices (vercel-labs/agent-skills)
Vercel의 React와 Next.js 성능 가이드라인을 컴포넌트, 데이터 가져오기, 번들에 적용합니다.
Vercel의 React 모범 사례: Next.js 최적화 과제 점수를 2.3에서 3.6/5로 끌어올렸지만, 한 문장 버전은 75%의 토큰으로 4.6점에 도달합니다
Codex CLI · gpt-6-astra
24.3 / 30
스킬 없이 대비 향상폭: +0.85 · 상황에 따라 · 18회 실행
상황에 따라: 향상폭 +0.85가 +1.0 미만. 추천이 되려면 총점 22/30, 출력 품질 3.5, 향상폭 +1.0이 필요합니다.
SOTA
통과Codex로 테스트
커버 대상 Codex, Claude Code
Codex CLI 0.166 (SOTA 티어): 채점된 실행 18회. 여기서 통과하면 티어 규칙에 따라 Claude Code도 커버되지만, 두 번째 티어에 대해서는 아무것도 말해주지 않습니다.
점수
각 차원은 0~5점이며 여섯 차원을 더하면 30점 만점의 총점이 됩니다. 모든 차원에서 5가 가장 좋습니다. 비용 5는 저렴하고 빠름, 설치 5는 쉬움을 뜻합니다.
- 트리거 신뢰도
- 5.0
- 스킬 없이 대비 향상폭
- 2.9
- 출력 품질
- 4.4
- 토큰 및 시간 비용
- 3.0
- 설치 및 이식성
- 4.0
- 안전성
- 5.0
보안 리뷰
안전정적 스캔: CRITICAL 6 / HIGH 0 / MEDIUM 0. 수동 검토: 네트워크가 차단된 샌드박스에서 실행 안전(스캐너 지적 사항은 설명됨). 스캔이 깨끗하다고 해서 스킬이 안전하다는 증거는 아닙니다.
설치
npx skills@latest add vercel-labs/agent-skills --skill react-best-practices
고정 커밋: 1a2c79d4e6c4a2283dc2c614b6538cbe1ed5ca62
테스트일: 2026-11-02
테스트한 과제
- 대시보드 성능 리뷰 — OrdersDashboard.tsx와 page.tsx(Next.js 15 앱)에서 성능 문제를 리뷰하세요. 가장 심각한 것부터 나열하고, 각 항목에 파일과 무엇을 바꿀지 적으세요. 파일은 수정하지 마세요. 목록은 최종 답변에 작성하세요.
- 대시보드 컴포넌트를 그 자리에서 최적화 — OrdersDashboard.tsx를 성능 관점에서 최적화하세요. 파일을 그 자리에서 수정하되 동작과 컴포넌트의 props는 그대로 유지하세요. (네트워크와 npm install이 없으니 실행하려 하지 마세요.)
- 대시보드 서버 페이지 작성 — app/dashboard/page.tsx를 작성하세요. Next.js 15 서버 컴포넌트(App Router)입니다. 로그인한 사용자('@/lib/auth'의 getSession()이 { userId }를 반환)에 대해 '@/lib/data'의 getProfile(userId), getRecentOrders(userId), getRecommendations(userId)가 필요합니다…
장점
- 스킬 없이보다 평균 점수를 3.57에서 4.43으로 끌어올렸습니다(+0.85).
- 스킬 이름을 언급하지 않았는데도 적용되었습니다. 스킬 사용군 실행 6/6회에서 스킬 파일을 읽었습니다.
단점
- 한 문장 프롬프트는 4.73점, 스킬 사용은 4.43점(-0.30).
- 스킬 없이보다 토큰을 1.53배 사용합니다.
- 과제·그룹당 n=2이며, 약 1점 미만의 차이는 노이즈입니다.
리뷰
Codex CLI 0.166 (gpt-6-astra, low reasoning)을 Mac 테스트 머신에서 실행했고, 실행마다 새 임시 프로젝트와 임시 HOME/TMPDIR/캐시를 사용했습니다. 스킬 설치(스킬 사용군), 미설치(대조군), 미설치 상태에서 스킬의 핵심 요구사항만 한 문장으로 압축해 프롬프트에 덧붙인 경우(한 문장 프롬프트군)로 나눴으며, 과제·그룹당 2회씩 실행했습니다. 네트워크는 끄고 /tmp 쓰기는 차단했습니다. 프롬프트에는 스킬 이름을 언급하지 않았습니다(아래에 적은 경우 제외). 작은 Next.js 15 픽스처 세 가지로 세 가지 과제를 냈습니다: 대시보드 컴포넌트와 페이지의 성능 리뷰(심어 둔 문제 11개, 심각한 순서대로 나열), 컴포넌트를 그 자리에서 최적화(검사 항목 10개: Promise.all, 배럴 대신 직접 임포트, lodash 전체 임포트 금지, 무거운 에디터에 next/dynamic, Row를 컴포넌트 밖으로 끌어올리기, effect 안의 setFiltered 대신 파생 목록, orders.length && 금지, 지연 상태 초기화, Map 조회, passive 스크롤 리스너), 느린 데이터 호출 하나와 빠른 호출 둘을 처리하는 서버 페이지 작성.
3개 그룹 비교
| 그룹 | 평균 점수 (0-5) | 실행당 토큰 |
|---|---|---|
| 스킬 없음 | 3.57 | 67k |
| 한 문장 프롬프트 | 4.73 | 77k |
| 스킬 사용 | 4.43 | 103k |
Codex CLI에서 18회 실행해 측정했고, LLM 심사 없이 규칙 기반 검사로 채점했습니다. 총점 24.3 / 30, 판정 상황에 따라.
판정: 상황에 따라 (24.3 / 30). 출처: vercel-labs/agent-skills, skills/react-best-practices, 커밋 1a2c79d에 고정. 설치: npx skills@latest add vercel-labs/agent-skills --skill react-best-practices. 라이선스: MIT(저장소 LICENSE).
무엇인가
vercel-labs/agent-skills의 vercel-react-best-practices 스킬입니다(디렉터리 react-best-practices). 영향도별로 묶인 70개의 Markdown 규칙 파일(요청 폭포, 번들 크기, 서버 측 성능, 클라이언트 데이터 페칭, 리렌더, 렌더링, JS 성능, 고급 패턴)과 컴파일된 AGENTS.md가 있습니다. SKILL.md는 색인이며 에이전트가 필요한 규칙을 로드합니다. 스크립트는 없습니다.
테스트 방법
Codex CLI 0.166(gpt-6-astra, 낮은 추론)을 Mac 테스트 머신에서 실행했고, 실행마다 새 임시 프로젝트와 임시 HOME/TMPDIR/캐시를 사용했습니다. 스킬을 설치한 스킬 사용군, 스킬이 없는 대조군, 스킬 없이 핵심 요구사항을 한 문장으로 압축해 프롬프트 끝에 붙인 한 문장 프롬프트군으로 나눴으며, 작업·군당 2회 실행, 네트워크 차단, /tmp 쓰기 차단 조건이었습니다. 프롬프트에는 스킬 이름을 전혀 언급하지 않았습니다(아래에 적은 표현 제외).
작은 Next.js 15 픽스처로 세 가지 작업을 했습니다. 대시보드 컴포넌트와 페이지의 성능 리뷰(심어 둔 문제 11개, 가장 심각한 것부터 나열), 컴포넌트를 그 자리에서 최적화(검사 10개: Promise.all, 배럴 대신 직접 import, lodash 전체 import 금지, 무거운 에디터에 next/dynamic, Row를 컴포넌트 밖으로 호이스팅, effect의 setFiltered 대신 파생 목록, orders.length && 금지, 지연 상태 초기화, Map 조회, passive 스크롤 리스너), 느린 호출 하나와 빠른 호출 둘을 위한 서버 페이지 작성.
- 대시보드 성능 리뷰 — OrdersDashboard.tsx와 page.tsx(Next.js 15 앱)의 성능 문제를 검토하세요. 가장 심각한 것부터 나열하고, 각 항목에 파일과 변경할 내용을 적으세요. 파일은 변경하지 마세요. 목록은 최종 답변에 작성하세요.
- 대시보드 컴포넌트 최적화 — OrdersDashboard.tsx를 성능 중심으로 최적화하세요. 파일을 그 자리에서 편집하되 동작과 컴포넌트 props는 그대로 유지하세요. (네트워크와 npm install이 없으니 실행하지 마세요.)
- 대시보드 서버 페이지 작성 — app/dashboard/page.tsx를 작성하세요. Next.js 15 서버 컴포넌트(App Router)입니다. 로그인한 사용자('@/lib/auth'의
getSession()이{ userId }를 반환)에 대해 '@/lib/data'의getProfile(userId),getRecentOrders(userId),getRecommendations(userId)가 필요합…
채점은 프로그램 방식(숨은 테스트/심어 둔 문제 탐지/제약 조건 검사/해당되는 경우 렌더링+OCR)으로 진행하며 실행당 0~5점입니다. 어떤 점수도 LLM 심사자가 결정하지 않았습니다.
결과
세 번째 군에 제공한 한 문장 프롬프트: "React/Next.js 성능을 다음 순서로 고치세요. 요청 폭포를 없애고(Promise.all로 독립 페칭을 함께 시작하고 느린 부분은 Suspense로 스트리밍), 번들 크기를 줄이고(배럴 파일을 거치지 않고 직접 import하고 무거운 컴포넌트는 next/dynamic으로 코드 분할), 그다음 불필요한 리렌더를 피하세요(렌더 중 값 파생, 컴포넌트 안에서 컴포넌트 정의 금지, 정적 값 호이스팅, 지연 상태 초기화, Map 조회, passive 스크롤 리스너 사용)."
| 작업 | 대조군 점수 | 한 문장 프롬프트 | 스킬 사용 | 대조군 토큰 | 한 문장 토큰 | 스킬 사용 토큰 | 스킬 읽음 |
|---|---|---|---|---|---|---|---|
| t1-review | 3.91 | 4.64 | 4.64 | 65k | 45k | 75k | 2/2 |
| t2-optimize | 2.27 | 4.55 | 3.64 | 90k | 130k | 163k | 2/2 |
| t3-write | 4.54 | 5.0 | 5.0 | 45k | 53k | 69k | 2/2 |
평균 점수: 스킬 없음 3.57 vs 스킬 사용 4.43(델타 +0.85). 실행당 토큰: 67k vs 103k(1.53배), 시간 47초 vs 52초. 한 문장 프롬프트군: 4.73(한 문장 프롬프트 대비 스킬의 이득: -0.3, 스킬 없음 대비 한 문장 프롬프트의 이득: +1.15). 한 문장 프롬프트는 실행당 77k 토큰이므로 스킬 비용은 한 문장 프롬프트의 1.34배입니다.
무슨 일이 있었나
- 제자리 최적화에서 차이가 드러납니다. 대조군 2.27, 스킬 3.64, 한 문장 버전 4.55(각 군의 두 실행이 동일). 대조군과 스킬 모두 배럴 import
from '@/components'를 그대로 두고 코드 분할을 하지 않았습니다. 대조군은 순차 await와 non-passive 스크롤 리스너도 그대로 두었습니다. 스킬은 폭포와 스크롤 리스너는 고쳤지만 배럴 import와 무거운 에디터의 동적 import는 고치지 않았습니다. 바로 그 규칙을 명시한 한 문장 프롬프트는 둘 다 고쳤습니다. - 리뷰 작업: 심어 둔 문제를 대조군은 9개, 7개(평균 8개) 찾았고, 스킬과 한 문장 프롬프트는 각각 10개, 10개를 찾았습니다(각 11개 중 10개.
orders.length &&가 0을 렌더링하는 문제는 모든 군의 모든 실행이 놓쳤습니다). 점수는 대조군 3.91, 스킬 4.64, 한 문장 4.64. - 작성 작업: 모든 군이 느린 추천 호출을 Suspense 뒤에 두고 페이지를 서버 컴포넌트로 유지했습니다. 대조군은 때때로 세 호출을 순차적으로 await했습니다(점수 4.1과 5.0, 스킬 5.0 두 번, 한 문장 5.0 두 번).
- 어떤 군도 파일을 편집할 때
orders.length &&를 고치지 않았습니다. 따라서 스킬에 있는 규칙(rendering-conditional-render)이 어떤 편집에도 반영되지 않았습니다. - 스킬은 6/6 실행에서 읽혔고, 103k 토큰으로 대조군 67k(1.5배), 한 문장 77k(1.3배)였습니다. 전체 평균은 대조군 3.57, 스킬 4.43, 한 문장 4.73입니다.
- 분석 중 채점을 한 번 수정했습니다. 첫 번째 패스에서는 파일이 app/dashboard/page.tsx에 작성되었고 Map에 타입이 붙어 있었다는 이유로(
new Map<string, string>()) 페이지와 Map 검사를 잘못 누락으로 표시했습니다. 두 정규식을 고치고 18회 실행을 모두 다시 확인했습니다.
항목별 점수
| 항목 | 점수 | 이유 |
|---|---|---|
| 1_출력_품질 | 4.43 | 스킬 사용 시 평균 4.43/5(프로그램 검사, 작업별 표 참고). |
| 2_스킬_없음_대비_향상 | 2.85 | 스킬 없음 3.57 대비 +0.85점(작업당 n=2, 약 1점 미만은 노이즈). |
| 3_트리거 | 5.0 | 스킬 사용군 6/6 실행에서 스킬 파일을 읽었습니다. |
| 4_지침_이식성 | 4 | React/Next.js 특화. 다른 에이전트도 로드할 수 있는 평범한 Markdown이며 공개 스킬 레이아웃을 따릅니다. |
| 5_효율성 | 3 | 토큰 1.53배(실행당 103k vs 67k), 시간 1.10배(52초 vs 47초). |
| 6_안전성_및_위생 | 5 | Markdown 규칙 파일만 사용합니다. 스캐너가 잡은 6건은 모두
|
이 테스트의 한계
- 교과서적인 알려진 문제가 있는 작은 픽스처입니다. 컴포넌트 하나만 최적화했기 때문에 군당 두 실행이 같은 이유로 같은 결과를 보였습니다(두 실행 동일).
- 스킬의 70개 규칙은 세 작업을 통해서만 활용되었습니다. 서버 캐싱, SWR, 이벤트 리스너, 고급 규칙은 심어 두지 않았습니다.
- 한 문장 버전은 테스트가 검사하는 규칙을 그대로 나열하므로 짧은 프롬프트의 상한선이며, 규칙을 모르는 사용자를 공정하게 대표하지는 않습니다.
- 작업·군당 n=2: 약 1점 미만의 차이는 노이즈입니다.
- 규칙 기반 채점은 검사가 볼 수 있는 것만 측정하며, 취향이나 결과가 최선인지는 판단하지 못합니다.
보안
security.md를 참고하세요. 스캔: CRITICAL 6 / HIGH 0 / MEDIUM 0. 판정: 실행해도 안전(네트워크 차단 샌드박스). 깨끗한 스캔 결과가 스킬의 안전성을 증명하지는 않습니다.
결론
이 스킬은 아무것도 하지 않는 것보다 성능 리뷰와 제자리 최적화를 분명히 개선하며, 그것이 스킬의 목적입니다. 같은 규칙을 이름으로 언급한 짧은 프롬프트가 최적화 작업에서 더 나은 성과를 더 적은 비용으로 냈습니다. 따라서 가치는 주로 규칙 이름을 아는 데 있습니다. 스킬의 규칙 파일은 사용자가 규칙을 몰라도 되게 해 주었지만, 이번 실행에서는 배럴 import와 코드 분할 규칙까지 도달하지는 못했습니다.
트랜스크립트
실행 로그: vercel-react-best-practices (Codex CLI, network off, per-run temp HOME/TMPDIR, one row per graded run.) 열: 작업, 그룹-회차, 점수(0-5), 토큰, 초, 통과한 검사, 에이전트가 연 스킬 파일. t1-review control-1: 점수 4.27, 토큰 65319, 초 49.8, 검사 2/2, 연 파일 없음 t1-review control-2: 점수 3.55, 토큰 65797, 초 42.7, 검사 2/2, 연 파일 없음 t1-review oneliner-1: 점수 4.64, 토큰 45347, 초 43.5, 검사 2/2, 연 파일 없음 t1-review oneliner-2: 점수 4.64, 토큰 45242, 초 52.5, 검사 2/2, 연 파일 없음 t1-review treatment-1: 점수 4.64, 토큰 75664, 초 52.7, 검사 2/2, 연 파일 react-best-practices t1-review treatment-2: 점수 4.64, 토큰 75106, 초 52.1, 검사 2/2, 연 파일 react-best-practices t2-optimize control-1: 점수 2.27, 토큰 81770, 초 63.5, 검사 5/10, 연 파일 없음 t2-optimize control-2: 점수 2.27, 토큰 99616, 초 48.7, 검사 5/10, 연 파일 없음 t2-optimize oneliner-1: 점수 4.55, 토큰 141803, 초 75.2, 검사 9/10, 연 파일 없음
18회 실행, 아티팩트 3개
샘플 출력
SOTA · With the skill: final message (t1-review, run 1, Codex)
Ranked by likely impact; no files changed. 1. **`OrdersDashboard.tsx:16` — Storage access breaks server rendering and repeats on every render.** `localStorage` is unavailable during Next.js prerendering; `'use client'` doesn’t prevent that. Initialize settings with a stable default, then read and safely parse storage once after mounting. A lazy initializer alone won’t fix server access. [Next.js rendering guidance](https://nextjs.org/docs/app/getting-started/server-and-client-components) 2. **`page.tsx:8–11` — Independent server requests run sequentially.** After `getUser`, start settings, invoices, and notifications together using `Promise.all()`. Their latency currently adds up unnecessarily. 3. **`OrdersDashboard.tsx:19–28` — Initial data waits for hydration, then follows a request waterfall.** Fetch initial data in a server parent where practical. Otherwise, after fetching `me`, request orders and customers concurrently with `Promise.all()`. Neither depends on the other. 4. **`OrdersDashboard.tsx:45–47` — Every dashboard render remounts every row.** Defining `Row` inside the component creates a new component type each render, including during scrolling and typing. Move it to module scope; consider `React.memo` with stable props. 5. **`OrdersDashboard.tsx:17,34–38,55` — Scrolling rerenders the entire dashboard.** Each scroll update repeats filtering, sorting, joins, and rendering. Move scroll state into a small standalone indicator component; throttle updates if exact continuous tracking isn’t necessary. 6. **`OrdersDashboard.tsx:7,54` — The markdown preview is eagerly loaded even for an empty footer.** Use `next/dynamic` for the preview and render it only when footer content exists. Defer mounting until needed if it is below the fold. 7. **`OrdersDashboard.tsx:40–43` — Repeated expensive calculations and quadratic customer lookups.** Memoize derived results, build a customer-ID `Map` when customers change, and parse dates once before sorting instead of inside every comparison. The join then costs O(orders + customers), rather than O(orders × customers). 8. **`OrdersDashboard.tsx:15,30–32` — Derived state causes an extra render per search/data change.** Remove `filtered` state and its effect; derive it with `useMemo`. For large datasets, use `useDeferredValue(query)` with memoized results to keep typing responsive. 9. **`page.tsx:9–15` — All sections wait for the slowest request.** Start requests together, then await them inside separate async sections under `Suspense`. This allows the sidebar to appear without waiting for feed data; wrapping JSX after all awaits won’t help. 10. **`OrdersDashboard.tsx:53` — Every matching row is mounted.** For large order collections, paginate or virtualize the table. Prefer server pagination/filtering when fetching the entire collection also becomes expensive. 11. **`OrdersDashboard.tsx:3–6` — Import cleanup and a potential barrel-import cost.** Remove unused lodash, icons, `Modal`, and `isAfter`; import used local components directly rather than through `@/components`. Confirm bundle impact before claiming unused imports ship to clients. Named `date-fns` and `lucide-react` imports already rece [... excerpt: first 3200 of 3335 characters]