React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

best-practicessummaryreact-libraries

React Libraries Best Practices

A condensed summary of the 25 most important best practices drawn from every page in this section.

  1. Return toDataStreamResponse: An AI SDK route handler must return result.toDataStreamResponse() (not result.text or NextResponse.json); otherwise you ship a full-response JSON blob and the useChat streaming UI never updates token-by-token.
  2. Set maxSteps for Tool Calls: Tools defined without maxSteps default to a single step, so the model can call a tool but never see its result and the conversation stalls — raise it to the maximum reasoning depth you actually want.
  3. Use generateObject With Zod: For structured output, generateObject({ schema }) with a Zod schema enforces shape at the SDK layer; prompting for JSON and hand-parsing is brittle and wastes tokens on format instructions.
  4. Encode Lambda Payload as Uint8Array: InvokeCommand rejects plain objects at runtime — wrap with new TextEncoder().encode(JSON.stringify(payload)) and decode the response symmetrically, respecting the 6 MB sync / 256 KB async payload ceilings.
  5. Check FunctionError, Not Catch: Lambda runtime errors arrive as a FunctionError field on the response, not as a thrown exception, so inspect the response envelope — async (Event) invocations drop errors silently unless you configure a DLQ.
  6. Mind Polly Character Limits: The neural engine caps at 3,000 characters per request and the standard engine at 6,000, so chunk long text on sentence boundaries and verify neural support with DescribeVoicesCommand before switching voices.
  7. Wrap SSML in speak and Escape: SSML input requires TextType: "ssml" and must be valid XML inside <speak>...</speak> with &, <, > escaped; also avoid pcm output for browsers since it is raw audio — use mp3 or ogg_vorbis.
  8. Upload to S3 via Presigned PUT: Generate a presigned URL on the server and let the browser PUT directly to S3 so large uploads bypass the Next.js request body limit (1 MB default) and never touch your server bandwidth.
  9. Configure Bucket CORS for PUT: Browser-to-S3 uploads require explicit bucket CORS rules listing your origin, the PUT method, and the Content-Type header — and always set ContentType on the presigned command so the object isn't saved as application/octet-stream.
  10. Keep AWS SDKs Server-Only: Never import @aws-sdk/client-s3, client-lambda, or client-polly from a "use client" file — credentials end up in the browser bundle and AWS SDK v3 balloons client JS; use Server Actions or Route Handlers.
  11. Use parseISO, Not new Date: new Date("2026-04-06") parses date-only ISO strings as UTC midnight and shifts visibly in non-UTC zones, so prefer parseISO (plus date-fns-tz for zone-aware work) to avoid silent off-by-one-day bugs.
  12. Mind MM vs mm Format Tokens: date-fns follows Unicode TR#35, where MM is month, mm is minutes, and DD is invalid (use dd) — case mistakes compile fine and produce silently wrong strings.
  13. Format Dates on the Client: Server-rendered formatted dates cause hydration mismatches whenever the server timezone differs from the client, so format in a Client Component after mount or pass the raw timestamp and format in the browser.
  14. Wrap Drag Items in DragOverlay: DragOverlay renders the drag preview outside the DOM flow and fixes the "item jumps" problem when the original slot collapses; combine with reduced opacity on the original for a stable visual.
  15. Require Pointer Movement to Start Drag: Add activationConstraint: { distance: 8 } (or a delay) to PointerSensor so a single click does not start a drag; without it, every click on a sortable item triggers accidental drags.
  16. Globally Unique dnd-kit IDs: dnd-kit identifies items by ID alone across all containers, so duplicate IDs across columns break multi-container drops — use UUIDs or container-prefixed keys rather than per-list indexes.
  17. Import From lodash-es: Use named imports from lodash-es so the bundler tree-shakes unused utilities; import _ from "lodash" pulls in the whole ~70 KB CJS build even for a single helper.
  18. Memoize debounce Creation: Creating debounce(fn, 300) inline in render returns a fresh instance every render and never accumulates calls; wrap creation in useMemo/useCallback and call .cancel() in the useEffect cleanup so pending invocations don't fire after unmount.
  19. Pass a Fresh Target to merge: Lodash's merge(target, source) mutates its first argument, so always pass a fresh {} (or cloneDeep first) as the target; also prefer native structuredClone over cloneDeep unless you need function or class-instance support.
  20. Generate a 32+ Char AUTH_SECRET: Auth.js crashes in production without AUTH_SECRET, and weak secrets compromise every session — generate one with npx auth secret and use distinct values per environment so a leaked dev key never compromises prod.
  21. Credentials Provider Is JWT-Only: The Credentials provider cannot use database sessions even with an adapter installed, and extending session types requires both the jwt/session callback pair and a module augmentation in types/next-auth.d.ts or TypeScript drifts from runtime.
  22. Use a Singleton Prisma Client: In dev, attach the Prisma client to globalThis so hot module reload does not open a fresh connection pool on every save — without this the database quickly refuses connections and the dev server appears to hang.
  23. Convert BigInt Before Serialization: Prisma BigInt fields cannot cross the Server→Client boundary via JSON, so map them to Number or String before passing to Client Components, and select only the relations you need (with include/select) to avoid N+1 queries.
  24. QueryClient Lives in useState: Construct the TanStack Query QueryClient inside useState(() => new QueryClient()) within a client provider; a module-level client in a Server Component leaks cache state across user requests and leaks data between users.
  25. Understand staleTime vs gcTime: staleTime controls when data is considered fresh and whether refetches happen; gcTime controls how long inactive queries stay in the cache — staleTime: Infinity still allows garbage collection, and mutations never auto-invalidate related queries without explicit invalidateQueries.