Creative Tim UI Blocks
creativetimofficial/ui
Helps install, generate and review Creative Tim UI blocks: shadcn/ui-based React and Tailwind sections that follow a restrained, production-minded design philosophy.
A skill your agent uses when building, reviewing, testing, securing, or optimizing a Next.js App Router app: Server vs Client boundaries, use server actions, route handlers, the v15 vs v16 use cache…
$ npx skills add ericrisco/rsc-harness --skill nextjs -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install ericrisco/rsc-harness nextjs --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/ericrisco/rsc-harness.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/nextjs .claude/skills/nextjs && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "nextjs" agent skill from https://github.com/ericrisco/rsc-harness/tree/main/skills/nextjs into .claude/skills/nextjs/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "nextjs", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/ericrisco/rsc-harness/tree/main/skills/nextjsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add ericrisco/rsc-harness --skill nextjs -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install ericrisco/rsc-harness nextjs --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ericrisco/rsc-harness.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/nextjs .agents/skills/nextjs && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "nextjs" agent skill from https://github.com/ericrisco/rsc-harness/tree/main/skills/nextjs into .agents/skills/nextjs/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "nextjs", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add ericrisco/rsc-harness --skill nextjs -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install ericrisco/rsc-harness nextjs --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ericrisco/rsc-harness.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/nextjs .cursor/skills/nextjs && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "nextjs" agent skill from https://github.com/ericrisco/rsc-harness/tree/main/skills/nextjs into .cursor/skills/nextjs/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "nextjs", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/ericrisco/rsc-harness.git --path skills/nextjs--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add ericrisco/rsc-harness --skill nextjs -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install ericrisco/rsc-harness nextjs --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ericrisco/rsc-harness.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/nextjs .gemini/skills/nextjs && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "nextjs" agent skill from https://github.com/ericrisco/rsc-harness/tree/main/skills/nextjs into .gemini/skills/nextjs/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "nextjs", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install ericrisco/rsc-harness nextjsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add ericrisco/rsc-harness --skill nextjs -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/ericrisco/rsc-harness.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/nextjs .github/skills/nextjs && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "nextjs" agent skill from https://github.com/ericrisco/rsc-harness/tree/main/skills/nextjs into .github/skills/nextjs/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "nextjs", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add ericrisco/rsc-harness --skill nextjs -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install ericrisco/rsc-harness nextjs --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ericrisco/rsc-harness.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/nextjs .opencode/skills/nextjs && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "nextjs" agent skill from https://github.com/ericrisco/rsc-harness/tree/main/skills/nextjs into .opencode/skills/nextjs/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "nextjs", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
nextjsA skill your agent uses when building, reviewing, testing, securing, or optimizing a Next.js App Router app: Server vs Client boundaries, use server actions, route handlers, the v15 vs v16 use cache…
Nextjs is an agent skill from ericrisco/rsc-harness. Use when building, reviewing, testing, securing, or optimizing a Next.js App Router app: Server vs Client boundaries, use server actions, route handlers, the v15 vs v16 use cache caching model, metadata/SEO, auth, and Core Web Vitals. NOT framework-agnostic React or a Vite SPA (that is react), and NOT visual/UI design (that is design).
Its SKILL.md is about 5.1k tokens, which your agent loads only when the skill is triggered. The skill folder holds 12 other files, including scripts and reference files (for example `evals/README.md`, `evals/cases.yaml` and `references/data-and-caching.md`).
It sits in Frontend & Design, covering Caching, UI design and Web performance. It works with Next.js, React and Vite. The repository describes itself as: Your agent invents things because it has no memory, and can't touch your database because it has no arms. rsc is the meta-harness that gives it both, plus the trade to know the… The licence is MIT.
4 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit e3d5b33. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 1 file in scripts/ (Shell), which the agent can run.
Shell commands in SKILL.md call:
bashtscFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
NEXT_PUBLIC_API_KEYFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Nextjs loads about 5.1k tokens when it runs, and up to ~21k if it reads all its reference files. Until then it costs about 88 tokens; SKILL.md has 1,544 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from ericrisco/rsc-harness at commit e3d5b33, republished under its MIT licence (© ericrisco). 1,544 words, ~5,145 tokens.
.claude/skills/nextjs/SKILL.md (or your agent's skills folder). This skill also uses 9 other files; get the full folder from GitHub.Build, review, test, secure and optimize App Router apps, handling both the Next.js 15 (uncached-by-default) and Next.js 16 (
use cache) caching models correctly.
SDD gate — read before writing code. If this fired on a new, non-trivial feature or behaviour change and there is no approved spec + plan under
02-DOCS/wiki/sdd/, STOP and hand off to../specify/SKILL.md(brainstorm → spec → plan → tasks); it routes back here once the plan is approved. Build directly only for a genuinely one-line / low-risk change. Method:../sdd/SKILL.md.
Not this skill: Pages Router (pages/) — note the difference, defer to the Next.js Pages docs.
A pure React SPA (Vite/CRA) → ../react/SKILL.md; React Native / Expo → ../react-native/SKILL.md;
a generic React question with no Next/RSC dimension → keep it brief, from references/react.md.
Non-Next backends → ../fastapi/SKILL.md, ../go/SKILL.md; the data layer behind the DAL →
../postgresdb/SKILL.md; framework-agnostic security → ../secure-coding/SKILL.md, complemented here, never duplicated.
Run this before prescribing or reviewing any caching, middleware, or React-Compiler behavior. Never mix v15 and v16 advice.
package.json → the next version.next.config.{ts,js,mjs} for cacheComponents, ppr, reactCompiler, experimental.proxy.ts at the root ⇒ v16; middleware.ts ⇒ v15 (or v16 not yet migrated).cacheComponents: true OR any "use cache" in the tree ⇒ Cache Components model (opt-in
caching). Otherwise ⇒ v15 model (uncached fetch by default, revalidate/tags).Do not flag proxy.ts, use cache, or cacheComponents as errors — they are correct on
Next.js 16.
| Signal in repo | Model | Caching API to use |
|---|---|---|
cacheComponents: true or any "use cache" | Cache Components (v16) | "use cache" + cacheLife() + cacheTag()/updateTag() |
middleware.ts, no cacheComponents | v15 baseline | fetch(..., { next: { revalidate, tags } }), unstable_cache, revalidateTag |
proxy.ts present | v16 routing | middleware logic lives in proxy.ts (NOT a security boundary) |
reactCompiler: true | Compiler on | drop manual useMemo/useCallback/React.memo (review-only) |
Default is a Server Component (async, can touch the DB and secrets, ships zero JS). Opt into a Client Component only for state, effects, event handlers, or browser APIs.
The four boundary laws:
children (no functions except Server Actions).import a Server Component into a Client Component; compose via children."use client" marks a module and its whole import subtree as client."use client" leaves small; push the directive down the tree.// app/projects/[id]/page.tsx — Good: server async page + a tiny client island
import { getProject } from "@/lib/dal";
import { LikeButton } from "./like-button";
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const project = await getProject(id); // DB call stays on the server
return (
<main>
<h1>{project.name}</h1>
<LikeButton projectId={project.id} initialLikes={project.likes} />
</main>
);
}When a Client Component needs server content, give it a children (or prop) slot and pass the
Server Component from a server parent — <ClientPanel><ServerChart /></ClientPanel>. The
import-graph rule and the full Bad/Good contrast are in references/react.md (Server vs Client deep dive).
Every Server Action is a public POST endpoint. It MUST authenticate and authorize itself. Middleware/proxy does NOT protect it.
// app/projects/actions.ts
"use server";
import { z } from "zod";
import { revalidateTag } from "next/cache";
import { auth } from "@/auth";
import { db } from "@/lib/db";
const RenameSchema = z.object({ id: z.string().uuid(), name: z.string().min(1).max(120) });
type RenameResult =
| { status: "ok"; data: { id: string; name: string } }
| { status: "error"; message: string };
export async function renameProject(_prev: RenameResult | null, formData: FormData): Promise<RenameResult> {
const session = await auth();
if (!session?.user) return { status: "error", message: "Not authenticated" };
const parsed = RenameSchema.safeParse(Object.fromEntries(formData));
if (!parsed.success) return { status: "error", message: "Invalid input" };
const owned = await db.project.findFirst({ where: { id: parsed.data.id, ownerId: session.user.id } });
if (!owned) return { status: "error", message: "Forbidden" };
const updated = await db.project.update({ where: { id: parsed.data.id }, data: { name: parsed.data.name } });
revalidateTag(`project:${updated.id}`);
return { status: "ok", data: { id: updated.id, name: updated.name } };
}Two invocation modes: <form action={renameProject}> — progressive enhancement, works without JS —
or imperative from a client handler wrapped in startTransition(() => renameProject(null, fd)).
route.ts)Use a Route Handler for: webhooks, a public JSON API, OAuth callbacks, streaming responses, and
non-form clients. Use a Server Action instead for internal form mutations. GET handlers are
uncached by default on v15 (control with export const dynamic / runtime), and every handler —
GET included — runs its own auth() check and scopes reads to the session user.
// app/api/projects/route.ts
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { auth } from "@/auth";
import { db } from "@/lib/db";
const CreateSchema = z.object({ name: z.string().min(1).max(120) });
export async function POST(req: NextRequest) {
const session = await auth();
if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const parsed = CreateSchema.safeParse(await req.json());
if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 422 });
const created = await db.project.create({ data: { name: parsed.data.name, ownerId: session.user.id } });
return NextResponse.json({ project: created }, { status: 201 });
}| File | Role / when it runs |
|---|---|
layout.tsx | Wraps a segment; persists across navigation, does NOT remount |
template.tsx | Like layout but remounts on every navigation (fresh state) |
loading.tsx | Instant Suspense fallback for the segment while it streams |
error.tsx | "use client" error boundary for the segment, gets reset() |
not-found.tsx | Rendered by notFound() and unmatched routes |
global-error.tsx | Replaces the root layout when the root throws |
An error.tsx is always "use client", receives { error: Error & { digest?: string }, reset },
and should render role="alert" plus a button calling reset().
// app/dashboard/page.tsx — Good: stream the shell, Suspense the slow part
import { Suspense } from "react";
import { Stats } from "./stats";
export default function Page() {
return (
<main>
<h1>Dashboard</h1>
<Suspense fallback={<p>Loading stats…</p>}>
<Stats /> {/* async Server Component; the shell paints immediately */}
</Suspense>
</main>
);
}(marketing)/ organize without affecting the URL; dynamic [id], catch-all
[...slug], optional [[...slug]].params and searchParams are Promises on v15+ — await them.@modal + default.tsx; intercepting (.)photo — modal-on-navigation.generateMetadata (async) + generateStaticParams.// Bad: treating params as a plain object (the top v15-migration bug)
function PageBad({ params }: { params: { id: string } }) {
return <h1>{params.id}</h1>; // runtime/type error on v15+
}
// Good: params is a Promise — await it
async function PageGood({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <h1>{id}</h1>;
}The App Router emits <title>, <meta>, OpenGraph/Twitter tags, sitemap.xml, and robots.txt
from code (identical API on v15/v16). Build-side patterns → references/metadata.md; the strategy
side — JSON-LD, GEO, keyword research — is ../marketing/SKILL.md's
(../marketing/references/seo-geo.md): this skill emits the tags, that one picks the content.
metadata/generateMetadata are Server-Component-only — one or the other per file (static
object when known at build; async generateMetadata when it depends on params/data, wrapped in
React.cache to dedupe with the page). Set metadataBase once in the root layout so relative
OG/canonical URLs resolve to absolute.app/sitemap.ts → MetadataRoute.Sitemap (50k-URL cap; shard with generateSitemaps() past that);
app/robots.ts → MetadataRoute.Robots (link the sitemap, disallow private paths).opengraph-image.tsx returning ImageResponse from next/og (flexbox-only CSS).// app/blog/[slug]/page.tsx — dynamic metadata + OpenGraph (sitemap.ts/robots.ts/next/og in references/metadata.md)
import type { Metadata } from "next";
import { getPost } from "@/lib/dal";
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const { slug } = await params; // params is a Promise on v15+
const post = await getPost(slug); // React.cache-shared with the page
if (!post) return {};
return {
title: post.title,
description: post.excerpt,
alternates: { canonical: `/blog/${slug}` },
openGraph: {
title: post.title,
type: "article",
images: [{ url: post.cover, width: 1200, height: 630, alt: post.title }], // recommended OG size
},
twitter: { card: "summary_large_image", title: post.title },
};
}Which block applies is decided by the detection gate above. Optimistic UI, useActionState + zod
forms and the full mutation patterns are in references/data-and-caching.md.
v15 model — fetch is uncached by default; opt in explicitly.
// uncached on v15 (re-fetched every request):
const live = await fetch("https://api.example.com/now").then((r) => r.json());
// opt into the data cache + tag it:
const products = await fetch("https://api.example.com/products", {
next: { revalidate: 3600, tags: ["products"] },
}).then((r) => r.json());
// from a Server Action: invalidate the tag (or a route with revalidatePath)
import { revalidateTag } from "next/cache";
revalidateTag("products");
// request-scoped dedupe (one query per render); see also unstable_cache + route segment config
import { cache } from "react";
export const getUser = cache(async (id: string) => db.user.findUnique({ where: { id } }));v16 Cache Components — everything dynamic by default; opt in with "use cache".
// lib/products.ts — Next.js 16: cacheLife/cacheTag/updateTag are STABLE (no unstable_ prefix;
// the v15 preview used `unstable_cacheLife as cacheLife`, `unstable_cacheTag as cacheTag`).
import { cacheLife, cacheTag, updateTag } from "next/cache";
export async function getProducts() {
"use cache";
cacheLife("hours");
cacheTag("products");
return db.product.findMany();
}
// from a Server Action: updateTag = immediate read-your-writes;
// revalidateTag("products", "hours") = stale-while-revalidate. See references/data-and-caching.md.
updateTag("products");// Bad: reading request APIs inside "use cache" hangs/errors the build
export async function getCartBad() {
"use cache";
const c = await cookies(); // ✗ not allowed inside use cache
return db.cart.find(c.get("cartId")?.value);
}
// Good: read the request value OUTSIDE, pass it as an argument
export async function getCart(cartId: string) {
"use cache";
cacheTag(`cart:${cartId}`);
return db.cart.find(cartId);
}The Next-relevant deltas (full discipline, hooks, state-location tree, composition →
references/react.md): useActionState(fn, initial) → [state, action, isPending] (replaces
useFormState); useFormStatus() for a child submit button; useOptimistic auto-reverts on
action error; use(promise) unwraps an RSC-passed Promise under <Suspense>; ref is a normal
prop (no forwardRef); <Context value> is the provider; React Compiler on
(reactCompiler: true) ⇒ drop manual memoization.
"use client";
import { useActionState } from "react";
import { renameProject } from "./actions"; // the "use server" action defined above
export function RenameForm({ id }: { id: string }) {
const [state, action, isPending] = useActionState(renameProject, null);
return (
<form action={action}>
<input type="hidden" name="id" value={id} />
<input name="name" aria-label="Project name" required />
<button disabled={isPending}>{isPending ? "Saving…" : "Save"}</button>
{state?.status === "error" && <p role="alert">{state.message}</p>}
</form>
);
}strict: true + noUncheckedIndexedAccess: true.typedRoutes: true, or experimental.typedRoutes on older v15).z.infer) shared across action input, form, and DB layer.{ status: "ok"; data } | { status: "error"; message }.params/searchParams typed as Promise<...>.// Bad: untyped form data
const data: any = Object.fromEntries(formData);
// Good: validate + infer one shared type
const schema = z.object({ name: z.string().min(1), email: z.string().email() });
type Input = z.infer<typeof schema>; // reuse for form + DB layer
const r = schema.safeParse(Object.fromEntries(formData));
if (!r.success) return { status: "error", message: "Invalid" };Defense in depth with three layers — middleware is NOT one of them. Full wiring (Auth.js v5
auth.ts, the DAL, CSRF, cookies, CSP, SSRF) lives in references/security.md; apply this checklist
on every review:
proxy.ts/middleware.ts is a coarse redirect only (NOT a security boundary).auth() check inside every Server Action and Route Handler (shown in those sections above);
re-check the session in a Data Access Layer (DAL) before any read/write — the DAL is the real boundary.httpOnly, secure, sameSite: "lax"; rotate the session on any privilege change.Origin/Host; never expose a mutation as an unauthenticated GET;
set serverActions.allowedOrigins in next.config.ts.NEXT_PUBLIC_* — they ship to the browser; proxy via a Route Handler and
mark server-only modules with import 'server-only'.fetch in Route Handlers; block internal/metadata ranges.proxy.ts/headers. See also ../secure-coding/SKILL.md.next/image — always width/height or fill + a sized parent; priority on the LCP image; sizes.next/font — self-host, display: "swap", subset → zero CLS + no extra round-trip.next/dynamic for heavy client islands; optimizePackageImports; @next/bundle-analyzer.Promise.all / split sibling fetches into parallel children; PPR/streaming, reserve space to avoid CLS.content-visibility: auto + virtualize (@tanstack/react-virtual) past ~50 rows; warm assets with react-dom preload/preconnect; narrow store selectors (Zustand) cut re-renders. Full lever→metric map in references/performance.md.| Common belief | Reality / STOP |
|---|---|
| "The client already checks the user, the action is safe" | Server Actions are public POST endpoints — authenticate inside the action |
"fetch caches by default, skip revalidate" | v15: fetch is uncached by default; that's the v13/14 mental model |
"Read cookies() inside use cache for convenience" | Build hangs/errors; read outside, pass the value as an argument |
"proxy.ts looks misnamed, rename to middleware.ts" | Correct on v16; renaming breaks middleware execution |
"Just import the Server Component into this client file" | Compose via children; importing forces it client / breaks the build |
"Put the API key in NEXT_PUBLIC_API_KEY" | It ships to the browser; proxy through a Route Handler/Server Action |
"Add useMemo everywhere for perf" | Measure first; with React Compiler manual memoization is noise |
"await params is unnecessary" | v15+: params/searchParams are Promises — you must await |
| "Middleware protects my dashboard, the data fetch is safe" | Middleware is not a security boundary; check in the DAL |
| "Snapshot-test the RSC page" | Async Server Components aren't jsdom-renderable; test data fns + Playwright |
Run bash scripts/verify.sh from the Next.js project root. It runs ESLint, tsc --noEmit,
Vitest, and next build, skipping any tool not installed (a missing tool is a yellow warning, never
a failure). It reads the installed Next.js major version and only falls back to next lint on
v15 and earlier — next lint was removed in v16, so on a v16 repo a missing ESLint is a SKIP,
never a false failure. The lint/type/test steps are read-only; the final next build writes the
.next/ output directory. No installs, no network mutations. Safe to re-run.
Test strategy — Vitest 3 + RTL + MSW 2 for units, Playwright for pages, and the RSC testing reality
behind that last anti-pattern row: references/testing.md.
In a project with a 02-DOCS/ layer (the harness Karpathy wiki), this
project's app decisions live in 02-DOCS/wiki/stack/nextjs.md, indexed from 02-DOCS/wiki/index.md
(the Knowledge map; root CLAUDE.md keeps only a pointer). Read it first on every use and stay
consistent. Missing or stale → write the project's real choices there — caching model in use (v15
fetch-cache vs v16 use cache), auth approach, server-action and data-fetching conventions, runtime
(edge/node), design-system hookup — index it, and bump its Updated date in the same change as any
convention change. No 02-DOCS/ layer? Skip silently (optionally suggest harness). Unlike the
brand study, technical conventions are recorded, not gated — never block the task on this.
© ericrisco, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 9 other files (scripts, references) in skills/nextjs of ericrisco/rsc-harness.
Open the folder on GitHubat commit e3d5b33
Nextjs next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Nextjs this skillericrisco/rsc-harness | 167 | — | ~5.1k | Automated safety check: Pass | MIT | |
| Creative Tim UI Blockscreativetimofficial/ui | 12k | — | ~2.1k | Automated safety check: Notes | MIT | |
| React Best Practicesryokun6/ryos | 1.3k | — | ~2k | Automated safety check: Pass | MIT | |
| Frontend Engineer Workflownanocoai/nanoclaw | 31k | — | ~1.5k | Automated safety check: Pass | MIT | |
| Nextjs Performancegiuseppe-trisciuoglio/developer-kit | 355 | — | ~3.2k | Automated safety check: Notes | MIT | |
| Add React Analyticsgotempsh/temps | 826 | — | ~2.7k | Automated safety check: Pass | Apache-2.0 |
creativetimofficial/ui
Helps install, generate and review Creative Tim UI blocks: shadcn/ui-based React and Tailwind sections that follow a restrained, production-minded design philosophy.
ryokun6/ryos
React performance optimization guidelines from Vercel Engineering (vercel-labs/agent-skills).
nanocoai/nanoclaw
Sets a build, test and verify routine for web projects: write typed, accessible code and never call work done until it is checked in a real browser with screenshots.
giuseppe-trisciuoglio/developer-kit
Expert Next.js performance optimization skill covering Core Web Vitals, image/font optimization, caching strategies, streaming, bundle optimization, and Server Components best practices.
gotempsh/temps
Add Temps analytics to React applications with comprehensive tracking capabilities including page views, custom events, scroll tracking, engagement monitoring, session recording, and Web Vitals…
LiteyukiStudio/LiteyukiBot
Build, review, or test LiteyukiBot v7's React/Vite WebUI under webui/ and its packaged static delivery in packages/webui/.
ericrisco/rsc-harness
A skill your agent uses when designing or analyzing a controlled experiment — falsifiable hypothesis, sample size from an MDE, reading significance/CI/power, CUPED, or rescuing tests that won't go…
ericrisco/rsc-harness
A skill your agent uses when making a web UI conform to WCAG 2.2 Level AA — axe-core or Lighthouse a11y violations, keyboard operability, focus management, ARIA roles/names/live regions, contrast…
ericrisco/rsc-harness
A skill your agent uses when running or fixing paid acquisition on Google or Meta — campaign structure (Performance Max, Demand Gen, Search, Advantage+), platform-fit creative, budget/scaling rules…
ericrisco/rsc-harness
A skill your agent uses when measuring whether an LLM or agent system actually got better and gating merges on it: golden sets, fixing an inflated LLM-as-judge, scoring RAG (faithfulness, contextual…
ericrisco/rsc-harness
A skill your agent uses when a creative goal must become a finished media file: pick and order generative-media models per modality — AI voiceover, image-to-video clips, score — then glue them with…
ericrisco/rsc-harness
A skill your agent uses when instrumenting product or web analytics — GA4/PostHog SDK wiring, event taxonomy, funnels, double-counted events, consent gating, PII scrubbing.
Categories
A skill your agent uses when building, reviewing, testing, securing, or optimizing a Next.js App Router app: Server vs Client boundaries, use server actions, route handlers, the v15 vs v16 use cache…. Nextjs is an agent skill from ericrisco/rsc-harness.js App Router app: Server vs Client boundaries, use server actions, route handlers, the v15 vs v16 use cache caching model, metadata/SEO, auth, and Core Web Vitals.
Nextjs fits situations like: optimizing a Next.js App Router app: Server vs Client boundaries; use server actions; the v15 vs v16 use cache caching model; core Web Vitals.
Run `npx skills add ericrisco/rsc-harness --skill nextjs -a claude-code`. Or copy the skill folder (skills/nextjs in ericrisco/rsc-harness) into .claude/skills/nextjs in your project. Claude Code loads it when a task matches its description.
Run `npx skills add ericrisco/rsc-harness --skill nextjs -a codex`. Or copy the skill folder (skills/nextjs in ericrisco/rsc-harness) into .agents/skills/nextjs in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add ericrisco/rsc-harness --skill nextjs -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/nextjs, .gemini/skills/nextjs, .github/skills/nextjs and .opencode/skills/nextjs in your project.
Going by SKILL.md and its folder, Nextjs needs a shell for the scripts in its folder, the command-line tools its instructions call (bash and tsc) and credentials named NEXT_PUBLIC_API_KEY. Our summary lists: A Bash shell.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Nextjs is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.1k tokens (SKILL.md is roughly 21k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 16k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Nextjs: Creative Tim UI Blocks (creativetimofficial/ui, 12k stars), React Best Practices (ryokun6/ryos, 1.3k stars), Frontend Engineer Workflow (nanocoai/nanoclaw, 31k stars) and Nextjs Performance (giuseppe-trisciuoglio/developer-kit, 355 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
ericrisco (a GitHub user) maintains it in ericrisco/rsc-harness, which has 167 GitHub stars. The repository holds 227 skills in this directory. The repository was last updated on October 7, 2026.
Source: ericrisco/rsc-harness on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.