All skills

vercel-react-best-practices (vercel-labs/agent-skills)

Vercel's React best practices: lifts a Next.js optimisation from 2.3 to 3.6/5, but the one-sentence version reaches 4.6 for 75% of the tokens

SituationalSafe

24.3 / 30

Lift over no skill: +0.85

Situational: the lift +0.85 is under +1.0. Recommended needs a total of 22/30, output quality of 3.5, and a lift of +1.0.

SOTA

Pass

Tested with Codex.

Covers Codex, Claude Code

Codex CLI 0.166 (SOTA tier): 18 graded runs. A pass here also covers Claude Code under our tier rule; it says nothing about the second tier.

Scores

Each dimension is 0 to 5 and the six add up to the total out of 30. 5 is better on every dimension. Cost 5 means cheap and fast. Install 5 means easy.

Trigger reliability
5.0
Lift over no skill
2.9
Output quality
4.4
Token and time cost
3.0
Install and portability
4.0
Safety
5.0

Security review

Safe

Static scan: 6 CRITICAL / 0 HIGH / 0 MEDIUM. Manual review: Safe to run in a no-network sandbox (scanner findings explained). A clean scan does not prove a skill safe.

Install

npx skills@latest add vercel-labs/agent-skills --skill react-best-practices

Source

Pinned commit: 1a2c79d4e6c4a2283dc2c614b6538cbe1ed5ca62

Tested: 2026-11-02

What we tested

  • Performance review of a dashboard — Review OrdersDashboard.tsx and page.tsx (a Next.js 15 app) for performance problems. List them most serious first, each with the file and what to change. Do not change any files. Write the list in your final answer.
  • Optimise the dashboard component in place — Optimise OrdersDashboard.tsx for performance. Edit the file in place, keep the behaviour and the component's props the same. (There is no network and no npm install; do not try to run it.)
  • Write a dashboard server page — Write app/dashboard/page.tsx, a Next.js 15 server component (App Router). For the logged-in user (getSession() from '@/lib/auth' returns { userId }) it needs getProfile(userId), getRecentOrders(userId) and getRecommendations(userId) from '@/lib/data'…

Pros

  • Lifts the mean score from 3.57 to 4.43 over no skill (+0.85).
  • The skill was picked up without being named: Skill file read in 6/6 treatment runs.

Cons

  • A one-sentence prompt scored 4.73, against 4.43 with the skill (-0.30).
  • Uses 1.53x the tokens of no skill.
  • n=2 per task per arm; differences under about 1 point are noise.

Review

Codex CLI 0.166 (gpt-6-astra, low reasoning) on a Mac test machine, a fresh temporary project and temporary HOME/TMPDIR/caches per run, skill installed (treatment) or absent (control) or absent with the skill's core requirement squeezed into one sentence appended to the prompt (one-liner group), 2 runs per group per task, network off, /tmp writes blocked. The prompts never mention the skill (except the wording noted below). Three tasks on small Next.js 15 fixtures: review a dashboard component and page for performance (11 planted problems, listed most serious first); optimise the component in place (10 checks: Promise.all, direct imports instead of a barrel, no whole-lodash import, next/dynamic for a heavy editor, Row hoisted out of the component, derived list instead of setFiltered in an effect, no orders.length &&, lazy state initialiser, Map lookup, passive scroll listener); write a server page for a slow and two fast data calls.

Three-arm comparison

ArmMean score (0-5)Tokens per run
Without the skill3.5767k
One-sentence prompt4.7377k
With the skill4.43103k

Measured with Codex CLI over 18 runs, scored by rule-based checks with no LLM judge. Total 24.3 / 30, verdict Situational.

Verdict: Situational (24.3 / 30). Source: vercel-labs/agent-skills, skills/react-best-practices, pinned at commit 1a2c79d. Install: npx skills@latest add vercel-labs/agent-skills --skill react-best-practices. Licence: MIT (repo LICENSE).

What it is

The vercel-react-best-practices skill from vercel-labs/agent-skills (directory react-best-practices): 70 Markdown rule files, grouped by impact (request waterfalls, bundle size, server-side performance, client data fetching, re-renders, rendering, JS performance, advanced patterns), plus a compiled AGENTS.md. SKILL.md is an index; the agent loads the rules it needs. No scripts.

How we tested

Codex CLI 0.166 (gpt-6-astra, low reasoning) on a Mac test machine, a fresh temporary project and temporary HOME/TMPDIR/caches per run, skill installed (treatment) or absent (control) or absent with the skill's core requirement squeezed into one sentence appended to the prompt (one-liner group), 2 runs per group per task, network off, /tmp writes blocked. The prompts never mention the skill (except the wording noted below).

Three tasks on small Next.js 15 fixtures: review a dashboard component and page for performance (11 planted problems, listed most serious first); optimise the component in place (10 checks: Promise.all, direct imports instead of a barrel, no whole-lodash import, next/dynamic for a heavy editor, Row hoisted out of the component, derived list instead of setFiltered in an effect, no orders.length &&, lazy state initialiser, Map lookup, passive scroll listener); write a server page for a slow and two fast data calls.

  1. Performance review of a dashboard — Review OrdersDashboard.tsx and page.tsx (a Next.js 15 app) for performance problems. List them most serious first, each with the file and what to change. Do not change any files. Write the list in your final answer.
  2. Optimise the dashboard component in place — Optimise OrdersDashboard.tsx for performance. Edit the file in place, keep the behaviour and the component's props the same. (There is no network and no npm install; do not try to run it.)
  3. Write a dashboard server page — Write app/dashboard/page.tsx, a Next.js 15 server component (App Router). For the logged-in user (getSession() from '@/lib/auth' returns { userId }) it needs getProfile(userId), getRecentOrders(userId) and getRecommendations(userId) from '@/lib/data'…

