@loomfsm/bundle-code 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/agents/acceptance.md +141 -0
- package/agents/api-contract.md +89 -0
- package/agents/architect.md +52 -0
- package/agents/challenger-reviewer.md +104 -0
- package/agents/classifier.md +74 -0
- package/agents/code-analyzer.md +43 -0
- package/agents/context-doc-verifier.md +94 -0
- package/agents/dependency-auditor.md +42 -0
- package/agents/implementer.md +135 -0
- package/agents/logic-reviewer.md +132 -0
- package/agents/migration.md +55 -0
- package/agents/performance.md +95 -0
- package/agents/plan-conformance.md +127 -0
- package/agents/plan-grounding-check.md +106 -0
- package/agents/planner.md +143 -0
- package/agents/playwright.md +68 -0
- package/agents/research.md +52 -0
- package/agents/security.md +88 -0
- package/agents/style-reviewer.md +85 -0
- package/agents/test.md +206 -0
- package/agents/ui-consistency.md +75 -0
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.js +34 -0
- package/dist/manifest.js.map +1 -0
- package/dist/src/bundle.d.ts +2 -0
- package/dist/src/bundle.js +424 -0
- package/dist/src/bundle.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +14 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/invariants.d.ts +10 -0
- package/dist/src/invariants.js +208 -0
- package/dist/src/invariants.js.map +1 -0
- package/dist/src/policy-resolver.d.ts +2 -0
- package/dist/src/policy-resolver.js +65 -0
- package/dist/src/policy-resolver.js.map +1 -0
- package/dist/src/sandbox-rules.d.ts +2 -0
- package/dist/src/sandbox-rules.js +40 -0
- package/dist/src/sandbox-rules.js.map +1 -0
- package/dist/test/bundle.test.d.ts +1 -0
- package/dist/test/bundle.test.js +289 -0
- package/dist/test/bundle.test.js.map +1 -0
- package/dist/test/sandbox-rules.test.d.ts +1 -0
- package/dist/test/sandbox-rules.test.js +73 -0
- package/dist/test/sandbox-rules.test.js.map +1 -0
- package/knowledge/references/api-design.md +188 -0
- package/knowledge/references/arch-patterns.md +106 -0
- package/knowledge/references/caching.md +190 -0
- package/knowledge/references/concurrency.md +195 -0
- package/knowledge/references/db-postgres.md +153 -0
- package/knowledge/references/e2e-flutter.md +56 -0
- package/knowledge/references/e2e-playwright.md +53 -0
- package/knowledge/references/error-handling.md +208 -0
- package/knowledge/references/next-app-router.md +231 -0
- package/knowledge/references/observability.md +169 -0
- package/knowledge/references/optimization-strategy.md +197 -0
- package/knowledge/references/perf-flutter.md +62 -0
- package/knowledge/references/perf-nestjs.md +59 -0
- package/knowledge/references/perf-python.md +50 -0
- package/knowledge/references/perf-react.md +52 -0
- package/knowledge/references/react19.md +176 -0
- package/knowledge/references/redis.md +175 -0
- package/knowledge/references/security-backend.md +219 -0
- package/knowledge/references/test-flutter.md +65 -0
- package/knowledge/references/test-nestjs.md +82 -0
- package/knowledge/references/test-python.md +76 -0
- package/knowledge/references/test-react.md +66 -0
- package/knowledge/references/test-strategy.md +175 -0
- package/knowledge/references/ui-flutter.md +56 -0
- package/knowledge/references/ui-web.md +51 -0
- package/package.json +34 -0
- package/schemas/agent-feedback.schema.json +80 -0
- package/schemas/category-vocab.json +170 -0
- package/schemas/classifier-output.schema.json +53 -0
- package/schemas/finding.schema.json +92 -0
- package/schemas/pipeline-state.schema.json +238 -0
- package/schemas/reviewer-output.schema.json +62 -0
- package/schemas/state-extension.schema.json +53 -0
- package/schemas/validator-output.schema.json +48 -0
- package/stack-candidates.yaml +248 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [nextjs, app-router, rsc, route-handlers, layouts, frontend]
|
|
3
|
+
stack_signals:
|
|
4
|
+
- language: [typescript, javascript]
|
|
5
|
+
- project_type: [frontend-app, monorepo]
|
|
6
|
+
summary: |
|
|
7
|
+
Next.js App Router stance — Server Components by default, Client only where
|
|
8
|
+
needed, layered cache (data/route/full-route/edge) decided per route. Each
|
|
9
|
+
file convention has a specific contract; mixing them creates subtle bugs.
|
|
10
|
+
when_to_load: |
|
|
11
|
+
Project uses Next.js ≥13 with App Router (app/ directory, not pages/).
|
|
12
|
+
Diff includes files under app/, 'use client'/'use server' directives,
|
|
13
|
+
loading.tsx, error.tsx, not-found.tsx, route.ts, layout.tsx, revalidate,
|
|
14
|
+
cacheTag, cacheLife, parallel/intercepted routes, or middleware.
|
|
15
|
+
agent_hints: [logic-reviewer, performance, ui-consistency, api-contract]
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Next.js App Router — Senior Stance
|
|
19
|
+
|
|
20
|
+
## When this applies
|
|
21
|
+
Load when project uses Next.js ≥13 with App Router (`app/` directory, not `pages/`). Reviewer auto-loads when diff includes files under `app/`, `'use client'`/`'use server'` directives, `loading.tsx`, `error.tsx`, `not-found.tsx`, `route.ts`, `layout.tsx`, `revalidate`, `cacheTag`, `cacheLife`, parallel/intercepted routes, or middleware. Complements `react19.md` (which covers RSC primitives) — this file is router-specific.
|
|
22
|
+
|
|
23
|
+
## Default Stance
|
|
24
|
+
The App Router collapses concerns that used to live in separate places (data fetching, caching, layouts, error boundaries, middleware, route handlers). Each file convention has a specific contract; mixing them creates subtle bugs. Default to Server Components; mark Client only where you actually need it. Cache behavior is layered (data cache, route cache, full route cache, edge cache) — make caching decisions explicit per route, not by default.
|
|
25
|
+
|
|
26
|
+
## Patterns (use these)
|
|
27
|
+
|
|
28
|
+
### File conventions — know what each does
|
|
29
|
+
- `page.tsx` — the route's UI. Default Server Component.
|
|
30
|
+
- `layout.tsx` — wraps pages in this segment. Persists across navigation. Default Server Component.
|
|
31
|
+
- `template.tsx` — like layout but re-renders on navigation. Use when state should reset.
|
|
32
|
+
- `loading.tsx` — Suspense fallback for the route. Auto-wraps the page.
|
|
33
|
+
- `error.tsx` — error boundary for the route. Must be Client Component.
|
|
34
|
+
- `not-found.tsx` — rendered for `notFound()` calls.
|
|
35
|
+
- `route.ts` (or `route.js`) — HTTP handler. Cannot coexist with `page.tsx` in same segment.
|
|
36
|
+
- `middleware.ts` — runs before request, at the edge.
|
|
37
|
+
|
|
38
|
+
### Server Components by default
|
|
39
|
+
A new component is a Server Component unless you mark it `'use client'`. Server Components:
|
|
40
|
+
- Run only on the server, never ship to the browser.
|
|
41
|
+
- Cannot use hooks (`useState`, `useEffect`, `useRef`, etc.).
|
|
42
|
+
- Can be `async` and fetch data directly.
|
|
43
|
+
- Cannot pass non-serializable values (functions, classes) to Client Components.
|
|
44
|
+
|
|
45
|
+
### Client Components — at the leaves
|
|
46
|
+
- `'use client'` directive at top of file.
|
|
47
|
+
- Mark only what needs interactivity / browser APIs / hooks.
|
|
48
|
+
- Wrap a small interactive piece, leave the rest Server.
|
|
49
|
+
- Server Components can render Client Components, AND can pass them children prop (slot pattern) — Client renders its slot Server-rendered content.
|
|
50
|
+
|
|
51
|
+
### Data fetching: where and how
|
|
52
|
+
- **Server Components**: `await fetch(...)` directly in the component. Next dedupes identical fetches per request, caches per default policy.
|
|
53
|
+
- **Server Actions**: `'use server'` functions. Can be invoked from Client Components for mutations. Auth check at the top.
|
|
54
|
+
- **Route Handlers** (`route.ts`): for non-RSC consumers — webhooks, JSON APIs, third-party callbacks.
|
|
55
|
+
- **Client Components**: still use TanStack Query / SWR for client-side data needs (real-time, optimistic, complex caching).
|
|
56
|
+
|
|
57
|
+
### Caching layers (Next 14+)
|
|
58
|
+
Four layers, each with different invalidation:
|
|
59
|
+
1. **Request Memoization** — per-request dedupe of identical fetches. Automatic.
|
|
60
|
+
2. **Data Cache** — persistent across requests. `fetch` with `next: { revalidate: N }` or `cache: 'no-store'`.
|
|
61
|
+
3. **Full Route Cache** — pre-rendered HTML + RSC payload. Static by default; opt out with dynamic functions (`cookies()`, `headers()`, `searchParams`).
|
|
62
|
+
4. **Router Cache** — client-side, in-memory, soft-navigation cache.
|
|
63
|
+
|
|
64
|
+
Invalidation: `revalidatePath()`, `revalidateTag()`, `revalidate` time-based.
|
|
65
|
+
|
|
66
|
+
In Next 16 (Cache Components): `'use cache'` directive + `cacheLife` / `cacheTag`. New PPR (Partial Prerendering) model. If you're on 16, see those primitives — different from 14/15 cache.
|
|
67
|
+
|
|
68
|
+
### Server Actions: secured at the function
|
|
69
|
+
```ts
|
|
70
|
+
'use server';
|
|
71
|
+
export async function deletePost(formData: FormData) {
|
|
72
|
+
const session = await getServerSession();
|
|
73
|
+
if (!session) throw new Error('unauthorized'); // NEVER skip
|
|
74
|
+
// ... rest
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
- Auth check at top of EVERY action body.
|
|
78
|
+
- Validate inputs with a schema (Zod). FormData is untyped.
|
|
79
|
+
- Don't return huge data. Return success flag, errors, redirect target.
|
|
80
|
+
|
|
81
|
+
### Loading + Error UX
|
|
82
|
+
- Co-locate `loading.tsx` and `error.tsx` per route segment.
|
|
83
|
+
- `loading.tsx` is wrapped in `<Suspense>` automatically — granular suspense by route.
|
|
84
|
+
- `error.tsx` MUST be `'use client'`. Receives `error` and `reset` props.
|
|
85
|
+
- `notFound()` → renders nearest `not-found.tsx`.
|
|
86
|
+
|
|
87
|
+
### Streaming + Suspense
|
|
88
|
+
- Page can be partially streamed: layout renders first, slow data Suspends, finishes streaming when ready.
|
|
89
|
+
- Place `<Suspense>` around slow data sources. Loading UI shows while waiting.
|
|
90
|
+
- One coarse Suspense around everything → users wait for slowest piece. Multiple fine Suspense → progressive reveal.
|
|
91
|
+
|
|
92
|
+
### Parallel and Intercepted Routes
|
|
93
|
+
- **Parallel** (`@slot/page.tsx`): render multiple pages in same layout simultaneously. Use for dashboards with independent regions.
|
|
94
|
+
- **Intercepted** (`(.)foo`, `(..)foo`): show one route in the context of another (e.g., photo modal over feed). Browser refresh shows full route.
|
|
95
|
+
- Powerful, but increases mental load. Use only when payoff is clear.
|
|
96
|
+
|
|
97
|
+
### Middleware
|
|
98
|
+
- Runs on every matching request at the Edge runtime.
|
|
99
|
+
- Auth checks, redirects, A/B routing.
|
|
100
|
+
- Cannot do heavy work — runs on every request.
|
|
101
|
+
- Use `matcher` config to limit which routes run middleware.
|
|
102
|
+
|
|
103
|
+
### Generating routes
|
|
104
|
+
- `generateStaticParams` for static generation.
|
|
105
|
+
- `dynamic = 'force-dynamic'` to opt out.
|
|
106
|
+
- `revalidate = 60` for ISR-like behavior.
|
|
107
|
+
Set explicitly per route — defaults change between Next versions.
|
|
108
|
+
|
|
109
|
+
## Anti-Patterns (DO NOT)
|
|
110
|
+
|
|
111
|
+
### `'use client'` on the root layout
|
|
112
|
+
Marks ENTIRE app tree as Client. You lose all Server Component benefits.
|
|
113
|
+
**Rule:** layout stays Server. Wrap interactive children in their own Client components.
|
|
114
|
+
|
|
115
|
+
### Big `'use client'` boundary at the top of a route
|
|
116
|
+
Page is mostly server-renderable but one button needs `onClick` → marking the whole page Client → entire tree shipped to browser.
|
|
117
|
+
**Rule:** isolate the interactive piece. Server page → renders Client button only.
|
|
118
|
+
|
|
119
|
+
### Server Action without auth check
|
|
120
|
+
**Why it bites:** action is callable directly via fetch from anywhere — components are not security boundaries.
|
|
121
|
+
**Rule:** every Server Action begins with explicit auth check. (Repeated from react19.md because it's the #1 issue.)
|
|
122
|
+
|
|
123
|
+
### Reading `request` / cookies in a layout that's static
|
|
124
|
+
Layout uses `cookies()` → entire route segment opts out of static rendering → unexpectedly dynamic.
|
|
125
|
+
**Rule:** know what you're opting out of. If you need cookies, accept the dynamic cost; if not, isolate the cookie use.
|
|
126
|
+
|
|
127
|
+
### `fetch` without `cache` option, then surprised by stale data
|
|
128
|
+
Default cache behavior changes between Next versions and per-route. Implicit defaults bite you.
|
|
129
|
+
**Rule:** explicit `cache: 'force-cache' | 'no-store'` and `next: { revalidate: N, tags: [...] }` per fetch. Don't rely on memory of defaults.
|
|
130
|
+
|
|
131
|
+
### Mutation in Server Component
|
|
132
|
+
```ts
|
|
133
|
+
async function Page() {
|
|
134
|
+
await deletePost(id); // BAD
|
|
135
|
+
return <div>...</div>;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
Server Components are GET-equivalent. Mutations go through Server Actions (POST) or route handlers.
|
|
139
|
+
**Rule:** mutation paths use Server Actions or route handlers. Server Components only read.
|
|
140
|
+
|
|
141
|
+
### Shared `'use client'` utility with re-exports
|
|
142
|
+
File marked Client, re-exports a Server-only function. Imports cross the boundary in unexpected ways.
|
|
143
|
+
**Rule:** keep Client and Server utility files separate. Import boundary follows directive boundary.
|
|
144
|
+
|
|
145
|
+
### `error.tsx` not marked Client
|
|
146
|
+
File is Server Component (default), but error boundaries must be Client. Build fails or runtime error occurs.
|
|
147
|
+
**Rule:** `error.tsx` always starts with `'use client'`.
|
|
148
|
+
|
|
149
|
+
### One coarse `<Suspense>` wrapping everything
|
|
150
|
+
Slowest data source dictates user-visible wait time. No streaming benefit.
|
|
151
|
+
**Rule:** Suspense at meaningful UI region boundaries (sidebar, main, footer, slow card).
|
|
152
|
+
|
|
153
|
+
### Middleware doing DB / heavy work
|
|
154
|
+
Middleware fires per request at the edge → can't reach app DB cheaply, adds 50-200ms per request.
|
|
155
|
+
**Rule:** middleware = lightweight redirects/auth checks. Heavy work in route handlers.
|
|
156
|
+
|
|
157
|
+
### `searchParams` used in static page
|
|
158
|
+
Page uses `searchParams` → forces dynamic rendering → no static optimization → slower TTFB.
|
|
159
|
+
**Rule:** know that `searchParams` is dynamic. Either accept dynamic, or use `generateStaticParams` for known param sets.
|
|
160
|
+
|
|
161
|
+
### Returning JSX from Server Action
|
|
162
|
+
Server Actions return data; UI is rendered by the page or component on response.
|
|
163
|
+
**Rule:** action returns plain JSON-serializable data. Component re-renders with that data.
|
|
164
|
+
|
|
165
|
+
### `revalidate: 0` everywhere "to be safe"
|
|
166
|
+
Defeats Next's caching, every request hits backing source.
|
|
167
|
+
**Rule:** pick TTL based on data freshness needs.
|
|
168
|
+
|
|
169
|
+
### Calling Client-only APIs inside Server Component
|
|
170
|
+
`window`, `document`, `localStorage` — runtime error or build error.
|
|
171
|
+
**Rule:** access via `'use client'` boundary only. Use `useEffect` for browser APIs.
|
|
172
|
+
|
|
173
|
+
### Imports from Client Component into Server Component creating cycles
|
|
174
|
+
Client Component imports from a server-only module; server-only module imports from a Client Component. Bundler chokes or duplicates code.
|
|
175
|
+
**Rule:** clean dependency tree. Server depends on Server; Client may depend on Client + serializable Server exports.
|
|
176
|
+
|
|
177
|
+
### Migrating Pages Router incrementally without strategy
|
|
178
|
+
Keeping `pages/` and `app/` simultaneously, sharing components without checking which directives propagate.
|
|
179
|
+
**Rule:** plan migration per-segment. Don't expect Pages Router middleware/_app to apply to App Router routes.
|
|
180
|
+
|
|
181
|
+
## Decision Framework
|
|
182
|
+
|
|
183
|
+
| Need | Choice |
|
|
184
|
+
|---|---|
|
|
185
|
+
| Read DB and render | Server Component with `await` |
|
|
186
|
+
| Form submit | Server Action + `useActionState` |
|
|
187
|
+
| Optimistic UI | `useOptimistic` (Client Component, see react19.md) |
|
|
188
|
+
| Public JSON API | Route handler `route.ts` |
|
|
189
|
+
| Webhook receiver | Route handler with signature verification |
|
|
190
|
+
| Auth redirect | Middleware (lightweight) or in layout/page |
|
|
191
|
+
| Real-time data | Client Component + WebSocket / SSE / polling |
|
|
192
|
+
| Slow data + fast layout | Suspense around slow part; layout renders first |
|
|
193
|
+
| Data shared across pages | Layout component fetches; pages receive via children render |
|
|
194
|
+
| Mutating data + revalidating | Server Action calls `revalidatePath` / `revalidateTag` |
|
|
195
|
+
| Static page with occasional updates | `revalidate: N` (ISR) |
|
|
196
|
+
| Per-user dynamic page | `dynamic = 'force-dynamic'` or use of cookies/headers |
|
|
197
|
+
| Multiple regions in a dashboard | Parallel routes (`@slot`) |
|
|
198
|
+
| Modal that's also a real route | Intercepted routes |
|
|
199
|
+
|
|
200
|
+
## Cost Model
|
|
201
|
+
|
|
202
|
+
| Pattern | Cost / Win |
|
|
203
|
+
|---|---|
|
|
204
|
+
| Server Component | 0 client JS for that component |
|
|
205
|
+
| `'use client'` on a leaf | +5-30KB JS bundle for that island |
|
|
206
|
+
| `'use client'` on root layout | Entire app shipped to browser |
|
|
207
|
+
| Server Action call | 1 round trip + serialization |
|
|
208
|
+
| Static route (full prerender) | Sub-100ms TTFB from CDN |
|
|
209
|
+
| `dynamic = 'force-dynamic'` | Every request hits the server; TTFB depends on backing data |
|
|
210
|
+
| ISR with revalidate=60 | First request after 60s rebuilds; users see brief stale |
|
|
211
|
+
| Middleware on every route | +5-50ms per request at the edge |
|
|
212
|
+
| Coarse Suspense | Wait for slowest data → entire page blocked |
|
|
213
|
+
| Granular Suspense | Progressive reveal; better perceived perf |
|
|
214
|
+
|
|
215
|
+
## Red Flags in Diff
|
|
216
|
+
|
|
217
|
+
- `'use client'` added to `app/layout.tsx` or any top-level layout → flag (massive bundle impact).
|
|
218
|
+
- `'use client'` added to a component without any hooks / event handlers / browser APIs → flag (probably can stay Server).
|
|
219
|
+
- Server Action without explicit auth check at top of body → flag immediately.
|
|
220
|
+
- New `error.tsx` not starting with `'use client'` → flag.
|
|
221
|
+
- New `fetch(...)` in Server Component without explicit `cache` / `next.revalidate` config → flag (implicit-default risk).
|
|
222
|
+
- `cookies()` / `headers()` used in a layout that's expected to be static → flag (forces dynamic render of all child pages).
|
|
223
|
+
- Server Action returning JSX or a class instance → flag (must be JSON-serializable).
|
|
224
|
+
- New middleware doing DB / heavy work → flag.
|
|
225
|
+
- Whole route or page wrapped in single `<Suspense>` → flag (no streaming benefit).
|
|
226
|
+
- `revalidate: 0` everywhere → flag (caching defeated).
|
|
227
|
+
- New `searchParams` use in a page expected to be statically generated → flag.
|
|
228
|
+
- Server Component file imports from a `'use client'` file and uses non-serializable export → flag.
|
|
229
|
+
- Route handler `route.ts` AND `page.tsx` in same segment → flag (illegal).
|
|
230
|
+
- `redirect()` inside Server Action without idempotency consideration → flag.
|
|
231
|
+
- New parallel/intercepted route without `default.tsx` for the parallel slot when no match → flag (will crash).
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [observability, logging, tracing, metrics, alerts, slo, opentelemetry]
|
|
3
|
+
stack_signals: []
|
|
4
|
+
summary: |
|
|
5
|
+
Observability design — logs for forensics, metrics for alerts, traces for
|
|
6
|
+
request paths. Every new endpoint, job, or external dependency emits at
|
|
7
|
+
least one metric, structured logs, and propagates trace context.
|
|
8
|
+
when_to_load: |
|
|
9
|
+
Task touches logging, structured logs, tracing (OpenTelemetry, distributed
|
|
10
|
+
tracing), metrics emission, health checks, alerting rules, dashboards, or
|
|
11
|
+
error reporting. Diff including new endpoints, new background jobs, new
|
|
12
|
+
external integrations, or any change that ships behavior-the-team-needs-to-watch
|
|
13
|
+
also qualifies.
|
|
14
|
+
agent_hints: [logic-reviewer, performance, challenger-reviewer]
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Observability — Senior Stance
|
|
18
|
+
|
|
19
|
+
## When this applies
|
|
20
|
+
Load when task touches: logging, structured logs, tracing (OpenTelemetry, distributed tracing), metrics emission, health checks, alerting rules, dashboards, error reporting. Reviewer auto-loads when diff includes new endpoints, new background jobs, new external integrations, or any change that ships behavior-the-team-needs-to-watch.
|
|
21
|
+
|
|
22
|
+
## Default Stance
|
|
23
|
+
You can't fix what you can't see. Every new endpoint, job, or external dependency MUST emit at least one metric, one structured log on entry/exit, and propagate trace context. Logs are for forensics, metrics are for alerts, traces are for "where did this request go". The three are complementary, not interchangeable. Sampling is fine; not emitting at all is not.
|
|
24
|
+
|
|
25
|
+
## Patterns (use these)
|
|
26
|
+
|
|
27
|
+
### Structured logs
|
|
28
|
+
- JSON format. One event per line. Machine-parseable.
|
|
29
|
+
- Required fields: `timestamp`, `level`, `message`, `service`, `request_id` (or `trace_id`).
|
|
30
|
+
- Domain fields: `user_id`, `task_id`, `endpoint`, `duration_ms`, etc.
|
|
31
|
+
- NEVER log sensitive data: passwords, tokens, full credit card, full SSN. Hash or redact at log boundary.
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{"ts":"2026-05-10T12:34:56Z","level":"info","msg":"user.created","service":"api","trace_id":"abc","user_id":"u_123","duration_ms":42}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Trace context propagation
|
|
38
|
+
- Every request gets a `trace_id` at the edge. Pass it downstream via header (`traceparent` per W3C Trace Context, or X-Request-ID).
|
|
39
|
+
- Each service emits its span with its operation, duration, status.
|
|
40
|
+
- Log lines include `trace_id` so you can correlate log events with the trace.
|
|
41
|
+
|
|
42
|
+
### Metric types
|
|
43
|
+
- **Counter** — monotonic increasing (`requests_total`, `errors_total`). Compute rate via `rate(counter[5m])` in Prometheus.
|
|
44
|
+
- **Gauge** — point-in-time value (`active_connections`, `queue_depth`). Goes up and down.
|
|
45
|
+
- **Histogram** — distribution (request duration, payload size). Compute p50, p95, p99 via `histogram_quantile`.
|
|
46
|
+
|
|
47
|
+
Naming: `<domain>_<entity>_<unit>`: `http_request_duration_seconds`, `db_query_duration_seconds`, `cache_hits_total`. Lowercase snake_case.
|
|
48
|
+
|
|
49
|
+
### RED method (per request-driven service)
|
|
50
|
+
For every endpoint:
|
|
51
|
+
- **R**ate — requests per second.
|
|
52
|
+
- **E**rrors — error rate (4xx + 5xx, OR business error count).
|
|
53
|
+
- **D**uration — p50 / p95 / p99 latency.
|
|
54
|
+
|
|
55
|
+
Dashboard: one row per endpoint, columns R-E-D. Glance to see "what's broken".
|
|
56
|
+
|
|
57
|
+
### USE method (per resource)
|
|
58
|
+
For every resource (CPU, memory, disk, connection pool, queue):
|
|
59
|
+
- **U**tilization — % busy.
|
|
60
|
+
- **S**aturation — queue depth / wait time.
|
|
61
|
+
- **E**rrors — count of errors talking to this resource.
|
|
62
|
+
|
|
63
|
+
### Service Level Objectives (SLOs)
|
|
64
|
+
- Define a metric (e.g., "99.5% of requests < 500ms over 30 days").
|
|
65
|
+
- Track an error budget (1 - SLO target).
|
|
66
|
+
- Alert when error budget burn rate is high (will exhaust before period end).
|
|
67
|
+
- Don't alert on every breach — alert on burn-rate over a window.
|
|
68
|
+
|
|
69
|
+
### Alerting hygiene
|
|
70
|
+
- **Symptom-based**, not cause-based. "p95 latency above 1s for 5 min" is a symptom alert. "CPU above 80%" is a cause alert (often false-positive).
|
|
71
|
+
- **Actionable** — every alert must have a runbook link explaining what the operator does next.
|
|
72
|
+
- **Escalation tiers** — page only for things that need immediate human action. Slack-channel alerts for things that need attention within hours.
|
|
73
|
+
- **No mystery alerts** — if oncall doesn't know why an alert fired, the alert is broken. Fix or delete.
|
|
74
|
+
|
|
75
|
+
### Health checks
|
|
76
|
+
- **Liveness** — "is process up". Cheap, never depends on external services. Used by orchestrator (k8s) to restart.
|
|
77
|
+
- **Readiness** — "can serve traffic". May check DB connection, downstream service, cache. Used by load balancer to drain.
|
|
78
|
+
- Don't conflate them. Liveness failing = restart me; readiness failing = stop sending traffic.
|
|
79
|
+
|
|
80
|
+
### Error reporting
|
|
81
|
+
- Capture error + stack trace + request context (user_id, request_id, path, params).
|
|
82
|
+
- Group by error fingerprint (Sentry, Honeybadger, etc.).
|
|
83
|
+
- Tag with deploy version → "this error started at deploy 4.2.0".
|
|
84
|
+
- Don't capture every error: validation errors and 4xx are noise. Capture 5xx and unexpected exceptions.
|
|
85
|
+
|
|
86
|
+
## Anti-Patterns (DO NOT)
|
|
87
|
+
|
|
88
|
+
### Logging without structure
|
|
89
|
+
`logger.info(\`User \${userId} did \${action} at \${time}\`)` → unparseable freeform string.
|
|
90
|
+
**Why it bites:** can't filter, group, or aggregate. Every grep is a one-off.
|
|
91
|
+
**Rule:** structured fields always. `logger.info('user.action', { user_id, action, ts })`.
|
|
92
|
+
|
|
93
|
+
### Logging sensitive data
|
|
94
|
+
`logger.info('login attempt', { email, password })`. Tokens, secrets, full PII in logs = breach surface.
|
|
95
|
+
**Rule:** redact at log boundary. Reject log lines containing forbidden patterns in CI (regex check).
|
|
96
|
+
|
|
97
|
+
### Excessive logging on hot paths
|
|
98
|
+
Every request → 50 log lines. At 1k QPS, you're emitting 50k lines/sec. Log pipeline backed up; storage cost spikes.
|
|
99
|
+
**Rule:** ONE log per request entry, ONE per exit (with duration). Detail logs at DEBUG level only, sampled or dynamically enabled.
|
|
100
|
+
|
|
101
|
+
### Metric names that are unique per request
|
|
102
|
+
`requests_total{user_id="u_12345"}` → cardinality explosion. Prometheus can't handle 1M time-series.
|
|
103
|
+
**Rule:** metric labels are LOW cardinality (≤100 unique values). High-cardinality data goes in logs/traces, not metrics.
|
|
104
|
+
|
|
105
|
+
### Alerting on every breach
|
|
106
|
+
"Latency exceeded threshold" alert fires once per minute when service is degraded. Operator drowns.
|
|
107
|
+
**Rule:** sustained breach (> 5 min) OR error budget burn rate. Alerts have hysteresis.
|
|
108
|
+
|
|
109
|
+
### Cause-based alerts everywhere
|
|
110
|
+
"CPU > 80%" — but the service is fine, the autoscaler will handle it.
|
|
111
|
+
**Rule:** alert on user-impacting symptoms. CPU/memory only when no symptom-level alert exists for the failure mode.
|
|
112
|
+
|
|
113
|
+
### No trace context across services
|
|
114
|
+
Service A has `trace_id=abc`, service B logs without it. Can't follow a request across services.
|
|
115
|
+
**Rule:** propagate trace context via header at every hop. Library / middleware ensures this; don't rely on per-handler discipline.
|
|
116
|
+
|
|
117
|
+
### Logging plus printing
|
|
118
|
+
`console.log(...)` AND `logger.info(...)` for the same event. Or `print('debug')` left in.
|
|
119
|
+
**Rule:** one logger, configured per environment. No bare `print` / `console.log` in committed code.
|
|
120
|
+
|
|
121
|
+
### Unmonitored "fire and forget" jobs
|
|
122
|
+
Background job runs, fails silently, no metric emitted. Bug ships when output dashboard shows zero new records for a day.
|
|
123
|
+
**Rule:** every job emits start/finish/duration, success/failure. Alert on missing successful run.
|
|
124
|
+
|
|
125
|
+
### Health check that always returns 200
|
|
126
|
+
`GET /health → 200 OK` even when DB is down. Load balancer keeps sending traffic to broken instance.
|
|
127
|
+
**Rule:** readiness check actually verifies dependencies it serves with.
|
|
128
|
+
|
|
129
|
+
### Sampling errors
|
|
130
|
+
1% sampled error reporting: 99% of errors invisible.
|
|
131
|
+
**Rule:** sample successful traces aggressively (1-10%). Capture errors at 100% (or near-100%).
|
|
132
|
+
|
|
133
|
+
## Decision Framework
|
|
134
|
+
|
|
135
|
+
| Need | Tool |
|
|
136
|
+
|---|---|
|
|
137
|
+
| "What happened in this specific request?" | Distributed trace + structured logs |
|
|
138
|
+
| "How is the system performing right now?" | Metrics dashboard (RED + USE) |
|
|
139
|
+
| "Wake me when something is broken" | Alerts on SLO burn / error budget |
|
|
140
|
+
| "Where's the error coming from?" | Error reporting (Sentry-class), grouped by fingerprint |
|
|
141
|
+
| New endpoint | Add: 1 entry log, 1 exit log, RED metrics, span |
|
|
142
|
+
| New background job | Add: start/finish logs, duration metric, success/fail counter, dead-letter queue with alert |
|
|
143
|
+
| New external dependency | Add: latency histogram, error counter, circuit-breaker state metric |
|
|
144
|
+
| Slow-query investigation | Trace → see which span is slow → check that span's logs |
|
|
145
|
+
|
|
146
|
+
## Cost Model
|
|
147
|
+
|
|
148
|
+
| Item | Cost magnitude |
|
|
149
|
+
|---|---|
|
|
150
|
+
| Structured log line, indexed | $0.50-2 per GB ingested (varies by vendor) |
|
|
151
|
+
| Metric time-series with low cardinality | $0.01-0.10 per series per month |
|
|
152
|
+
| Distributed trace span | $0.50-2 per million spans (often sampled to 1-10%) |
|
|
153
|
+
| Error reported & grouped | $0.10-1 per event (Sentry pricing tier) |
|
|
154
|
+
| 1 mystery alert (waking oncall) | Hours of human time + trust erosion |
|
|
155
|
+
|
|
156
|
+
## Red Flags in Diff
|
|
157
|
+
|
|
158
|
+
- New endpoint without entry/exit log lines or duration metric → flag.
|
|
159
|
+
- New `console.log` / `print` in non-test code → flag.
|
|
160
|
+
- New log line containing `password`, `token`, `secret`, `api_key` substrings → flag immediately (security + observability).
|
|
161
|
+
- New metric label using request-unique IDs (`user_id`, `request_id`) → flag (cardinality blowup).
|
|
162
|
+
- New alerting rule without runbook reference / linked doc → flag.
|
|
163
|
+
- New alert fires on instantaneous breach (no duration window) → flag (flap risk).
|
|
164
|
+
- New external HTTP/DB call without timeout AND without error metric → flag.
|
|
165
|
+
- Health check returning 200 statically → flag.
|
|
166
|
+
- New background job without success/failure metric → flag.
|
|
167
|
+
- Trace context not propagated across new service boundary (header not forwarded) → flag.
|
|
168
|
+
- New error swallowed silently (`try { ... } catch {}`) without log/metric → flag.
|
|
169
|
+
- Logging large payloads (full request body, full DB row) on hot path → flag (cost + PII risk).
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [performance, optimization, profiling, latency, throughput, slo]
|
|
3
|
+
stack_signals: []
|
|
4
|
+
summary: |
|
|
5
|
+
Strategy-level performance discipline — measure before you optimize. Profile,
|
|
6
|
+
hypothesize, change one thing, measure again. Pairs with platform-specific
|
|
7
|
+
perf-*.md files.
|
|
8
|
+
when_to_load: |
|
|
9
|
+
Task touches performance-sensitive code, "make it faster" is in scope, a
|
|
10
|
+
perf regression is suspected, or a feature has explicit latency/throughput
|
|
11
|
+
requirements. Preemptive load when CLAUDE.md or task mentions SLO, latency
|
|
12
|
+
budget, "scale to N users", or similar.
|
|
13
|
+
agent_hints: [performance, logic-reviewer, challenger-reviewer]
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Optimization Strategy — Senior Stance
|
|
17
|
+
|
|
18
|
+
## When this applies
|
|
19
|
+
Load when task touches performance-sensitive code, when "make it faster" is in scope, when a perf regression is suspected, or when a feature has explicit latency/throughput requirements. Performance Agent loads this in addition to platform-specific perf-{stack}.md. Load preemptively when CLAUDE.md or task mentions SLO, latency budget, "scale to N users", or similar.
|
|
20
|
+
|
|
21
|
+
## Default Stance
|
|
22
|
+
Don't optimize what you haven't measured. Most "obviously slow" code is fast enough; most "obviously fine" code has surprises. Profile first, hypothesize, change one thing, measure again. Optimization without measurement is decoration. Once you've measured, fix the biggest hot spot — the long tail rarely matters.
|
|
23
|
+
|
|
24
|
+
The order: **correct → tested → measured → optimized**. Skip steps and you're guessing.
|
|
25
|
+
|
|
26
|
+
## Patterns (use these)
|
|
27
|
+
|
|
28
|
+
### Measure before, measure after
|
|
29
|
+
- Establish a baseline: what's slow, by how much, under what load?
|
|
30
|
+
- Make the change.
|
|
31
|
+
- Re-measure under the same conditions.
|
|
32
|
+
- If you can't tell the difference, you didn't optimize anything.
|
|
33
|
+
|
|
34
|
+
### Profile to find hot spots
|
|
35
|
+
Tools by stack:
|
|
36
|
+
- Node.js: `--prof` + processed with `--prof-process`, or `clinic.js`, or Chrome DevTools.
|
|
37
|
+
- Python: `cProfile` + `snakeviz` or `py-spy` (sampling, low overhead, prod-safe).
|
|
38
|
+
- JVM: `async-profiler`, `JFR`.
|
|
39
|
+
- Go: `pprof` (built-in).
|
|
40
|
+
- Browser: Chrome DevTools Performance tab; Lighthouse for page-level.
|
|
41
|
+
- DB: EXPLAIN ANALYZE; `pg_stat_statements`.
|
|
42
|
+
|
|
43
|
+
Look for: tall stack frames, repeated work per call, calls into expensive primitives (DB, network, parse).
|
|
44
|
+
|
|
45
|
+
### Latency vs throughput
|
|
46
|
+
Different goals, different fixes:
|
|
47
|
+
- **Latency** (single-request time): reduce work in the request path. Cache, denormalize, precompute, prefetch.
|
|
48
|
+
- **Throughput** (aggregate ops/sec): reduce contention, parallelize, batch, queue.
|
|
49
|
+
A change that improves latency may hurt throughput (e.g., always-fresh cache lookup beats stale-while-revalidate for latency, but more DB load → worse throughput).
|
|
50
|
+
|
|
51
|
+
### Big-O matters when N is large
|
|
52
|
+
- 1000 items in a list: O(N) vs O(N²) matters → microseconds vs milliseconds.
|
|
53
|
+
- 1M items: O(N) vs O(N²) matters → seconds vs minutes.
|
|
54
|
+
- 10 items: O(N²) is fine; readability beats cleverness.
|
|
55
|
+
Don't optimize O(N) → O(log N) when N=10. Don't tolerate O(N²) when N=10K.
|
|
56
|
+
|
|
57
|
+
### Hot loop discipline
|
|
58
|
+
For code that runs millions of times per second:
|
|
59
|
+
- Avoid allocations inside the loop (object creation, array spread, string concat).
|
|
60
|
+
- Avoid closures/functions created per iteration.
|
|
61
|
+
- Hoist invariants out of the loop.
|
|
62
|
+
- Batch where possible.
|
|
63
|
+
|
|
64
|
+
For code that runs 100x: clarity beats micro-optimization.
|
|
65
|
+
|
|
66
|
+
### Caching as last resort, not first
|
|
67
|
+
Cache is hard (invalidation, staleness, stampedes — see caching.md). Add a cache only when:
|
|
68
|
+
- The underlying call is measurably expensive.
|
|
69
|
+
- The data has clear invalidation semantics.
|
|
70
|
+
- You've designed how the cache empties.
|
|
71
|
+
- The hit rate justifies the complexity.
|
|
72
|
+
|
|
73
|
+
Often the right answer is "fix the slow query" (add index, denormalize, materialize) — not "cache around it".
|
|
74
|
+
|
|
75
|
+
### Database-side optimization first
|
|
76
|
+
For data-heavy operations, the DB is usually the bottleneck. Before app-side caching:
|
|
77
|
+
- Add indexes for new query shapes.
|
|
78
|
+
- Rewrite N+1 as JOIN or batch loader.
|
|
79
|
+
- Use materialized views for expensive aggregations.
|
|
80
|
+
- Consider read replicas for read-heavy paths.
|
|
81
|
+
|
|
82
|
+
### Bundle-size optimization (frontend)
|
|
83
|
+
- Measure with `webpack-bundle-analyzer`, `vite-plugin-visualizer`, `next build` output.
|
|
84
|
+
- Code-split routes (lazy / dynamic imports).
|
|
85
|
+
- Remove unused deps; replace heavy libs (moment → date-fns → native Intl).
|
|
86
|
+
- Tree-shake-friendly imports (`import { format } from 'date-fns'` not `import _ from 'date-fns'`).
|
|
87
|
+
|
|
88
|
+
### Render performance (frontend)
|
|
89
|
+
- Profile with React DevTools Profiler / Vue Devtools.
|
|
90
|
+
- Look for unnecessary rerenders. Memo only after profiling identifies the cost.
|
|
91
|
+
- Move expensive work off render path: `useMemo` for compute, `useCallback` to stabilize refs, `useDeferredValue` for non-urgent updates.
|
|
92
|
+
- React Compiler (when enabled) handles most of this; manual memo becomes anti-pattern.
|
|
93
|
+
|
|
94
|
+
### Networking
|
|
95
|
+
- Reduce round trips: batch where API allows.
|
|
96
|
+
- Compression: gzip / brotli for text responses.
|
|
97
|
+
- HTTP/2 multiplexing eliminates per-request connection overhead.
|
|
98
|
+
- CDN for static assets and edge-cacheable dynamic content.
|
|
99
|
+
- Connection pooling for outbound HTTP.
|
|
100
|
+
|
|
101
|
+
## Anti-Patterns (DO NOT)
|
|
102
|
+
|
|
103
|
+
### Optimize without measuring
|
|
104
|
+
"This loop is slow, let me optimize" — without profiling. Spend a day; benchmark says no improvement.
|
|
105
|
+
**Rule:** profile first. The hot spot is rarely where you think.
|
|
106
|
+
|
|
107
|
+
### Micro-optimize cold paths
|
|
108
|
+
Code runs 5 times per day; spend a week making it 20% faster.
|
|
109
|
+
**Rule:** ROI matters. Optimize where the wall-clock time lives.
|
|
110
|
+
|
|
111
|
+
### Cache everything
|
|
112
|
+
"Add cache to make it faster" → invalidation bugs ship → stale data shown to users → harder bug to fix than the original perf.
|
|
113
|
+
**Rule:** cache only what's measurably expensive AND has clear invalidation. Otherwise fix the underlying cost.
|
|
114
|
+
|
|
115
|
+
### Premature parallelization
|
|
116
|
+
Parallel implementation is harder to debug, harder to read, harder to maintain. If serial is fast enough, leave it alone.
|
|
117
|
+
**Rule:** parallelize after measuring serial cost.
|
|
118
|
+
|
|
119
|
+
### Optimize without context
|
|
120
|
+
Same code path: 50ms in dev, 5ms in prod (cached at scale). Optimizing dev path costs eng time, prod doesn't care.
|
|
121
|
+
**Rule:** measure under prod-shaped load.
|
|
122
|
+
|
|
123
|
+
### Synthetic benchmarks unrepresentative of real load
|
|
124
|
+
Loop 1M times calling `f(0)` — JIT detects constant, eliminates the call. Benchmark says "f is free". Real callers vary input → JIT doesn't help → f is expensive.
|
|
125
|
+
**Rule:** realistic input distribution; warm-up; multiple runs.
|
|
126
|
+
|
|
127
|
+
### Optimizing without acceptance criteria
|
|
128
|
+
"Make it faster" with no target. Spend forever; never know when to stop.
|
|
129
|
+
**Rule:** define the target. p95 < 200ms. Bundle < 100KB. Then stop when met.
|
|
130
|
+
|
|
131
|
+
### "Faster" code that breaks invariants
|
|
132
|
+
Removed a defensive check "for perf"; turns out the check was load-bearing in an edge case.
|
|
133
|
+
**Rule:** measure, change, re-measure, AND re-test. Performance change must keep tests passing.
|
|
134
|
+
|
|
135
|
+
### Optimizing the wrong layer
|
|
136
|
+
App caches DB result; DB query was actually fast; the slow part was the JSON serialization. Cache helps a little; fixing the serialization helps a lot.
|
|
137
|
+
**Rule:** profile points at the layer; fix at the layer the profile points to.
|
|
138
|
+
|
|
139
|
+
### Memoizing pure functions that are already cheap
|
|
140
|
+
`useMemo(() => x + y, [x, y])` — overhead of memo > cost of `+`. Especially with React Compiler.
|
|
141
|
+
**Rule:** memo when profile shows it pays. Otherwise it's noise.
|
|
142
|
+
|
|
143
|
+
### "10x faster" claims without measurement
|
|
144
|
+
PR description says "10x faster". No benchmark. No before/after.
|
|
145
|
+
**Rule:** include measurement in the PR. Numbers, not vibes.
|
|
146
|
+
|
|
147
|
+
### Killing readability for micro-perf
|
|
148
|
+
`for (let i = 0, l = arr.length; i < l; ++i)` instead of `for (const x of arr)` — saves nanoseconds, costs reader 5 seconds. Hot loop? OK. Cold path? Don't.
|
|
149
|
+
|
|
150
|
+
## Decision Framework
|
|
151
|
+
|
|
152
|
+
| Symptom | Investigation order |
|
|
153
|
+
|---|---|
|
|
154
|
+
| Slow request handler | Profile request path → identify slowest span → fix slowest |
|
|
155
|
+
| Slow page load | Lighthouse → bundle analysis → render profile → fix biggest |
|
|
156
|
+
| High DB latency | EXPLAIN slowest queries → indexes → denormalize → cache as last resort |
|
|
157
|
+
| OOM under load | Heap profile → leaks → unbounded data structures → caching with proper bounds |
|
|
158
|
+
| CPU pinned | Profile → hot function → algorithmic vs micro fix |
|
|
159
|
+
| Throughput plateau | Identify bottleneck (CPU? IO? DB pool? Lock contention?) → fix that one |
|
|
160
|
+
| Tail latency p99 high | Find: GC pauses? Cache miss? DB connection wait? Each has different fix |
|
|
161
|
+
| Slow boot/cold start | Lazy-load non-critical modules; warm pools; provisioned concurrency for serverless |
|
|
162
|
+
|
|
163
|
+
## Cost Model
|
|
164
|
+
|
|
165
|
+
| Optimization | Typical effort | Typical gain |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| Add missing DB index | hours | 10-1000x query speedup |
|
|
168
|
+
| Fix N+1 with JOIN | hours | 10-100x for affected request |
|
|
169
|
+
| Add Redis cache for hot read | day | 5-10x latency, IF hit rate is high |
|
|
170
|
+
| Code-split a heavy route | hours | 30-70% initial bundle reduction |
|
|
171
|
+
| Replace heavy lib (moment → date-fns) | hours | 50-80% lib size reduction |
|
|
172
|
+
| Memoize expensive React component | minutes | 0% if not on hot path; 10-30% if it is |
|
|
173
|
+
| Refactor algorithm O(N²) → O(N log N) | day-week | massive at scale, zero at small N |
|
|
174
|
+
| Migrate to faster runtime / lib | weeks-months | 10-50%; high risk |
|
|
175
|
+
| Add connection pool | hours | reduces tail latency markedly under load |
|
|
176
|
+
|
|
177
|
+
| Anti-pattern | Cost when wrong |
|
|
178
|
+
|---|---|
|
|
179
|
+
| Cache hides slow query | Quality bug shipped via stale data; original problem still there |
|
|
180
|
+
| Premature parallelism | Code 5x harder to read, race conditions, no measured win |
|
|
181
|
+
| Micro-opt over readability | Slowed team velocity; bug-prone code; nano gain |
|
|
182
|
+
| No measurement before / after | Could be ZERO actual improvement, you have no idea |
|
|
183
|
+
|
|
184
|
+
## Red Flags in Diff
|
|
185
|
+
|
|
186
|
+
- New `useMemo` / `useCallback` without a profile/comment justifying it (especially with React Compiler enabled) → flag.
|
|
187
|
+
- New cache layer added without a TTL OR without invalidation strategy → flag (see caching.md).
|
|
188
|
+
- New parallel code (Promise.all, asyncio.gather) without bound on concurrency → flag.
|
|
189
|
+
- New "optimization" PR without before/after benchmark numbers → flag.
|
|
190
|
+
- New micro-optimized code (manual loops, hand-rolled algorithms) replacing a clear stdlib call without measurement → flag.
|
|
191
|
+
- Removed validation / safety check labelled "for perf" → flag.
|
|
192
|
+
- Hardcoded "magic number" tunable (timeout, batch size) without comment about source → flag.
|
|
193
|
+
- Profile-driven changes affecting hot path without test coverage on the changed paths → flag (perf regression risk).
|
|
194
|
+
- New `setImmediate` / `setTimeout(0)` claims to "improve perf" → flag (almost always wrong fix).
|
|
195
|
+
- Heavy library added in a code path that already had a lighter alternative → flag.
|
|
196
|
+
- Heavy operation moved into render / hot loop → flag.
|
|
197
|
+
- New "fast path" with subtly different semantics from the slow path → flag (correctness drift risk).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [performance, flutter, dart, widget-rebuild, mobile]
|
|
3
|
+
stack_signals:
|
|
4
|
+
- language: [dart]
|
|
5
|
+
- project_type: [mobile, frontend-app]
|
|
6
|
+
summary: |
|
|
7
|
+
Flutter / Dart performance checklist — const constructors, build() scope,
|
|
8
|
+
setState() placement, list virtualization, image caching.
|
|
9
|
+
when_to_load: |
|
|
10
|
+
Task touches Flutter widgets, Dart code with perf concerns, or mobile-app
|
|
11
|
+
scale targets. Diff in *.dart with widget tree changes, setState() calls,
|
|
12
|
+
or list/scroll views.
|
|
13
|
+
agent_hints: [performance, logic-reviewer, ui-consistency]
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Performance: Flutter / Dart
|
|
17
|
+
|
|
18
|
+
## Widget Rebuilds
|
|
19
|
+
- Missing `const` constructors on stateless widgets and static children
|
|
20
|
+
- Large `build()` methods that should be split into smaller widgets
|
|
21
|
+
- `setState()` at too high a level (rebuilds entire subtree instead of targeted widget)
|
|
22
|
+
- Missing `const` keyword on widget constructors with no dynamic params
|
|
23
|
+
- Heavy computation inside `build()` — move to `initState()` or compute outside
|
|
24
|
+
|
|
25
|
+
## Lists & Scrolling
|
|
26
|
+
- `ListView(children: [...])` with 20+ items — use `ListView.builder` instead
|
|
27
|
+
- Missing `itemExtent` or `prototypeItem` on large uniform lists
|
|
28
|
+
- `SingleChildScrollView` wrapping a `Column` with many children — use `ListView`
|
|
29
|
+
- Missing `cacheExtent` tuning for heavy list items
|
|
30
|
+
- `IntrinsicWidth`/`IntrinsicHeight` in lists — causes expensive two-pass layout
|
|
31
|
+
|
|
32
|
+
## Animation & Painting
|
|
33
|
+
- Missing `RepaintBoundary` to isolate frequently repainting regions
|
|
34
|
+
- Using `Opacity` widget — use `FadeTransition` or `AnimatedOpacity` instead (Opacity forces offscreen buffer via saveLayer)
|
|
35
|
+
- `ShaderMask`, `ColorFilter`, `ClipPath` with non-default clipBehavior trigger expensive saveLayer calls
|
|
36
|
+
- First-run shader compilation jank — consider Impeller (default on iOS) or `--bundle-sksl-path` for Skia
|
|
37
|
+
|
|
38
|
+
## Images & Assets
|
|
39
|
+
- No `cacheWidth`/`cacheHeight` on large images (decode full resolution for small display)
|
|
40
|
+
- Missing `CachedNetworkImage` — raw `Image.network` without caching
|
|
41
|
+
- Large images loaded without resize — use `ResizeImage` or server-side thumbnails
|
|
42
|
+
- SVG assets that could be compiled to code via `flutter_svg` or replaced with icons
|
|
43
|
+
|
|
44
|
+
## State Management
|
|
45
|
+
- Riverpod/BLoC/Provider at too high a scope (rebuilds unrelated widgets)
|
|
46
|
+
- Missing `select()` / `Selector` — listening to entire state when only one field needed
|
|
47
|
+
- `FutureBuilder` / `StreamBuilder` recreating Future/Stream on every build (store in variable or initState)
|
|
48
|
+
- Missing `GlobalKey` cleanup — excessive GlobalKeys kept alive unnecessarily
|
|
49
|
+
|
|
50
|
+
## Async & Resources
|
|
51
|
+
- Missing `dispose()` for controllers, streams, animation controllers
|
|
52
|
+
- `Timer.periodic` without cancel in `dispose()`
|
|
53
|
+
- Heavy work on main isolate (image processing, JSON parsing of large payloads) — use `compute()` or `Isolate.run()`
|
|
54
|
+
- Network request deduplication — multiple widgets triggering same fetch without caching
|
|
55
|
+
|
|
56
|
+
## Platform & Size
|
|
57
|
+
- Unused packages in `pubspec.yaml` (inflates app size)
|
|
58
|
+
- Tree-shaking for icon fonts is default in release mode — verify not disabled
|
|
59
|
+
- Platform channels called in hot path without caching result
|
|
60
|
+
|
|
61
|
+
## Profiling Note
|
|
62
|
+
Always profile in **profile or release mode** — debug mode has vastly different performance characteristics. Use DevTools Performance tab or `flutter run --profile`.
|