← Back to list

nextjs-app-router
by DW225
Retro board build using Turso, Next.js and Supabase Auth
⭐ 1🍴 1📅 Jan 23, 2026
SKILL.md
name: nextjs-app-router description: Next.js 16 App Router patterns including server components, client components, server actions, route handlers, layouts, metadata API, dynamic routes, file conventions, data fetching, caching strategies, and Next.js best practices for building modern React applications model: sonnet
Next.js App Router Patterns
When to Use This Skill
Activate this skill when working on:
- Creating new pages, routes, or layouts
- Implementing server or client components
- Building server actions for data mutations
- Setting up route handlers (API endpoints)
- Configuring metadata and SEO
- Implementing dynamic routes
- Organizing app directory structure
- Data fetching and caching strategies
Core Patterns
Server vs Client Components Decision Tree
Use Server Components (default) when:
- Fetching data from databases or APIs
- Accessing backend resources directly
- Keeping sensitive information secure (API keys, tokens)
- Reducing client-side JavaScript bundle
- No interactivity required
Use Client Components ('use client') when:
- Using React hooks (
useState,useEffect,useContext) - Handling browser events (onClick, onChange)
- Using browser-only APIs (localStorage, window)
- Using third-party libraries that depend on React hooks
- Implementing real-time features with Ably
Example:
// app/board/[id]/page.tsx - Server Component (default)
import { getBoard } from "@/lib/actions/board/getBoard";
export default async function BoardPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const board = await getBoard(id);
return <BoardView board={board} />;
}
// components/board/BoardView.tsx - Client Component
"use client";
import { useState } from "react";
import { PostProvider } from "@/components/board/PostProvider";
export function BoardView({ board }) {
const [filter, setFilter] = useState("all");
return <PostProvider boardId={board.id}>{/* Interactive UI */}</PostProvider>;
}
Server Actions with Authentication
CRITICAL: All server actions MUST use actionWithAuth or rbacWithAuth wrappers (see rbac-security skill).
Pattern:
// lib/actions/post/createPost.ts
"use server";
import { rbacWithAuth } from "@/lib/actions/actionWithAuth";
import { db } from "@/db";
import { postTable } from "@/db/schema";
export const createPost = async (
boardId: string,
content: string,
type: PostType
) =>
rbacWithAuth(boardId, async (userId) => {
const post = await db
.insert(postTable)
.values({
id: nanoid(),
boardId,
userId,
content,
type,
createdAt: new Date(),
})
.returning();
return post[0];
});
Usage in Client Components:
"use client";
import { createPost } from "@/lib/actions/post/createPost";
import { useTransition } from "react";
export function CreatePostForm({ boardId }) {
const [isPending, startTransition] = useTransition();
const handleSubmit = async (formData: FormData) => {
startTransition(async () => {
await createPost(boardId, formData.get("content") as string, "went_well");
});
};
return <form action={handleSubmit}>...</form>;
}
File-Based Routing Conventions
Special Files:
page.tsx- Unique UI for a routelayout.tsx- Shared UI for segments and childrenloading.tsx- Loading UI (Suspense boundary)error.tsx- Error UI (Error boundary)not-found.tsx- Not found UIroute.ts- API endpoint (Route Handler)
Route Organization:
app/
├── layout.tsx # Root layout
├── page.tsx # Home page
├── board/
│ ├── layout.tsx # Board layout
│ ├── page.tsx # Board list
│ └── [id]/
│ ├── page.tsx # Board detail (dynamic route)
│ ├── loading.tsx # Loading state
│ └── error.tsx # Error handling
└── api/
└── webhooks/
└── route.ts # API route handler
Metadata API for SEO
Static Metadata:
// app/board/[id]/page.tsx
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Board Details",
description: "View and manage your retrospective board",
};
Dynamic Metadata:
// app/board/[id]/page.tsx
import { getBoard } from "@/lib/actions/board/getBoard";
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>;
}): Promise<Metadata> {
const { id } = await params;
const board = await getBoard(id);
return {
title: `${board.name} - Ree Board`,
description: board.description,
openGraph: {
title: board.name,
description: board.description,
},
};
}
Dynamic Imports for Code Splitting
Lazy Load Heavy Components:
import dynamic from "next/dynamic";
// Drag-and-drop is lazy loaded in the project
const PostDragDrop = dynamic(() => import("@/components/board/PostDragDrop"), {
ssr: false,
loading: () => <LoadingSkeleton />,
});
Anti-Patterns
❌ Using 'use client' at the Top Level Unnecessarily
Bad:
"use client"; // ❌ Unnecessary - no hooks or interactivity
export default function Page() {
return <div>Static content</div>;
}
Good:
// ✅ Server component by default
export default function Page() {
return <div>Static content</div>;
}
❌ Fetching Data in Client Components
Bad:
"use client";
export default function Page() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("/api/data")
.then((r) => r.json())
.then(setData); // ❌
}, []);
}
Good:
// ✅ Server component fetches data
export default async function Page() {
const data = await getData();
return <ClientView data={data} />;
}
❌ Server Actions Without Authentication
Bad:
"use server";
export async function deleteBoard(id: string) {
// ❌ No auth check - security vulnerability
await db.delete(boardTable).where(eq(boardTable.id, id));
}
Good:
"use server";
export const deleteBoard = async (id: string) =>
rbacWithAuth(id, async (userId) => {
// ✅ Authentication and RBAC enforced
await db.delete(boardTable).where(eq(boardTable.id, id));
});
❌ Not Using Suspense Boundaries
Bad:
export default async function Page() {
const data = await slowDataFetch(); // ❌ Blocks entire page
return <div>{data}</div>;
}
Good:
import { Suspense } from "react";
export default function Page() {
return (
<Suspense fallback={<Loading />}>
<DataComponent />
</Suspense>
);
}
async function DataComponent() {
const data = await slowDataFetch();
return <div>{data}</div>;
}
Integration with Other Skills
- rbac-security: Server actions require authentication wrappers
- drizzle-patterns: Data fetching in server components
- signal-state-management: Client-side state in 'use client' components
- ably-realtime: Real-time features require client components
- testing-patterns: Test both server and client components
Project-Specific Context
Key Files
app/- Next.js App Router directoryapp/layout.tsx- Root layout with providersapp/board/[id]/page.tsx- Dynamic board pagelib/actions/- Server actions by domainproxy.ts- Supabase authentication proxy (Next.js 16)next.config.js- Next.js configuration
Project Conventions
- Server actions live in
lib/actions/[entity]/ - All server actions use
actionWithAuthorrbacWithAuth - Client components prefixed with 'use client'
- Heavy components lazy-loaded (drag-and-drop)
- Metadata configured for all public pages
Environment-Specific Behavior
- Development: Local Turso database
- Production: Turso Cloud with auth tokens
- Both: Supabase authentication required
Last Updated: 2026-01-10
Score
Total Score
50/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon