--- name: building-nextjs-apps description: Specialized skill for building Next.js 15 App Router applications with React Server Components, Server Actions, and production-ready patterns. Use when implementing Next.js features, components, or application structure. --- # Building Next.js Apps You are an expert in building production-ready Next.js 15 applications using the App Router with opinionated best practices. ## Enforced Patterns ### App Router Only - NEVER use Pages Router - Use App Router features: layouts, loading, error, not-found - Leverage nested layouts for shared UI - Use route groups for organization (no URL impact) ### Server Components First Default to Server Components. Only use Client Components when you need: - Interactivity (event handlers: onClick, onChange, etc.) - Browser-only APIs (localStorage, window, document) - React hooks (useState, useEffect, useReducer, etc.) - Third-party libraries that require client-side rendering ### Data Fetching **Server Components** (Preferred): ```typescript // app/posts/page.tsx import { getPosts } from '@/lib/data'; export default async function PostsPage() { const posts = await getPosts(); // Direct async call return (
{posts.map(post => (

{post.title}

{post.content}

))}
); } ``` **Client Components** (When needed): ```typescript // components/posts-list.tsx 'use client'; import { useEffect, useState } from 'react'; export function PostsList() { const [posts, setPosts] = useState([]); useEffect(() => { fetch('/api/posts') .then(res => res.json()) .then(setPosts); }, []); return
{/* render posts */}
; } ``` ### Mutations with Server Actions **Form Actions** (Preferred): ```typescript // app/actions.ts 'use server'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import { z } from 'zod'; const CreatePostSchema = z.object({ title: z.string().min(1, 'Title required'), content: z.string().min(1, 'Content required'), }); export async function createPost(formData: FormData) { const validated = CreatePostSchema.parse({ title: formData.get('title'), content: formData.get('content'), }); // Write to database const postId = await db.createPost(validated); revalidatePath('/posts'); redirect(`/posts/${postId}`); } ``` ```typescript // app/posts/new/page.tsx import { createPost } from '@/app/actions'; export default function NewPostPage() { return (