diff --git a/README.md b/README.md
index 6ebf9115..793e92f7 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ Audits a Vercel project for cost, performance, reliability, caching, function us
### react-best-practices
-React and Next.js performance optimization guidelines from Vercel Engineering. Contains 40+ rules across 8 categories, prioritized by impact.
+React and Next.js performance optimization guidelines from Vercel Engineering. Contains 71 rules across 8 categories, prioritized by impact.
**Use when:**
diff --git a/packages/react-best-practices-build/src/extract-tests.ts b/packages/react-best-practices-build/src/extract-tests.ts
index d1d4bd2f..94582386 100644
--- a/packages/react-best-practices-build/src/extract-tests.ts
+++ b/packages/react-best-practices-build/src/extract-tests.ts
@@ -63,7 +63,7 @@ async function extractTests() {
}
// Write test cases as JSON
- await writeFile(TEST_CASES_FILE, JSON.stringify(allTestCases, null, 2), 'utf-8')
+ await writeFile(TEST_CASES_FILE, JSON.stringify(allTestCases, null, 2) + '\n', 'utf-8')
console.log(`✓ Extracted ${allTestCases.length} test cases to ${TEST_CASES_FILE}`)
console.log(` - Bad examples: ${allTestCases.filter(tc => tc.type === 'bad').length}`)
diff --git a/packages/react-best-practices-build/test-cases.json b/packages/react-best-practices-build/test-cases.json
index 02ce981e..c4529e17 100644
--- a/packages/react-best-practices-build/test-cases.json
+++ b/packages/react-best-practices-build/test-cases.json
@@ -783,6 +783,22 @@
"language": "tsx",
"description": "runs only once"
},
+ {
+ "ruleId": "",
+ "ruleTitle": "Lower Context Providers Below Lazy Boundaries",
+ "type": "bad",
+ "code": "function UserSettingsButton({ userId }: { userId: string }) {\n const [open, setOpen] = useState(false)\n\n return (\n \n \n \n \n \n \n \n \n )\n}\n\nfunction SettingsDataProvider({\n userId,\n children,\n}: {\n userId: string\n children: React.ReactNode\n}) {\n const value = useMemo(() => buildSettingsModel(userId), [userId])\n\n return (\n \n {children}\n \n )\n}",
+ "language": "tsx",
+ "description": "provider runs while the modal is closed"
+ },
+ {
+ "ruleId": "",
+ "ruleTitle": "Lower Context Providers Below Lazy Boundaries",
+ "type": "good",
+ "code": "function UserSettingsButton({ userId }: { userId: string }) {\n const [open, setOpen] = useState(false)\n\n return (\n <>\n \n \n \n \n \n \n \n \n >\n )\n}\n\nfunction SettingsDataProvider({\n userId,\n children,\n}: {\n userId: string\n children: React.ReactNode\n}) {\n const value = useMemo(() => buildSettingsModel(userId), [userId])\n\n return (\n \n {children}\n \n )\n}",
+ "language": "tsx",
+ "description": "provider only mounts with modal content"
+ },
{
"ruleId": "",
"ruleTitle": "Extract Default Non-primitive Parameter Value from Memoized Component to Constant",
diff --git a/skills/react-best-practices.zip b/skills/react-best-practices.zip
index f6dfb7a3..4e76a2d7 100644
Binary files a/skills/react-best-practices.zip and b/skills/react-best-practices.zip differ
diff --git a/skills/react-best-practices/AGENTS.md b/skills/react-best-practices/AGENTS.md
index 4e340a50..0335bb5a 100644
--- a/skills/react-best-practices/AGENTS.md
+++ b/skills/react-best-practices/AGENTS.md
@@ -14,7 +14,7 @@ January 2026
## 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.
+Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 71 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.
---
@@ -57,15 +57,16 @@ Comprehensive performance optimization guide for React and Next.js applications,
- 5.4 [Don't Define Components Inside Components](#54-dont-define-components-inside-components)
- 5.5 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#55-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)
- 5.6 [Extract to Memoized Components](#56-extract-to-memoized-components)
- - 5.7 [Narrow Effect Dependencies](#57-narrow-effect-dependencies)
- - 5.8 [Put Interaction Logic in Event Handlers](#58-put-interaction-logic-in-event-handlers)
- - 5.9 [Split Combined Hook Computations](#59-split-combined-hook-computations)
- - 5.10 [Subscribe to Derived State](#510-subscribe-to-derived-state)
- - 5.11 [Use Functional setState Updates](#511-use-functional-setstate-updates)
- - 5.12 [Use Lazy State Initialization](#512-use-lazy-state-initialization)
- - 5.13 [Use Transitions for Non-Urgent Updates](#513-use-transitions-for-non-urgent-updates)
- - 5.14 [Use useDeferredValue for Expensive Derived Renders](#514-use-usedeferredvalue-for-expensive-derived-renders)
- - 5.15 [Use useRef for Transient Values](#515-use-useref-for-transient-values)
+ - 5.7 [Lower Context Providers Below Lazy Boundaries](#57-lower-context-providers-below-lazy-boundaries)
+ - 5.8 [Narrow Effect Dependencies](#58-narrow-effect-dependencies)
+ - 5.9 [Put Interaction Logic in Event Handlers](#59-put-interaction-logic-in-event-handlers)
+ - 5.10 [Split Combined Hook Computations](#510-split-combined-hook-computations)
+ - 5.11 [Subscribe to Derived State](#511-subscribe-to-derived-state)
+ - 5.12 [Use Functional setState Updates](#512-use-functional-setstate-updates)
+ - 5.13 [Use Lazy State Initialization](#513-use-lazy-state-initialization)
+ - 5.14 [Use Transitions for Non-Urgent Updates](#514-use-transitions-for-non-urgent-updates)
+ - 5.15 [Use useDeferredValue for Expensive Derived Renders](#515-use-usedeferredvalue-for-expensive-derived-renders)
+ - 5.16 [Use useRef for Transient Values](#516-use-useref-for-transient-values)
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)
@@ -1865,7 +1866,87 @@ function Profile({ user, loading }: Props) {
**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.7 Narrow Effect Dependencies
+### 5.7 Lower Context Providers Below Lazy Boundaries
+
+**Impact: MEDIUM (avoids provider setup and context updates for inactive UI)**
+
+Place context providers as close as possible to the subtree that consumes them, especially behind UI boundaries that only render when active, such as modals, drawers, popovers, tabs, accordions, and route segments. A provider above the boundary runs its initialization, subscriptions, memoization, and context updates even when the expensive UI is closed or not selected.
+
+**Incorrect: provider runs while the modal is closed**
+
+```tsx
+function UserSettingsButton({ userId }: { userId: string }) {
+ const [open, setOpen] = useState(false)
+
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+
+function SettingsDataProvider({
+ userId,
+ children,
+}: {
+ userId: string
+ children: React.ReactNode
+}) {
+ const value = useMemo(() => buildSettingsModel(userId), [userId])
+
+ return (
+
+ {children}
+
+ )
+}
+```
+
+**Correct: provider only mounts with modal content**
+
+```tsx
+function UserSettingsButton({ userId }: { userId: string }) {
+ const [open, setOpen] = useState(false)
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+function SettingsDataProvider({
+ userId,
+ children,
+}: {
+ userId: string
+ children: React.ReactNode
+}) {
+ const value = useMemo(() => buildSettingsModel(userId), [userId])
+
+ return (
+
+ {children}
+
+ )
+}
+```
+
+Keep providers higher only when their state is genuinely shared by always-visible UI, needed to decide whether the boundary should open, or intentionally preloaded before the user activates the subtree.
+
+### 5.8 Narrow Effect Dependencies
**Impact: LOW (minimizes effect re-runs)**
@@ -1906,7 +1987,7 @@ useEffect(() => {
}, [isMobile])
```
-### 5.8 Put Interaction Logic in Event Handlers
+### 5.9 Put Interaction Logic in Event Handlers
**Impact: MEDIUM (avoids effect re-runs and duplicate side effects)**
@@ -1947,7 +2028,7 @@ function Form() {
Reference: [https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)
-### 5.9 Split Combined Hook Computations
+### 5.10 Split Combined Hook Computations
**Impact: MEDIUM (avoids recomputing independent steps)**
@@ -2007,7 +2088,7 @@ useEffect(() => {
**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, it automatically optimizes dependency tracking and may handle some of these cases for you.
-### 5.10 Subscribe to Derived State
+### 5.11 Subscribe to Derived State
**Impact: MEDIUM (reduces re-render frequency)**
@@ -2032,7 +2113,7 @@ function Sidebar() {
}
```
-### 5.11 Use Functional setState Updates
+### 5.12 Use Functional setState Updates
**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)**
@@ -2110,7 +2191,7 @@ function TodoList() {
**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.
-### 5.12 Use Lazy State Initialization
+### 5.13 Use Lazy State Initialization
**Impact: MEDIUM (wasted computation on every render)**
@@ -2164,7 +2245,7 @@ Use lazy initialization when computing initial values from localStorage/sessionS
For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.
-### 5.13 Use Transitions for Non-Urgent Updates
+### 5.14 Use Transitions for Non-Urgent Updates
**Impact: MEDIUM (maintains UI responsiveness)**
@@ -2200,7 +2281,7 @@ function ScrollTracker() {
}
```
-### 5.14 Use useDeferredValue for Expensive Derived Renders
+### 5.15 Use useDeferredValue for Expensive Derived Renders
**Impact: MEDIUM (keeps input responsive during heavy computation)**
@@ -2257,7 +2338,7 @@ function Search({ items }: { items: Item[] }) {
Reference: [https://react.dev/reference/react/useDeferredValue](https://react.dev/reference/react/useDeferredValue)
-### 5.15 Use useRef for Transient Values
+### 5.16 Use useRef for Transient Values
**Impact: MEDIUM (avoids unnecessary re-renders on frequent updates)**
diff --git a/skills/react-best-practices/SKILL.md b/skills/react-best-practices/SKILL.md
index 237988de..452e218f 100644
--- a/skills/react-best-practices/SKILL.md
+++ b/skills/react-best-practices/SKILL.md
@@ -9,7 +9,7 @@ metadata:
# Vercel React Best Practices
-Comprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 70 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.
+Comprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 71 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.
## When to Apply
@@ -83,6 +83,7 @@ Reference these guidelines when:
- `rerender-derived-state-no-effect` - Derive state during render, not effects
- `rerender-functional-setstate` - Use functional setState for stable callbacks
- `rerender-lazy-state-init` - Pass function to useState for expensive values
+- `rerender-lower-context-providers` - Put context providers below lazy UI boundaries when only inactive content needs them
- `rerender-simple-expression-in-memo` - Avoid memo for simple primitives
- `rerender-split-combined-hooks` - Split hooks with independent dependencies
- `rerender-move-effect-to-event` - Put interaction logic in event handlers
diff --git a/skills/react-best-practices/metadata.json b/skills/react-best-practices/metadata.json
index 3bec38b1..0490f331 100644
--- a/skills/react-best-practices/metadata.json
+++ b/skills/react-best-practices/metadata.json
@@ -2,7 +2,7 @@
"version": "1.0.0",
"organization": "Vercel Engineering",
"date": "January 2026",
- "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.",
+ "abstract": "Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 71 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.",
"references": [
"https://react.dev",
"https://nextjs.org",
diff --git a/skills/react-best-practices/rules/rerender-lower-context-providers.md b/skills/react-best-practices/rules/rerender-lower-context-providers.md
new file mode 100644
index 00000000..44d63778
--- /dev/null
+++ b/skills/react-best-practices/rules/rerender-lower-context-providers.md
@@ -0,0 +1,84 @@
+---
+title: Lower Context Providers Below Lazy Boundaries
+impact: MEDIUM
+impactDescription: avoids provider setup and context updates for inactive UI
+tags: react, context, providers, modal, lazy-rendering, performance
+---
+
+## Lower Context Providers Below Lazy Boundaries
+
+Place context providers as close as possible to the subtree that consumes them, especially behind UI boundaries that only render when active, such as modals, drawers, popovers, tabs, accordions, and route segments. A provider above the boundary runs its initialization, subscriptions, memoization, and context updates even when the expensive UI is closed or not selected.
+
+**Incorrect (provider runs while the modal is closed):**
+
+```tsx
+function UserSettingsButton({ userId }: { userId: string }) {
+ const [open, setOpen] = useState(false)
+
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+
+function SettingsDataProvider({
+ userId,
+ children,
+}: {
+ userId: string
+ children: React.ReactNode
+}) {
+ const value = useMemo(() => buildSettingsModel(userId), [userId])
+
+ return (
+
+ {children}
+
+ )
+}
+```
+
+**Correct (provider only mounts with modal content):**
+
+```tsx
+function UserSettingsButton({ userId }: { userId: string }) {
+ const [open, setOpen] = useState(false)
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+function SettingsDataProvider({
+ userId,
+ children,
+}: {
+ userId: string
+ children: React.ReactNode
+}) {
+ const value = useMemo(() => buildSettingsModel(userId), [userId])
+
+ return (
+
+ {children}
+
+ )
+}
+```
+
+Keep providers higher only when their state is genuinely shared by always-visible UI, needed to decide whether the boundary should open, or intentionally preloaded before the user activates the subtree.