From 51d1dcf917898526d42d6dcc1a88b9e9661216b9 Mon Sep 17 00:00:00 2001 From: Urjit Chakraborty <135136842+urjitc@users.noreply.github.com> Date: Wed, 14 Jan 2026 20:59:54 -0500 Subject: [PATCH] Revert "Refactor performance optimizations and security headers" --- .../vercel-react-best-practices/AGENTS.md | 2249 ----------------- .../vercel-react-best-practices/README.md | 123 - .../vercel-react-best-practices/SKILL.md | 121 - .../vercel-react-best-practices/metadata.json | 15 - .../rules/_sections.md | 46 - .../rules/_template.md | 28 - .../rules/advanced-event-handler-refs.md | 55 - .../rules/advanced-use-latest.md | 49 - .../rules/async-api-routes.md | 38 - .../rules/async-defer-await.md | 80 - .../rules/async-dependencies.md | 36 - .../rules/async-parallel.md | 28 - .../rules/async-suspense-boundaries.md | 99 - .../rules/bundle-barrel-imports.md | 59 - .../rules/bundle-conditional.md | 31 - .../rules/bundle-defer-third-party.md | 49 - .../rules/bundle-dynamic-imports.md | 35 - .../rules/bundle-preload.md | 50 - .../rules/client-event-listeners.md | 74 - .../rules/client-swr-dedup.md | 56 - .../rules/js-batch-dom-css.md | 82 - .../rules/js-cache-function-results.md | 80 - .../rules/js-cache-property-access.md | 28 - .../rules/js-cache-storage.md | 70 - .../rules/js-combine-iterations.md | 32 - .../rules/js-early-exit.md | 50 - .../rules/js-hoist-regexp.md | 45 - .../rules/js-index-maps.md | 37 - .../rules/js-length-check-first.md | 49 - .../rules/js-min-max-loop.md | 82 - .../rules/js-set-map-lookups.md | 24 - .../rules/js-tosorted-immutable.md | 57 - .../rules/rendering-activity.md | 26 - .../rules/rendering-animate-svg-wrapper.md | 47 - .../rules/rendering-conditional-render.md | 40 - .../rules/rendering-content-visibility.md | 38 - .../rules/rendering-hoist-jsx.md | 46 - .../rules/rendering-hydration-no-flicker.md | 82 - .../rules/rendering-svg-precision.md | 28 - .../rules/rerender-defer-reads.md | 39 - .../rules/rerender-dependencies.md | 45 - .../rules/rerender-derived-state.md | 29 - .../rules/rerender-functional-setstate.md | 74 - .../rules/rerender-lazy-state-init.md | 58 - .../rules/rerender-memo.md | 44 - .../rules/rerender-transitions.md | 40 - .../rules/server-after-nonblocking.md | 73 - .../rules/server-cache-lru.md | 41 - .../rules/server-cache-react.md | 26 - .../rules/server-parallel-fetching.md | 79 - .../rules/server-serialization.md | 38 - .claude/skills/web-design-guidelines/SKILL.md | 36 - .../vercel-react-best-practices/AGENTS.md | 2249 ----------------- .../vercel-react-best-practices/README.md | 123 - .../vercel-react-best-practices/SKILL.md | 121 - .../vercel-react-best-practices/metadata.json | 15 - .../rules/_sections.md | 46 - .../rules/_template.md | 28 - .../rules/advanced-event-handler-refs.md | 55 - .../rules/advanced-use-latest.md | 49 - .../rules/async-api-routes.md | 38 - .../rules/async-defer-await.md | 80 - .../rules/async-dependencies.md | 36 - .../rules/async-parallel.md | 28 - .../rules/async-suspense-boundaries.md | 99 - .../rules/bundle-barrel-imports.md | 59 - .../rules/bundle-conditional.md | 31 - .../rules/bundle-defer-third-party.md | 49 - .../rules/bundle-dynamic-imports.md | 35 - .../rules/bundle-preload.md | 50 - .../rules/client-event-listeners.md | 74 - .../rules/client-swr-dedup.md | 56 - .../rules/js-batch-dom-css.md | 82 - .../rules/js-cache-function-results.md | 80 - .../rules/js-cache-property-access.md | 28 - .../rules/js-cache-storage.md | 70 - .../rules/js-combine-iterations.md | 32 - .../rules/js-early-exit.md | 50 - .../rules/js-hoist-regexp.md | 45 - .../rules/js-index-maps.md | 37 - .../rules/js-length-check-first.md | 49 - .../rules/js-min-max-loop.md | 82 - .../rules/js-set-map-lookups.md | 24 - .../rules/js-tosorted-immutable.md | 57 - .../rules/rendering-activity.md | 26 - .../rules/rendering-animate-svg-wrapper.md | 47 - .../rules/rendering-conditional-render.md | 40 - .../rules/rendering-content-visibility.md | 38 - .../rules/rendering-hoist-jsx.md | 46 - .../rules/rendering-hydration-no-flicker.md | 82 - .../rules/rendering-svg-precision.md | 28 - .../rules/rerender-defer-reads.md | 39 - .../rules/rerender-dependencies.md | 45 - .../rules/rerender-derived-state.md | 29 - .../rules/rerender-functional-setstate.md | 74 - .../rules/rerender-lazy-state-init.md | 58 - .../rules/rerender-memo.md | 44 - .../rules/rerender-transitions.md | 40 - .../rules/server-after-nonblocking.md | 73 - .../rules/server-cache-lru.md | 41 - .../rules/server-cache-react.md | 26 - .../rules/server-parallel-fetching.md | 79 - .../rules/server-serialization.md | 38 - .github/skills/web-design-guidelines/SKILL.md | 36 - next.config.ts | 5 - src/app/api/workspaces/[id]/events/route.ts | 21 +- src/app/api/workspaces/[id]/route.ts | 38 +- src/app/api/workspaces/slug/[slug]/route.ts | 11 +- src/components/landing/FourWays.tsx | 2 + src/components/landing/ThreeSteps.tsx | 1 + vercel.json | 96 - 111 files changed, 24 insertions(+), 9922 deletions(-) delete mode 100644 .claude/skills/vercel-react-best-practices/AGENTS.md delete mode 100644 .claude/skills/vercel-react-best-practices/README.md delete mode 100644 .claude/skills/vercel-react-best-practices/SKILL.md delete mode 100644 .claude/skills/vercel-react-best-practices/metadata.json delete mode 100644 .claude/skills/vercel-react-best-practices/rules/_sections.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/_template.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/async-api-routes.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/async-defer-await.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/async-dependencies.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/async-parallel.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/bundle-conditional.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/bundle-preload.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/client-event-listeners.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-cache-storage.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-early-exit.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-index-maps.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-length-check-first.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rendering-activity.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rerender-memo.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/rerender-transitions.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/server-cache-lru.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/server-cache-react.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md delete mode 100644 .claude/skills/vercel-react-best-practices/rules/server-serialization.md delete mode 100644 .claude/skills/web-design-guidelines/SKILL.md delete mode 100644 .github/skills/vercel-react-best-practices/AGENTS.md delete mode 100644 .github/skills/vercel-react-best-practices/README.md delete mode 100644 .github/skills/vercel-react-best-practices/SKILL.md delete mode 100644 .github/skills/vercel-react-best-practices/metadata.json delete mode 100644 .github/skills/vercel-react-best-practices/rules/_sections.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/_template.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/advanced-use-latest.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/async-api-routes.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/async-defer-await.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/async-dependencies.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/async-parallel.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/bundle-conditional.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/bundle-preload.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/client-event-listeners.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/client-swr-dedup.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-batch-dom-css.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-cache-function-results.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-cache-property-access.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-cache-storage.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-combine-iterations.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-early-exit.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-hoist-regexp.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-index-maps.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-length-check-first.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-min-max-loop.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-set-map-lookups.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rendering-activity.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rendering-conditional-render.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rendering-content-visibility.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rendering-svg-precision.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rerender-defer-reads.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rerender-dependencies.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rerender-derived-state.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rerender-memo.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/rerender-transitions.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/server-after-nonblocking.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/server-cache-lru.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/server-cache-react.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/server-parallel-fetching.md delete mode 100644 .github/skills/vercel-react-best-practices/rules/server-serialization.md delete mode 100644 .github/skills/web-design-guidelines/SKILL.md delete mode 100644 vercel.json diff --git a/.claude/skills/vercel-react-best-practices/AGENTS.md b/.claude/skills/vercel-react-best-practices/AGENTS.md deleted file mode 100644 index 898afb39..00000000 --- a/.claude/skills/vercel-react-best-practices/AGENTS.md +++ /dev/null @@ -1,2249 +0,0 @@ -# React Best Practices - -**Version 0.1.0** -Vercel Engineering -January 2026 - -> **Note:** -> This document is mainly for agents and LLMs to follow when maintaining, -> generating, or refactoring React and Next.js codebases at Vercel. Humans -> may also find it useful, but guidance here is optimized for automation -> and consistency by AI-assisted workflows. - ---- - -## Abstract - -Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation. - ---- - -## Table of Contents - -1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL** - - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed) - - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization) - - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes) - - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations) - - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries) -2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL** - - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports) - - 2.2 [Conditional Module Loading](#22-conditional-module-loading) - - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries) - - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components) - - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent) -3. [Server-Side Performance](#3-server-side-performance) — **HIGH** - - 3.1 [Cross-Request LRU Caching](#31-cross-request-lru-caching) - - 3.2 [Minimize Serialization at RSC Boundaries](#32-minimize-serialization-at-rsc-boundaries) - - 3.3 [Parallel Data Fetching with Component Composition](#33-parallel-data-fetching-with-component-composition) - - 3.4 [Per-Request Deduplication with React.cache()](#34-per-request-deduplication-with-reactcache) - - 3.5 [Use after() for Non-Blocking Operations](#35-use-after-for-non-blocking-operations) -4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH** - - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners) - - 4.2 [Use SWR for Automatic Deduplication](#42-use-swr-for-automatic-deduplication) -5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM** - - 5.1 [Defer State Reads to Usage Point](#51-defer-state-reads-to-usage-point) - - 5.2 [Extract to Memoized Components](#52-extract-to-memoized-components) - - 5.3 [Narrow Effect Dependencies](#53-narrow-effect-dependencies) - - 5.4 [Subscribe to Derived State](#54-subscribe-to-derived-state) - - 5.5 [Use Functional setState Updates](#55-use-functional-setstate-updates) - - 5.6 [Use Lazy State Initialization](#56-use-lazy-state-initialization) - - 5.7 [Use Transitions for Non-Urgent Updates](#57-use-transitions-for-non-urgent-updates) -6. [Rendering Performance](#6-rendering-performance) — **MEDIUM** - - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element) - - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists) - - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements) - - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision) - - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering) - - 6.6 [Use Activity Component for Show/Hide](#66-use-activity-component-for-showhide) - - 6.7 [Use Explicit Conditional Rendering](#67-use-explicit-conditional-rendering) -7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM** - - 7.1 [Batch DOM CSS Changes](#71-batch-dom-css-changes) - - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups) - - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops) - - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls) - - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls) - - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations) - - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons) - - 7.8 [Early Return from Functions](#78-early-return-from-functions) - - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation) - - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort) - - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups) - - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability) -8. [Advanced Patterns](#8-advanced-patterns) — **LOW** - - 8.1 [Store Event Handlers in Refs](#81-store-event-handlers-in-refs) - - 8.2 [useLatest for Stable Callback Refs](#82-uselatest-for-stable-callback-refs) - ---- - -## 1. Eliminating Waterfalls - -**Impact: CRITICAL** - -Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains. - -### 1.1 Defer Await Until Needed - -**Impact: HIGH (avoids blocking unused code paths)** - -Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them. - -**Incorrect: blocks both branches** - -```typescript -async function handleRequest(userId: string, skipProcessing: boolean) { - const userData = await fetchUserData(userId) - - if (skipProcessing) { - // Returns immediately but still waited for userData - return { skipped: true } - } - - // Only this branch uses userData - return processUserData(userData) -} -``` - -**Correct: only blocks when needed** - -```typescript -async function handleRequest(userId: string, skipProcessing: boolean) { - if (skipProcessing) { - // Returns immediately without waiting - return { skipped: true } - } - - // Fetch only when needed - const userData = await fetchUserData(userId) - return processUserData(userData) -} -``` - -**Another example: early return optimization** - -```typescript -// Incorrect: always fetches permissions -async function updateResource(resourceId: string, userId: string) { - const permissions = await fetchPermissions(userId) - const resource = await getResource(resourceId) - - if (!resource) { - return { error: 'Not found' } - } - - if (!permissions.canEdit) { - return { error: 'Forbidden' } - } - - return await updateResourceData(resource, permissions) -} - -// Correct: fetches only when needed -async function updateResource(resourceId: string, userId: string) { - const resource = await getResource(resourceId) - - if (!resource) { - return { error: 'Not found' } - } - - const permissions = await fetchPermissions(userId) - - if (!permissions.canEdit) { - return { error: 'Forbidden' } - } - - return await updateResourceData(resource, permissions) -} -``` - -This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive. - -### 1.2 Dependency-Based Parallelization - -**Impact: CRITICAL (2-10× improvement)** - -For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment. - -**Incorrect: profile waits for config unnecessarily** - -```typescript -const [user, config] = await Promise.all([ - fetchUser(), - fetchConfig() -]) -const profile = await fetchProfile(user.id) -``` - -**Correct: config and profile run in parallel** - -```typescript -import { all } from 'better-all' - -const { user, config, profile } = await all({ - async user() { return fetchUser() }, - async config() { return fetchConfig() }, - async profile() { - return fetchProfile((await this.$.user).id) - } -}) -``` - -Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all) - -### 1.3 Prevent Waterfall Chains in API Routes - -**Impact: CRITICAL (2-10× improvement)** - -In API routes and Server Actions, start independent operations immediately, even if you don't await them yet. - -**Incorrect: config waits for auth, data waits for both** - -```typescript -export async function GET(request: Request) { - const session = await auth() - const config = await fetchConfig() - const data = await fetchData(session.user.id) - return Response.json({ data, config }) -} -``` - -**Correct: auth and config start immediately** - -```typescript -export async function GET(request: Request) { - const sessionPromise = auth() - const configPromise = fetchConfig() - const session = await sessionPromise - const [config, data] = await Promise.all([ - configPromise, - fetchData(session.user.id) - ]) - return Response.json({ data, config }) -} -``` - -For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization). - -### 1.4 Promise.all() for Independent Operations - -**Impact: CRITICAL (2-10× improvement)** - -When async operations have no interdependencies, execute them concurrently using `Promise.all()`. - -**Incorrect: sequential execution, 3 round trips** - -```typescript -const user = await fetchUser() -const posts = await fetchPosts() -const comments = await fetchComments() -``` - -**Correct: parallel execution, 1 round trip** - -```typescript -const [user, posts, comments] = await Promise.all([ - fetchUser(), - fetchPosts(), - fetchComments() -]) -``` - -### 1.5 Strategic Suspense Boundaries - -**Impact: HIGH (faster initial paint)** - -Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads. - -**Incorrect: wrapper blocked by data fetching** - -```tsx -async function Page() { - const data = await fetchData() // Blocks entire page - - return ( -
-
Sidebar
-
Header
-
- -
-
Footer
-
- ) -} -``` - -The entire layout waits for data even though only the middle section needs it. - -**Correct: wrapper shows immediately, data streams in** - -```tsx -function Page() { - return ( -
-
Sidebar
-
Header
-
- }> - - -
-
Footer
-
- ) -} - -async function DataDisplay() { - const data = await fetchData() // Only blocks this component - return
{data.content}
-} -``` - -Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data. - -**Alternative: share promise across components** - -```tsx -function Page() { - // Start fetch immediately, but don't await - const dataPromise = fetchData() - - return ( -
-
Sidebar
-
Header
- }> - - - -
Footer
-
- ) -} - -function DataDisplay({ dataPromise }: { dataPromise: Promise }) { - const data = use(dataPromise) // Unwraps the promise - return
{data.content}
-} - -function DataSummary({ dataPromise }: { dataPromise: Promise }) { - const data = use(dataPromise) // Reuses the same promise - return
{data.summary}
-} -``` - -Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together. - -**When NOT to use this pattern:** - -- Critical data needed for layout decisions (affects positioning) - -- SEO-critical content above the fold - -- Small, fast queries where suspense overhead isn't worth it - -- When you want to avoid layout shift (loading → content jump) - -**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities. - ---- - -## 2. Bundle Size Optimization - -**Impact: CRITICAL** - -Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint. - -### 2.1 Avoid Barrel File Imports - -**Impact: CRITICAL (200-800ms import cost, slow builds)** - -Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`). - -Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts. - -**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph. - -**Incorrect: imports entire library** - -```tsx -import { Check, X, Menu } from 'lucide-react' -// Loads 1,583 modules, takes ~2.8s extra in dev -// Runtime cost: 200-800ms on every cold start - -import { Button, TextField } from '@mui/material' -// Loads 2,225 modules, takes ~4.2s extra in dev -``` - -**Correct: imports only what you need** - -```tsx -import Check from 'lucide-react/dist/esm/icons/check' -import X from 'lucide-react/dist/esm/icons/x' -import Menu from 'lucide-react/dist/esm/icons/menu' -// Loads only 3 modules (~2KB vs ~1MB) - -import Button from '@mui/material/Button' -import TextField from '@mui/material/TextField' -// Loads only what you use -``` - -**Alternative: Next.js 13.5+** - -```js -// next.config.js - use optimizePackageImports -module.exports = { - experimental: { - optimizePackageImports: ['lucide-react', '@mui/material'] - } -} - -// Then you can keep the ergonomic barrel imports: -import { Check, X, Menu } from 'lucide-react' -// Automatically transformed to direct imports at build time -``` - -Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR. - -Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`. - -Reference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js) - -### 2.2 Conditional Module Loading - -**Impact: HIGH (loads large data only when needed)** - -Load large data or modules only when a feature is activated. - -**Example: lazy-load animation frames** - -```tsx -function AnimationPlayer({ enabled }: { enabled: boolean }) { - const [frames, setFrames] = useState(null) - - useEffect(() => { - if (enabled && !frames && typeof window !== 'undefined') { - import('./animation-frames.js') - .then(mod => setFrames(mod.frames)) - .catch(() => setEnabled(false)) - } - }, [enabled, frames]) - - if (!frames) return - return -} -``` - -The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed. - -### 2.3 Defer Non-Critical Third-Party Libraries - -**Impact: MEDIUM (loads after hydration)** - -Analytics, logging, and error tracking don't block user interaction. Load them after hydration. - -**Incorrect: blocks initial bundle** - -```tsx -import { Analytics } from '@vercel/analytics/react' - -export default function RootLayout({ children }) { - return ( - - - {children} - - - - ) -} -``` - -**Correct: loads after hydration** - -```tsx -import dynamic from 'next/dynamic' - -const Analytics = dynamic( - () => import('@vercel/analytics/react').then(m => m.Analytics), - { ssr: false } -) - -export default function RootLayout({ children }) { - return ( - - - {children} - - - - ) -} -``` - -### 2.4 Dynamic Imports for Heavy Components - -**Impact: CRITICAL (directly affects TTI and LCP)** - -Use `next/dynamic` to lazy-load large components not needed on initial render. - -**Incorrect: Monaco bundles with main chunk ~300KB** - -```tsx -import { MonacoEditor } from './monaco-editor' - -function CodePanel({ code }: { code: string }) { - return -} -``` - -**Correct: Monaco loads on demand** - -```tsx -import dynamic from 'next/dynamic' - -const MonacoEditor = dynamic( - () => import('./monaco-editor').then(m => m.MonacoEditor), - { ssr: false } -) - -function CodePanel({ code }: { code: string }) { - return -} -``` - -### 2.5 Preload Based on User Intent - -**Impact: MEDIUM (reduces perceived latency)** - -Preload heavy bundles before they're needed to reduce perceived latency. - -**Example: preload on hover/focus** - -```tsx -function EditorButton({ onClick }: { onClick: () => void }) { - const preload = () => { - if (typeof window !== 'undefined') { - void import('./monaco-editor') - } - } - - return ( - - ) -} -``` - -**Example: preload when feature flag is enabled** - -```tsx -function FlagsProvider({ children, flags }: Props) { - useEffect(() => { - if (flags.editorEnabled && typeof window !== 'undefined') { - void import('./monaco-editor').then(mod => mod.init()) - } - }, [flags.editorEnabled]) - - return - {children} - -} -``` - -The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed. - ---- - -## 3. Server-Side Performance - -**Impact: HIGH** - -Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times. - -### 3.1 Cross-Request LRU Caching - -**Impact: HIGH (caches across requests)** - -`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache. - -**Implementation:** - -```typescript -import { LRUCache } from 'lru-cache' - -const cache = new LRUCache({ - max: 1000, - ttl: 5 * 60 * 1000 // 5 minutes -}) - -export async function getUser(id: string) { - const cached = cache.get(id) - if (cached) return cached - - const user = await db.user.findUnique({ where: { id } }) - cache.set(id, user) - return user -} - -// Request 1: DB query, result cached -// Request 2: cache hit, no DB query -``` - -Use when sequential user actions hit multiple endpoints needing the same data within seconds. - -**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis. - -**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching. - -Reference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache) - -### 3.2 Minimize Serialization at RSC Boundaries - -**Impact: HIGH (reduces data transfer size)** - -The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses. - -**Incorrect: serializes all 50 fields** - -```tsx -async function Page() { - const user = await fetchUser() // 50 fields - return -} - -'use client' -function Profile({ user }: { user: User }) { - return
{user.name}
// uses 1 field -} -``` - -**Correct: serializes only 1 field** - -```tsx -async function Page() { - const user = await fetchUser() - return -} - -'use client' -function Profile({ name }: { name: string }) { - return
{name}
-} -``` - -### 3.3 Parallel Data Fetching with Component Composition - -**Impact: CRITICAL (eliminates server-side waterfalls)** - -React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching. - -**Incorrect: Sidebar waits for Page's fetch to complete** - -```tsx -export default async function Page() { - const header = await fetchHeader() - return ( -
-
{header}
- -
- ) -} - -async function Sidebar() { - const items = await fetchSidebarItems() - return -} -``` - -**Correct: both fetch simultaneously** - -```tsx -async function Header() { - const data = await fetchHeader() - return
{data}
-} - -async function Sidebar() { - const items = await fetchSidebarItems() - return -} - -export default function Page() { - return ( -
-
- -
- ) -} -``` - -**Alternative with children prop:** - -```tsx -async function Layout({ children }: { children: ReactNode }) { - const header = await fetchHeader() - return ( -
-
{header}
- {children} -
- ) -} - -async function Sidebar() { - const items = await fetchSidebarItems() - return -} - -export default function Page() { - return ( - - - - ) -} -``` - -### 3.4 Per-Request Deduplication with React.cache() - -**Impact: MEDIUM (deduplicates within request)** - -Use `React.cache()` for server-side request deduplication. Authentication and database queries benefit most. - -**Usage:** - -```typescript -import { cache } from 'react' - -export const getCurrentUser = cache(async () => { - const session = await auth() - if (!session?.user?.id) return null - return await db.user.findUnique({ - where: { id: session.user.id } - }) -}) -``` - -Within a single request, multiple calls to `getCurrentUser()` execute the query only once. - -### 3.5 Use after() for Non-Blocking Operations - -**Impact: MEDIUM (faster response times)** - -Use Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response. - -**Incorrect: blocks response** - -```tsx -import { logUserAction } from '@/app/utils' - -export async function POST(request: Request) { - // Perform mutation - await updateDatabase(request) - - // Logging blocks the response - const userAgent = request.headers.get('user-agent') || 'unknown' - await logUserAction({ userAgent }) - - return new Response(JSON.stringify({ status: 'success' }), { - status: 200, - headers: { 'Content-Type': 'application/json' } - }) -} -``` - -**Correct: non-blocking** - -```tsx -import { after } from 'next/server' -import { headers, cookies } from 'next/headers' -import { logUserAction } from '@/app/utils' - -export async function POST(request: Request) { - // Perform mutation - await updateDatabase(request) - - // Log after response is sent - after(async () => { - const userAgent = (await headers()).get('user-agent') || 'unknown' - const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous' - - logUserAction({ sessionCookie, userAgent }) - }) - - return new Response(JSON.stringify({ status: 'success' }), { - status: 200, - headers: { 'Content-Type': 'application/json' } - }) -} -``` - -The response is sent immediately while logging happens in the background. - -**Common use cases:** - -- Analytics tracking - -- Audit logging - -- Sending notifications - -- Cache invalidation - -- Cleanup tasks - -**Important notes:** - -- `after()` runs even if the response fails or redirects - -- Works in Server Actions, Route Handlers, and Server Components - -Reference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after) - ---- - -## 4. Client-Side Data Fetching - -**Impact: MEDIUM-HIGH** - -Automatic deduplication and efficient data fetching patterns reduce redundant network requests. - -### 4.1 Deduplicate Global Event Listeners - -**Impact: LOW (single listener for N components)** - -Use `useSWRSubscription()` to share global event listeners across component instances. - -**Incorrect: N instances = N listeners** - -```tsx -function useKeyboardShortcut(key: string, callback: () => void) { - useEffect(() => { - const handler = (e: KeyboardEvent) => { - if (e.metaKey && e.key === key) { - callback() - } - } - window.addEventListener('keydown', handler) - return () => window.removeEventListener('keydown', handler) - }, [key, callback]) -} -``` - -When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener. - -**Correct: N instances = 1 listener** - -```tsx -import useSWRSubscription from 'swr/subscription' - -// Module-level Map to track callbacks per key -const keyCallbacks = new Map void>>() - -function useKeyboardShortcut(key: string, callback: () => void) { - // Register this callback in the Map - useEffect(() => { - if (!keyCallbacks.has(key)) { - keyCallbacks.set(key, new Set()) - } - keyCallbacks.get(key)!.add(callback) - - return () => { - const set = keyCallbacks.get(key) - if (set) { - set.delete(callback) - if (set.size === 0) { - keyCallbacks.delete(key) - } - } - } - }, [key, callback]) - - useSWRSubscription('global-keydown', () => { - const handler = (e: KeyboardEvent) => { - if (e.metaKey && keyCallbacks.has(e.key)) { - keyCallbacks.get(e.key)!.forEach(cb => cb()) - } - } - window.addEventListener('keydown', handler) - return () => window.removeEventListener('keydown', handler) - }) -} - -function Profile() { - // Multiple shortcuts will share the same listener - useKeyboardShortcut('p', () => { /* ... */ }) - useKeyboardShortcut('k', () => { /* ... */ }) - // ... -} -``` - -### 4.2 Use SWR for Automatic Deduplication - -**Impact: MEDIUM-HIGH (automatic deduplication)** - -SWR enables request deduplication, caching, and revalidation across component instances. - -**Incorrect: no deduplication, each instance fetches** - -```tsx -function UserList() { - const [users, setUsers] = useState([]) - useEffect(() => { - fetch('/api/users') - .then(r => r.json()) - .then(setUsers) - }, []) -} -``` - -**Correct: multiple instances share one request** - -```tsx -import useSWR from 'swr' - -function UserList() { - const { data: users } = useSWR('/api/users', fetcher) -} -``` - -**For immutable data:** - -```tsx -import { useImmutableSWR } from '@/lib/swr' - -function StaticContent() { - const { data } = useImmutableSWR('/api/config', fetcher) -} -``` - -**For mutations:** - -```tsx -import { useSWRMutation } from 'swr/mutation' - -function UpdateButton() { - const { trigger } = useSWRMutation('/api/user', updateUser) - return -} -``` - -Reference: [https://swr.vercel.app](https://swr.vercel.app) - ---- - -## 5. Re-render Optimization - -**Impact: MEDIUM** - -Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness. - -### 5.1 Defer State Reads to Usage Point - -**Impact: MEDIUM (avoids unnecessary subscriptions)** - -Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks. - -**Incorrect: subscribes to all searchParams changes** - -```tsx -function ShareButton({ chatId }: { chatId: string }) { - const searchParams = useSearchParams() - - const handleShare = () => { - const ref = searchParams.get('ref') - shareChat(chatId, { ref }) - } - - return -} -``` - -**Correct: reads on demand, no subscription** - -```tsx -function ShareButton({ chatId }: { chatId: string }) { - const handleShare = () => { - const params = new URLSearchParams(window.location.search) - const ref = params.get('ref') - shareChat(chatId, { ref }) - } - - return -} -``` - -### 5.2 Extract to Memoized Components - -**Impact: MEDIUM (enables early returns)** - -Extract expensive work into memoized components to enable early returns before computation. - -**Incorrect: computes avatar even when loading** - -```tsx -function Profile({ user, loading }: Props) { - const avatar = useMemo(() => { - const id = computeAvatarId(user) - return - }, [user]) - - if (loading) return - return
{avatar}
-} -``` - -**Correct: skips computation when loading** - -```tsx -const UserAvatar = memo(function UserAvatar({ user }: { user: User }) { - const id = useMemo(() => computeAvatarId(user), [user]) - return -}) - -function Profile({ user, loading }: Props) { - if (loading) return - return ( -
- -
- ) -} -``` - -**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders. - -### 5.3 Narrow Effect Dependencies - -**Impact: LOW (minimizes effect re-runs)** - -Specify primitive dependencies instead of objects to minimize effect re-runs. - -**Incorrect: re-runs on any user field change** - -```tsx -useEffect(() => { - console.log(user.id) -}, [user]) -``` - -**Correct: re-runs only when id changes** - -```tsx -useEffect(() => { - console.log(user.id) -}, [user.id]) -``` - -**For derived state, compute outside effect:** - -```tsx -// Incorrect: runs on width=767, 766, 765... -useEffect(() => { - if (width < 768) { - enableMobileMode() - } -}, [width]) - -// Correct: runs only on boolean transition -const isMobile = width < 768 -useEffect(() => { - if (isMobile) { - enableMobileMode() - } -}, [isMobile]) -``` - -### 5.4 Subscribe to Derived State - -**Impact: MEDIUM (reduces re-render frequency)** - -Subscribe to derived boolean state instead of continuous values to reduce re-render frequency. - -**Incorrect: re-renders on every pixel change** - -```tsx -function Sidebar() { - const width = useWindowWidth() // updates continuously - const isMobile = width < 768 - return