Next.js App Router changed the mental model for React applications. Server Components, streaming, parallel routes, intercepting routes — the surface area is huge and AI tools sometimes generate Pages Router patterns by mistake. These recipes produce App Router code exclusively, with correct server/client boundaries and proper data patterns.
Server Component and Server Action recipes with correct boundaries
Data fetching patterns using RSC, streaming, and Suspense
Authentication and middleware recipes for App Router
Performance optimization prompts for Core Web Vitals
Scenario: Your dashboard page makes 4 independent API calls. They all block rendering until the slowest one resolves.
Tip
Refactor app/dashboard/page.tsx to use streaming with Suspense boundaries. The page is a Server Component (no “use client”). It makes 4 independent data fetches: getStats(), getRecentOrders(), getTopProducts(), getActivityFeed(). Wrap each data-dependent section in its own <Suspense fallback={<SectionSkeleton />}> boundary. Create an async component for each section that fetches its own data: StatsSection, RecentOrdersSection, TopProductsSection, ActivityFeedSection. The page shell renders immediately. Each section streams in as its data resolves. Do NOT use Promise.all — each section is independent. Create matching skeleton components that match final layout dimensions to prevent layout shift. Add proper metadata using the generateMetadata export.
Expected output: Page with 4 Suspense boundaries, 4 async section components, 4 skeletons, and metadata.
Scenario: You need a contact form that validates server-side, handles errors, and shows pending state without a separate API route.
Tip
Create a contact form with Server Actions. (1) app/contact/actions.ts with “use server”: submitContactForm action accepting FormData, validating with Zod (name required, email valid, message 10-1000 chars, honeypot empty), saving to database, sending email via Resend, returning success or field-level errors. (2) app/contact/page.tsx as Server Component containing the form. (3) app/contact/contact-form.tsx as Client Component using useFormState for the action and field errors, useFormStatus for submit button state, progressive enhancement so the form works without JavaScript. (4) After success, show message and reset form. (5) Rate limit: 5 submissions per hour per IP using KV store. Test with valid data, validation errors, and rate limit exceeded.
Expected output: Server action, page component, client form component, and tests.
Scenario: You need email/password and OAuth authentication with session management and protected routes.
Tip
Set up NextAuth.js v5 with App Router. (1) Create auth.ts with NextAuth() configuration: Credentials provider (email/password with bcrypt), Google, GitHub. Drizzle adapter for DB sessions. JWT with 30-day maxAge. Callbacks: jwt includes userId and role, session exposes them. (2) middleware.ts protecting /dashboard/, /admin/ , /api/* except /api/auth/*. (3) Route handler at app/api/auth/[...nextauth]/route.ts. (4) Typed useSession wrapper in lib/auth-client.ts. (5) LoginForm Client Component with email/password and OAuth buttons. (6) UserMenu with avatar, name, sign-out. (7) withAuth(Component, { role? }) HOC for server-side auth checking. Test login flow, session persistence, protected redirects, and role-based access.
Expected output: Auth config, middleware, route handler, client utilities, components, HOC, and tests.
Scenario: Product details should open in a modal from the list but show a full page when accessed directly.
Tip
Implement intercepting routes for a product modal. (1) app/products/page.tsx — Server Component listing products in a grid linking to /products/[id]. (2) app/products/[id]/page.tsx — full detail page for direct access. (3) app/products/@modal/(.)products/[id]/page.tsx — intercepting route rendering the same data inside a modal. (4) app/products/layout.tsx rendering children and modal as parallel slots. (5) ProductModal.tsx Client Component with backdrop click to close (router.back()), Escape key, focus trapping, and “View full page” link. (6) Handle navigation correctly: router.back() returns to list, browser back closes modal, refresh shows full page. Test all navigation scenarios.
Expected output: Product pages, intercepting route, layout with parallel slots, modal component, and tests.
Scenario: You need low-latency API routes running on the edge with typing, validation, and caching.
Tip
Create API route handlers in app/api/products/. (1) route.ts with GET (list with pagination, filtering, sorting) and POST (create with Zod validation). (2) [id]/route.ts with GET, PUT, DELETE (soft-delete). (3) Add export const runtime = 'edge'. (4) Create lib/api-utils.ts with helpers: validateBody<T>(schema, request), paginatedResponse(data, total, page, pageSize), apiError(status, message, details?). (5) Add auth checking via auth() from NextAuth. (6) GET requests return Cache-Control with stale-while-revalidate. POST/PUT/DELETE purge cache tags with revalidateTag(). (7) Add JSDoc @swagger comments. Test each endpoint with valid requests, validation errors, auth failures, and not-found.
Expected output: Route handlers, API utility library, and tests for each endpoint.
Scenario: Your blog has 500 posts. Full static generation takes 10 minutes. You need ISR.
Tip
Implement ISR. (1) In app/blog/page.tsx, fetch with { next: { revalidate: 3600 } } for 1-hour cache. (2) In app/blog/[slug]/page.tsx, use generateStaticParams() for the 50 most recent posts. Set dynamicParams = true and revalidate = 86400. (3) Create app/api/revalidate/route.ts webhook handler calling revalidatePath and revalidateTag when CMS publishes. (4) Add fetch tags: { next: { tags: ['post-' + slug, 'blog-posts'] } }. (5) Generate sitemap at app/sitemap.ts using MetadataRoute.Sitemap. (6) Add opengraph-image.tsx for dynamic OG images using ImageResponse from next/og. Test that revalidation webhook triggers cache purge.
Expected output: Blog pages with ISR, revalidation webhook, sitemap, OG image generation, and tests.
Scenario: Split traffic between landing page variants and gate features behind flags at the edge.
Tip
Create middleware.ts for A/B testing and feature flags. (1) A/B: check for ab-variant cookie, assign randomly if missing, set for 30 days. Rewrite /pricing to /pricing/variant-a or variant-b invisibly. (2) Feature flags: fetch from KV keyed by user/anonymous ID, set as x-feature-flags request header for Server Components. (3) Geolocation: read request.geo, redirect / to /en or /es respecting language cookie. (4) Bot detection: check User-Agent, set x-is-bot header for skipping expensive personalizations. (5) Create composable middleware functions in lib/middleware-utils.ts. (6) Add config.matcher to skip static assets. Test each behavior with mocked NextRequest.
Expected output: Middleware, utility functions, variant directories, and tests.
Scenario: Every page needs unique meta tags, dynamic OG images, and JSON-LD structured data.
Tip
Create a metadata system. (1) In app/layout.tsx, set default metadata: title, description, OG image, Twitter card, robots. (2) Create lib/metadata.ts with generatePageMetadata(options) returning Metadata with template title, description, canonical URL, OG image, alternates, and JSON-LD. (3) For blog: generateMetadata fetching post, returning title, excerpt description, dynamic OG image from opengraph-image.tsx (Satori-rendered), article structured data. (4) For products: Product structured data with price, availability, rating. (5) Create app/robots.ts and app/sitemap.ts. (6) Verify no duplicate tags, proper canonicals, valid JSON-LD.
Expected output: Metadata utility, OG image generation, JSON-LD helpers, robots.ts, sitemap.ts, and tests.
Scenario: Lighthouse performance score is 65. LCP 4.2s, CLS 0.25, FID 180ms.
Tip
Fix Core Web Vitals. (1) LCP: Convert hero images to next/image with priority, explicit dimensions, sizes attribute. Preload LCP image in metadata. (2) CLS: Add explicit dimensions to all images/embeds. Use Suspense skeletons matching final dimensions. Fix fonts with next/font display:swap and preload. (3) INP: Move filtering/sorting to useDeferredValue or useTransition. Split large Client Components with dynamic imports and ssr:false. (4) General: Enable PPR if available. Use Script strategy=“lazyOnload” for analytics. Configure images: { formats: ['image/avif', 'image/webp'] }. (5) Set up web-vitals reporting. Add Lighthouse CI in GitHub Actions with budgets: LCP < 2.5s, CLS < 0.1, INP < 200ms.
Expected output: Optimized components, config changes, font setup, Lighthouse CI workflow, and metrics.
Scenario: Your SaaS serves multiple tenants from a single deployment with subdomain-based routing and data isolation.
Tip
Implement multi-tenancy. (1) In middleware.ts, extract tenant from subdomain, validate in KV, set as x-tenant-id header. (2) Create lib/tenant.ts with getTenant() reading the header in Server Components. (3) Create TenantProvider Client Component providing tenant context (name, theme, logo, features). (4) In database layer, every query includes WHERE tenant_id. Create scopedQuery(tenantId) wrapper for Drizzle. (5) Tenant-specific theming via CSS variables set in root layout. (6) Handle missing tenant subdomain with branded 404. Handle www and naked domain as marketing site. Test tenant isolation: queries never return other tenants’ data.
Expected output: Middleware, tenant utility, provider, scoped queries, themed layout, and isolation tests.
Scenario: Your dashboard needs user-installed plugins that add pages, API routes, and UI components.
Tip
Build a plugin system. (1) Define Plugin interface: id, name, version, routes, apiRoutes, sidebarItems, settingsComponent. (2) Create PluginRegistry loading plugins from database. (3) Catch-all route app/plugins/[pluginId]/[...path]/page.tsx dynamically loading plugin components. (4) API catch-all app/api/plugins/[pluginId]/[...path]/route.ts delegating to plugin handlers. (5) Sidebar reads active plugins and injects items. (6) Installation flow: upload bundle, validate manifest, store, register. (7) Sandbox: error boundary per plugin, scoped API access. Test loading, delegation, and isolation.
Expected output: Plugin interface, registry, catch-all routes, sidebar integration, installation flow, and tests.
Scenario: Your 20 API endpoints have types that are always out of sync with the frontend.
Tip
Set up tRPC v11 with App Router. (1) server/trpc.ts with context (session, database), router, middleware (isAuthed, isAdmin). (2) server/routers/ with users, posts (cursor pagination), comments sub-routers. Each procedure has Zod input validation. (3) app/api/trpc/[trpc]/route.ts handler. (4) lib/trpc-client.ts with typed client. (5) lib/trpc-react.ts with @trpc/react-query integration. (6) In components, use trpc.posts.list.useQuery and trpc.posts.create.useMutation — fully typed from router with zero manual type definitions on client. (7) Add subscriptions for real-time. Test: changing router output type causes TypeScript errors in components.
Expected output: tRPC setup, routers, handler, typed client, React Query integration, and type-safety verification.