Scoring is programmatic (hidden tests / planted-issue recall / constraint checks / render+OCR where relevant), 0–5 per run. No LLM judge decided any score.

Results

The one-liner given to the third group: "Fix React/Next.js performance in this order: remove request waterfalls (start independent fetches together with Promise.all and stream slow parts with Suspense), cut bundle size (import directly instead of through barrel files, code-split heavy components with next/dynamic), then avoid needless re-renders (derive values during render, do not define components inside components, hoist static values, use lazy state initialisers, Map lookups, passive scroll listeners)."

TaskControl scoreOne-linerWith skillControl tokensOne-liner tokensWith-skill tokensSkill read
t1-review3.914.644.6465k45k75k2/2
t2-optimize2.274.553.6490k130k163k2/2
t3-write4.545.05.045k53k69k2/2

Mean score: 3.57 without vs 4.43 with the skill (delta +0.85). Tokens per run: 67k vs 103k (1.53x); time 47s vs 52s. One-liner group: 4.73 (the skill's gain over the one-liner: -0.3; one-liner's own gain over no skill: +1.15); one-liner tokens 77k per run, so the skill costs 1.34x the one-liner.

What happened

  • Optimising in place is where it shows: score 2.27 control, 3.64 skill, 4.55 one-sentence version (both runs of each group identical). Control and skill both left the barrel import from '@/components' and kept no code-splitting; control also left the sequential awaits and the non-passive scroll listener. The skill fixed the waterfall and the scroll listener but not the barrel import or the dynamic import of the heavy editor; the one-liner, which names exactly those rules, fixed both.
  • Review task: planted problems found 9, 7 for control (8 average) against 10, 10 for both the skill and the one-liner (10 of 11 each; the orders.length && rendering a 0 was missed by every run in every group). Score 3.91 control, 4.64 skill, 4.64 one-liner.
  • Write task: all groups put the slow recommendations behind Suspense and kept the page a server component; control sometimes awaited all three calls in sequence (score 4.1 and 5.0, skill 5.0 both, one-liner 5.0 both).
  • No group fixed orders.length && when editing the file, so a rule that is in the skill (rendering-conditional-render) did not make it into any edit.
  • The skill was read in 6/6 runs and cost 103k tokens against 67k (1.5x) and 77k for the one-liner (1.3x). Overall mean 3.57 control, 4.43 skill, 4.73 one-liner.
  • The scoring was corrected once during analysis: the first pass wrongly marked the page and Map checks as misses because the file was written to app/dashboard/page.tsx and the Map was typed (new Map<string, string>()); both regexes were fixed and all 18 runs re-checked.

Dimension scores

DimensionScoreWhy
1_output_quality4.43

Mean score 4.43/5 with the skill (programmatic checks, see per-task table).

2_lift_vs_no_skill2.85

+0.85 points vs 3.57 without the skill (n=2 per task; under ~1 point is noise).

3_trigger5.0Skill file read in 6/6 treatment runs.
4_instructions_portability4

React/Next.js specific; plain Markdown that other agents can load; follows the open skills layout.

5_efficiency31.53x tokens (103k vs 67k per run) and 1.10x time (52s vs 47s).
6_safety_hygiene5

Markdown rule files only; 6 scanner hits are document.cookie in code examples, read in context.

Limits of this test

  • Small fixtures with known textbook problems; only one component was optimised, so two runs per group show the same result for the same reason (two runs identical).
  • The skill's 70 rules were exercised only through three tasks; server caching, SWR, event-listener and advanced rules were not planted.
  • The one-sentence version lists the same rules the test checks, so it is an upper bound for a short prompt, not a fair stand-in for a user who does not know the rules.
  • n = 2 per group per task: differences under about 1 point are noise.
  • Rule-based scoring measures what the checks can see; it cannot judge taste or whether a result is the best possible.

Security

See security.md. Scan: 6 CRITICAL / 0 HIGH / 0 MEDIUM. Verdict: safe to run (no-network sandbox). A clean scan does not prove a skill safe.

Bottom line

The skill clearly improves a performance review and an in-place optimisation compared with doing nothing, which is the point of it. A short prompt that names the same rules did better on the optimise task and cost less, so the value is mostly in knowing the rule names; the skill's rule files saved a user from having to know them but did not reach the barrel-import and code-splitting rules in these runs.

Transcript

Run log for vercel-react-best-practices: Codex CLI, network off, per-run temp HOME/TMPDIR, one row per graded run.
Columns: task, arm-run, score (0-5), tokens, seconds, checks passed, skill files the agent opened.

t1-review control-1: score 4.27, tokens 65319, secs 49.8, checks 2/2, opened nothing
t1-review control-2: score 3.55, tokens 65797, secs 42.7, checks 2/2, opened nothing
t1-review oneliner-1: score 4.64, tokens 45347, secs 43.5, checks 2/2, opened nothing
t1-review oneliner-2: score 4.64, tokens 45242, secs 52.5, checks 2/2, opened nothing
t1-review treatment-1: score 4.64, tokens 75664, secs 52.7, checks 2/2, opened react-best-practices
t1-review treatment-2: score 4.64, tokens 75106, secs 52.1, checks 2/2, opened react-best-practices
t2-optimize control-1: score 2.27, tokens 81770, secs 63.5, checks 5/10, opened nothing
t2-optimize control-2: score 2.27, tokens 99616, secs 48.7, checks 5/10, opened nothing
t2-optimize oneliner-1: score 4.55, tokens 141803, secs 75.2, checks 9/10, opened nothing

18 runs, 3 artifacts

Sample output

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]

See Pro