Ship a React or Next.js app that survives real users: typed, tested, observable.
Read the skill at https://ai-skills-vault.pages.dev/skills/react-production-app.md and install it as a skill in your agent. Follow it exactly, including its checklists. Then ask me the questions it requires and do the work.
Build a React app that survives real users: typed, tested, observable, fast, and deployable on day one.
Write these six answers into DECISIONS.md. Changing any of them later is a rewrite.
src/
app/ routes only, thin - no business logic
features/ feature folders: components, hooks, api, types
components/ui/ design system primitives
lib/ clients, utils, formatters
server/ server-only code, db access, actions
styles/ tokens and globals
Rules: a route file imports from features/, never the reverse. Nothing in features/ imports from app/. server/ never leaks into a client bundle - enforce with import 'server-only'.
User, Order, Plan) in features/*/types.ts.zod at the edge, inferred types inside.any. Use unknown + a parser. strict: true in tsconfig, plus noUncheckedIndexedAccess.import { z } from "zod";
export const OrderSchema = z.object({
id: z.string().uuid(),
total: z.number().int().nonnegative(),
status: z.enum(["pending", "paid", "shipped", "cancelled"]),
});
export type Order = z.infer<typeof OrderSchema>;
revalidate (Next) or staleTime (Query).SELECT * unbounded.Every form gets: schema validation, per-field errors on blur, disabled submit while pending, success state, server-error mapping to fields, and a double-submit guard.
const { register, handleSubmit, formState: { errors, isSubmitting } } =
useForm<z.infer<typeof FormSchema>>({ resolver: zodResolver(FormSchema) });
| Metric | Budget |
|---|---|
| First-load JS (gzipped) | under 180 KB |
| LCP on 4G mobile | under 2.5 s |
| CLS | under 0.1 |
| INP | under 200 ms |
| Route-level code splitting | every route |
| Images | next/image or srcset + loading=lazy, AVIF/WebP |
Tactics: dynamic() for heavy widgets, no barrel-file imports of large libs, virtualise lists over 100 rows, debounce search 250 ms, memoise only measured hot paths.
Skip snapshot tests of large trees - they fail on every styling change and catch nothing.
console.log in production code.NODE_ENV=production build passes locallyTyped end to end, no any; every async path has loading, empty and error states; auth enforced server-side; Lighthouse mobile 90+ performance and 100 accessibility; critical journey covered by one passing e2e; deploy reproducible from a clean clone in under 10 minutes.
| Symptom | Cause | Fix |
|---|---|---|
| Re-render storms | Server data in global store, unstable props | Query cache + stable keys + memo on hot list rows |
| Hydration mismatch | Date.now()/window in render | Move to useEffect or server-provide the value |
| Bundle bloat | Barrel imports, whole-lib imports | Named imports, dynamic(), bundle analyser |
| Auth bypass | Client-only route guards | Server-side session check in middleware + loader |
| Slow lists | 1000 rows rendered | Virtualise, paginate, memoise row |
Read the skill at the URL above. Start by asking me the six Stage 1 decisions in one message and writeDECISIONS.md. Then scaffold the folder structure, define Zod contracts, and build feature by feature with loading/empty/error states included from the first commit. Enforce the Stage 6 performance budget and finish with the Stage 9 deploy checklist. Never useany.