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.
View on GitHubswapkats/robin
robin
January 20, 2026
Select agents to install to:
npx add-skill https://github.com/swapkats/robin/blob/main/skills/building-nextjs-apps/SKILL.md -a claude-code --skill building-nextjs-appsInstallation paths:
.claude/skills/building-nextjs-apps/# 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 (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</article>
))}
</div>
);
}
```
**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 <div>{/* render posts */}</div>;
}
```
### 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'),
co