nextjs-optimization
<!-- Loaded by performance-optimization-engineer when task involves Next.js, App Router, SSR, ISR, next/image, next/font, streaming, or server components -->
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
Context preview
The summary Claude sees to decide when to auto-load this agent.
<!-- Loaded by performance-optimization-engineer when task involves Next.js, App Router, SSR, ISR, next/image, next/font, streaming, or server components -->
Agent definition
nextjs-optimization.mdNext.js Performance Optimization Reference
<!-- Loaded by performance-optimization-engineer when task involves Next.js, App Router, SSR, ISR, next/image, next/font, streaming, or server components -->
> **Scope**: Next.js-specific performance patterns for App Router (13.4+). Pages Router patterns are noted where they differ. > **Version range**: Next.js 13.4+ (App Router stable); Next.js 14+ (Server Actions, partial prerendering) > **Generated**: 2026-04-09
---
Overview
Next.js App Router introduces React Server Components, streaming, and partial prerendering — all of which change the performance optimization playbook. The most common failure mode is applying Pages Router patterns to App Router: using `getServerSideProps` mental models with Server Components, or blocking streaming with synchronous data fetching in layout files.
---
Pattern Table
| Pattern | API | Version | Pages Router Equivalent | |---------|-----|---------|-------------------------| | Route-level caching | `export const revalidate = 60` | Next.js 13.4+ | `getStaticProps` revalidate | | Opt out of caching | `export const dynamic = 'force-dynamic'` | Next.js 13.4+ | `getServerSideProps` | | Parallel data fetching | Multiple `await` in Server Component | Next.js 13.4+ | `getServerSideProps` with `Promise.all` | | Streaming UI | `<Suspense>` around Server Components | Next.js 13.4+ | No equivalent (client-only) | | Preconnect/DNS prefetch | `<link rel="preconnect">` in `<head>` | All versions | `next/head` | | Optimized images | `next/image` with `sizes` prop | Next.js 13+ | Same | | Font optimization | `next/font` | Next.js 13+ | `@next/font` (deprecated) |
---
Correct Patterns
Parallel Data Fetching in Server Components
Fetch data in parallel, not sequentially. Each `await` in series adds to LCP.
// app/dashboard/page.tsx
async function DashboardPage() {
// BAD: Sequential — each fetch waits for the previous
// const user = await getUser()
// const posts = await getPosts(user.id)
// const analytics = await getAnalytics()
// GOOD: Parallel — all fetches start simultaneously
const [user, posts, analytics] = await Promise.all([
getUser(),
getPosts(),
getAnalytics(),
])
return <Dashboard user={user} posts={posts} analytics={analytics} />
}**Why**: Sequential fetches in a single Server Component are serialized on the server — a 3-request chain of 200ms each = 600ms total. Parallel fetches = 200ms total. This directly impacts TTFB and LCP.
---
Streaming with Suspense for Slow Data
Use Suspense to stream fast content immediately while slow data loads.
// app/dashboard/page.tsx
import { Suspense } from 'react'
import { AnalyticsSkeleton } from './skeletons'
export default function DashboardPage() {
return (
<main>
{/* Fast: renders immediately from cache */}
<UserProfile />
{/* Slow: streams in when ready, shows skeleton until then */}
<Suspense fallback={<AnalyticsSkeleton />}>
<AnalyticsPanel /> {/* Server Component with slow DB query */}
</Suspense>
</main>
)
}**Why**: Without Suspense, the entire page waits for the slowest data source. With Suspense, React streams HTML progressively — users see content faster and LCP improves because the above-fold content doesn't block on slow queries.
---
next/image with Proper `sizes` Attribute
Always provide `sizes` to prevent downloading oversized images.
import Image from 'next/image'
// BAD: Missing sizes — Next.js downloads the largest srcset variant
<Image src="/hero.jpg" width={800} height={600} alt="Hero" />
// GOOD: sizes tells browser which breakpoint variant to download
<Image
src="/hero.jpg"
width={800}
height={600}
alt="Hero"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px"
priority // Add for LCP image — disables lazy loading
/>**Why**: Without `sizes`, the browser downloads the image at full display width regardless of viewport. A 1600px image downloaded on a 375px mobile viewport is a 4-5x image size regression. The `priority` prop skips lazy loading for LCP candidates — critical for images above the fold.
---
next/font for Zero Layout Shift
Use `next/font` instead of `@import` in CSS to eliminate font-related CLS.
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
// Preload reduces FOUT; variable improves load efficiency
variable: '--font-inter',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
)
}**Why**: `@import url('...')` in CSS blocks rendering. `next/font` downloads fonts at build time, serves them from the same domain (no DNS lookup), and automatically inlines the font-face declaration — eliminating the layout shift from font swapping that contributes to CLS.
---
Route Segment Config for Caching Control
Use route segment config to set cache behavior per page — not global middleware.
// app/dashboard/page.tsx
// Static: cached at build time, revalidated every 60s (ISR)
export const revalidate = 60
// Dynamic: no caching, runs on every request (like getServerSideProps)
export const dynamic = 'force-dynamic'
// Static: cached permanently until manually revalidated
export const revalidate = false // or: export const dynamic = 'force-static'
**Why**: Overusing `force-dynamic` bypasses the Next.js data cache and kills performance. Profile which routes actually need fresh data (user-specific, real-time) vs. can be cached (product pages, blog posts). Misconfigured `dynamic` is one of the most common causes of unexpectedly slow Next.js apps.
---
Pattern Catalog
Stream Slow Data with Suspense Boundaries
**Detection**:
grep -rn "async function.*Layout\|async.*layout" --include="*.tsx" --include="*.ts" app/
rg "a
Read more
Next.js Performance Optimization Reference
<!-- Loaded by performance-optimization-engineer when task involves Next.js, App Router, SSR, ISR, next/image, next/font, streaming, or server components -->
> **Scope**: Next.js-specific performance patterns for App Router (13.4+). Pages Router patterns are noted where they differ. > **Version range**: Next.js 13.4+ (App Router stable); Next.js 14+ (Server Actions, partial prerendering) > **Generated**: 2026-04-09
---
Overview
Next.js App Router introduces React Server Components, streaming, and partial prerendering — all of which change the performance optimization playbook. The most common failure mode is applying Pages Router patterns to App Router: using `getServerSideProps` mental models with Server Components, or blocking streaming with synchronous data fetching in layout files.
---
Pattern Table
| Pattern | API | Version | Pages Router Equivalent | |---------|-----|---------|-------------------------| | Route-level caching | `export const revalidate = 60` | Next.js 13.4+ | `getStaticProps` revalidate | | Opt out of caching | `export const dynamic = 'force-dynamic'` | Next.js 13.4+ | `getServerSideProps` | | Parallel data fetching | Multiple `await` in Server Component | Next.js 13.4+ | `getServerSideProps` with `Promise.all` | | Streaming UI | `<Suspense>` around Server Components | Next.js 13.4+ | No equivalent (client-only) | | Preconnect/DNS prefetch | `<link rel="preconnect">` in `<head>` | All versions | `next/head` | | Optimized images | `next/image` with `sizes` prop | Next.js 13+ | Same | | Font optimization | `next/font` | Next.js 13+ | `@next/font` (deprecated) |
---
Correct Patterns
Parallel Data Fetching in Server Components
Fetch data in parallel, not sequentially. Each `await` in series adds to LCP.
// app/dashboard/page.tsx
async function DashboardPage() {
// BAD: Sequential — each fetch waits for the previous
// const user = await getUser()
// const posts = await getPosts(user.id)
// const analytics = await getAnalytics()
// GOOD: Parallel — all fetches start simultaneously
const [user, posts, analytics] = await Promise.all([
getUser(),
getPosts(),
getAnalytics(),
])
return <Dashboard user={user} posts={posts} analytics={analytics} />
}**Why**: Sequential fetches in a single Server Component are serialized on the server — a 3-request chain of 200ms each = 600ms total. Parallel fetches = 200ms total. This directly impacts TTFB and LCP.
---
Streaming with Suspense for Slow Data
Use Suspense to stream fast content immediately while slow data loads.
// app/dashboard/page.tsx
import { Suspense } from 'react'
import { AnalyticsSkeleton } from './skeletons'
export default function DashboardPage() {
return (
<main>
{/* Fast: renders immediately from cache */}
<UserProfile />
{/* Slow: streams in when ready, shows skeleton until then */}
<Suspense fallback={<AnalyticsSkeleton />}>
<AnalyticsPanel /> {/* Server Component with slow DB query */}
</Suspense>
</main>
)
}**Why**: Without Suspense, the entire page waits for the slowest data source. With Suspense, React streams HTML progressively — users see content faster and LCP improves because the above-fold content doesn't block on slow queries.
---
next/image with Proper `sizes` Attribute
Always provide `sizes` to prevent downloading oversized images.
import Image from 'next/image'
// BAD: Missing sizes — Next.js downloads the largest srcset variant
<Image src="/hero.jpg" width={800} height={600} alt="Hero" />
// GOOD: sizes tells browser which breakpoint variant to download
<Image
src="/hero.jpg"
width={800}
height={600}
alt="Hero"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px"
priority // Add for LCP image — disables lazy loading
/>**Why**: Without `sizes`, the browser downloads the image at full display width regardless of viewport. A 1600px image downloaded on a 375px mobile viewport is a 4-5x image size regression. The `priority` prop skips lazy loading for LCP candidates — critical for images above the fold.
---
next/font for Zero Layout Shift
Use `next/font` instead of `@import` in CSS to eliminate font-related CLS.
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
// Preload reduces FOUT; variable improves load efficiency
variable: '--font-inter',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
)
}**Why**: `@import url('...')` in CSS blocks rendering. `next/font` downloads fonts at build time, serves them from the same domain (no DNS lookup), and automatically inlines the font-face declaration — eliminating the layout shift from font swapping that contributes to CLS.
---
Route Segment Config for Caching Control
Use route segment config to set cache behavior per page — not global middleware.
// app/dashboard/page.tsx // Static: cached at build time, revalidated every 60s (ISR) export const revalidate = 60 // Dynamic: no caching, runs on every request (like getServerSideProps) export const dynamic = 'force-dynamic' // Static: cached permanently until manually revalidated export const revalidate = false // or: export const dynamic = 'force-static'
**Why**: Overusing `force-dynamic` bypasses the Next.js data cache and kills performance. Profile which routes actually need fresh data (user-specific, real-time) vs. can be cached (product pages, blog posts). Misconfigured `dynamic` is one of the most common causes of unexpectedly slow Next.js apps.
---
Pattern Catalog
Stream Slow Data with Suspense Boundaries
**Detection**:
grep -rn "async function.*Layout\|async.*layout" --include="*.tsx" --include="*.ts" app/ rg "a
Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.
Repo: notque/vexjoy-agent
Other agents on vexjoy-agent.
- ansible-automation-engineer
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
Open agent - modules
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) **Generated**: 2026-04-04 — verify against current Ansible
Open agent - testing
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation
Open agent - base-instructions
Universal operational rules injected by /do at agent dispatch. Domain-specific rules live in each agent's .md file.
Open agent - communication-patterns
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix each. **Version range**: all versions **Generated**: 2026-05-11
Open agent - combat-effects-upgrade
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.
Open agent

