Next.js全栈模式 nextjs-fullstack-patterns

这个技能专注于使用Next.js框架构建生产级全栈应用程序的最佳实践和模式,包括App Router、服务器组件、数据获取、认证、部署等。关键词:Next.js, 全栈开发, React, App Router, 服务器组件, 数据获取, 认证, 部署, SEO, 性能优化。

前端开发 0 次安装 0 次浏览 更新于 3/7/2026

name: nextjs-fullstack-patterns description: 使用App Router模式、服务器组件、数据获取策略、认证和部署构建生产级Next.js应用程序。涵盖从数据库到UI的全栈。触发于Next.js开发、React服务器组件、App Router或全栈TypeScript请求。 license: MIT

Next.js全栈模式

现代Next.js应用程序的生产模式。

App Router基础

基于文件的路由

app/
├── layout.tsx          # 根布局(必需)
├── page.tsx            # 主页(/)
├── loading.tsx         # 加载UI
├── error.tsx           # 错误边界
├── not-found.tsx       # 404页面
├── globals.css         # 全局样式
│
├── (marketing)/        # 路由组(不影响URL)
│   ├── about/
│   │   └── page.tsx    # /about
│   └── blog/
│       └── page.tsx    # /blog
│
├── dashboard/
│   ├── layout.tsx      # 仪表板布局
│   ├── page.tsx        # /dashboard
│   └── settings/
│       └── page.tsx    # /dashboard/settings
│
├── api/
│   └── route.ts        # API路由(/api)
│
└── [slug]/
    └── page.tsx        # 动态路由

路由约定

文件 用途
page.tsx 路由的唯一UI
layout.tsx 共享UI,保留状态
template.tsx 类似布局但重新挂载
loading.tsx 加载UI(Suspense)
error.tsx 错误边界
not-found.tsx 404 UI
route.ts API端点

服务器与客户端组件

默认:服务器组件

// app/posts/page.tsx(默认为服务器组件)
async function PostsPage() {
  const posts = await db.posts.findMany(); // 直接数据库访问
  
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

export default PostsPage;

客户端组件

'use client'; // 必需指令

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(c => c + 1)}>
      计数: {count}
    </button>
  );
}

使用时机

服务器组件 客户端组件
数据获取 事件处理程序(如onClick)
访问后端资源 useState, useEffect
敏感数据(令牌、密钥) 浏览器API
大型依赖 交互性
SEO关键内容 实时更新

组合模式

// 带有客户端岛屿的服务器组件
import { Counter } from './counter'; // 客户端组件

async function Page() {
  const data = await fetchData(); // 服务器端
  
  return (
    <div>
      <h1>{data.title}</h1>
      <Counter /> {/* 客户端岛屿 */}
    </div>
  );
}

数据获取

服务器组件获取

// 在组件中直接使用async/await
async function Page() {
  const data = await fetch('https://api.example.com/data', {
    cache: 'force-cache',     // 默认:缓存
    // cache: 'no-store',     // 无缓存(动态)
    // next: { revalidate: 60 } // ISR:每60秒重新验证
  });
  
  return <div>{data.title}</div>;
}

并行数据获取

async function Page() {
  // 并行获取(不要顺序await)
  const [posts, comments] = await Promise.all([
    getPosts(),
    getComments(),
  ]);
  
  return (
    <>
      <Posts data={posts} />
      <Comments data={comments} />
    </>
  );
}

使用Suspense的流式传输

import { Suspense } from 'react';

async function Page() {
  return (
    <div>
      <h1>仪表板</h1>
      
      {/* 准备就绪时流式传输 */}
      <Suspense fallback={<Loading />}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}

服务器操作

表单操作

// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  
  await db.posts.create({ data: { title } });
  
  revalidatePath('/posts');
  redirect('/posts');
}
// app/posts/new/page.tsx
import { createPost } from '../actions';

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <button type="submit">创建</button>
    </form>
  );
}

使用useFormStatus

'use client';

import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending } = useFormStatus();
  
  return (
    <button type="submit" disabled={pending}>
      {pending ? '保存中...' : '保存'}
    </button>
  );
}

使用useActionState

'use client';

import { useActionState } from 'react';
import { createPost } from './actions';

export function PostForm() {
  const [state, action, pending] = useActionState(createPost, null);
  
  return (
    <form action={action}>
      <input name="title" />
      {state?.error && <p>{state.error}</p>}
      <button disabled={pending}>创建</button>
    </form>
  );
}

API路由

路由处理器

// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const posts = await db.posts.findMany();
  return NextResponse.json(posts);
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  const post = await db.posts.create({ data: body });
  return NextResponse.json(post, { status: 201 });
}

动态路由处理器

// app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const post = await db.posts.findUnique({ 
    where: { id: params.id } 
  });
  
  if (!post) {
    return NextResponse.json(
      { error: '未找到' }, 
      { status: 404 }
    );
  }
  
  return NextResponse.json(post);
}

认证模式

中间件认证

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('token'); <!-- allow-secret -->
  
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*'],
};

认证上下文模式

// lib/auth.ts
import { cookies } from 'next/headers';
import { cache } from 'react';

export const getUser = cache(async () => {
  const token = cookies().get('token')?.value; <!-- allow-secret -->
  if (!token) return null;
  
  // 验证令牌,获取用户
  return await validateAndFetchUser(token);
});
// app/dashboard/page.tsx
import { getUser } from '@/lib/auth';
import { redirect } from 'next/navigation';

export default async function Dashboard() {
  const user = await getUser();
  
  if (!user) {
    redirect('/login');
  }
  
  return <div>欢迎, {user.name}</div>;
}

数据库模式

Prisma设置

// lib/db.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const db = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = db;
}

数据访问层

// lib/data/posts.ts
import { db } from '@/lib/db';
import { cache } from 'react';

export const getPosts = cache(async () => {
  return db.post.findMany({
    orderBy: { createdAt: 'desc' },
    include: { author: true },
  });
});

export const getPost = cache(async (id: string) => {
  return db.post.findUnique({
    where: { id },
    include: { author: true },
  });
});

错误处理

错误边界

'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>出错了!</h2>
      <button onClick={() => reset()}>重试</button>
    </div>
  );
}

未找到

// app/posts/[id]/page.tsx
import { notFound } from 'next/navigation';

export default async function Post({ params }: { params: { id: string } }) {
  const post = await getPost(params.id);
  
  if (!post) {
    notFound();
  }
  
  return <article>{post.content}</article>;
}

元数据与SEO

静态元数据

// app/about/page.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: '关于我们',
  description: '了解我们的公司',
};

动态元数据

// app/posts/[id]/page.tsx
import type { Metadata } from 'next';

export async function generateMetadata({ 
  params 
}: { 
  params: { id: string } 
}): Promise<Metadata> {
  const post = await getPost(params.id);
  
  return {
    title: post?.title,
    description: post?.excerpt,
  };
}

项目结构

├── app/                  # App Router
├── components/
│   ├── ui/              # 可重用UI组件
│   └── features/        # 功能特定组件
├── lib/
│   ├── db.ts            # 数据库客户端
│   ├── auth.ts          # 认证工具
│   └── utils.ts         # 通用工具
├── actions/             # 服务器操作
├── types/               # TypeScript类型
├── hooks/               # 自定义React钩子
└── public/              # 静态资产

参考资料

  • references/deployment-checklist.md - 生产部署清单
  • references/performance-patterns.md - 优化技术
  • references/testing-patterns.md - 测试Next.js应用