搜索

PythonReactSEOPDF
GitHub Skill

nextjs-app-router

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

DW225☆ 1 仓库 Stars1 仓库 Forks

安装与使用

请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。

查看来源 ↗使用指南

技能说明

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 route
  • layout.tsx - Shared UI for segments and children
  • loading.tsx - Loading UI (Suspense boundary)
  • error.tsx - Error UI (Error boundary)
  • not-found.tsx - Not found UI
  • route.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

Project-Specific Context

Key Files
  • app/ - Next.js App Router directory
  • app/layout.tsx - Root layout with providers
  • app/board/[id]/page.tsx - Dynamic board page
  • lib/actions/ - Server actions by domain
  • proxy.ts - Supabase authentication proxy (Next.js 16)
  • next.config.js - Next.js configuration
Project Conventions
  1. Server actions live in lib/actions/[entity]/
  2. All server actions use actionWithAuth or rbacWithAuth
  3. Client components prefixed with 'use client'
  4. Heavy components lazy-loaded (drag-and-drop)
  5. 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

使用示例

查看来源仓库中的使用示例与配置说明。

查看来源 ↗

来源历史

收录时间
2026-01-20
索引更新时间
2026-01-26
Branch
main

收录与同步日期属于本站记录。源码修改历史请查看来源仓库。

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!