封面图

Next.js 15 带来了 React Server Components 的成熟实现、增强的 Server Actions 以及对 AI 应用开发的原生支持。本文将从架构原理到实战代码,带你全面掌握 Next.js 15 全栈开发的核心技术栈。

一、App Router 架构深度解析

Next.js 15 的 App Router 基于 React 的文件系统路由,采用嵌套布局(Nested Layout)和并行路由(Parallel Routes)的设计理念。

1.1 目录结构与路由约定

app/
├── layout.tsx          # 根布局
├── page.tsx            # 首页 (/)
├── blog/
│   ├── layout.tsx      # 博客布局
│   ├── page.tsx        # /blog
│   └── [slug]/
│       └── page.tsx    # /blog/:slug
├── api/
│   └── ai/
│       └── chat/
│           └── route.ts  # /api/ai/chat
└── middleware.ts       # 全局中间件

关键约定:page.tsx 定义路由页面,layout.tsx 定义共享布局,route.ts 定义 API 端点,loading.tsx 自动处理 Suspense 加载状态。

1.2 嵌套布局与模板继承

// app/layout.tsx - 根布局
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-CN">
      <body>
        <nav className="navbar">
          <Link href="/">首页</Link>
          <Link href="/blog">博客</Link>
        </nav>
        {children}
      </body>
    </html>
  );
}

// app/blog/layout.tsx - 博客专属布局
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="blog-container">
      <aside>{/* 侧边栏 */}</aside>
      <main>{children}</main>
    </div>
  );
}

布局组件在路由切换时不会重新挂载,状态得以保留。而 template.tsx 则在每次导航时重新创建实例,适合需要重置状态的场景。

二、React Server Components 原理与实战

2.1 服务端组件 vs 客户端组件

React Server Components(RSC)是 React 19 的核心特性,Next.js 15 将其作为默认渲染模式。

// app/blog/page.tsx - 默认为服务端组件(Server Component)
// 直接访问数据库,无需 API 层
import { db } from '@/lib/database';

export default async function BlogPage() {
  const posts = await db.post.findMany({
    orderBy: { createdAt: 'desc' },
    take: 10,
  });

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </li>
      ))}
    </ul>
  );
}
// app/components/Counter.tsx - 客户端组件(Client Component)
'use client';

import { useState } from 'react';

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

2.2 RSC 的性能优势原理

服务端组件的核心理念是将计算推向服务端

// app/dashboard/page.tsx - 利用 Suspense 实现流式加载
import { Suspense } from 'react';
import { UserStats } from './user-stats';
import { RecentActivity } from './recent-activity';

export default function DashboardPage() {
  return (
    <div className="grid grid-cols-2 gap-4">
      <Suspense fallback={<StatsSkeleton />}>
        <UserStats />
      </Suspense>
      <Suspense fallback={<ActivitySkeleton />}>
        <RecentActivity />
      </Suspense>
    </div>
  );
}

2.3 组件组合模式最佳实践

将数据获取逻辑保留在服务端组件,将交互逻辑下沉到客户端组件:

// 服务端组件获取数据,客户端组件处理交互
import { LikeButton } from './like-button';

async function PostDetail({ slug }: { slug: string }) {
  const post = await getPost(slug); // 服务端直接查库

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML= />
      <LikeButton postId={post.id} initialLikes={post.likes} />
    </article>
  );
}

三、Server Actions 表单处理

3.1 基础 Server Action

Server Actions 允许你在组件中直接定义服务端函数,彻底简化了表单提交和数据变更的流程:

// app/actions/post.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).max(200),
  content: z.string().min(10),
  tags: z.array(z.string()).max(5),
});

export async function createPost(formData: FormData) {
  const validated = CreatePostSchema.parse({
    title: formData.get('title'),
    content: formData.get('content'),
    tags: formData.getAll('tags'),
  });

  await db.post.create({ data: validated });
  revalidatePath('/blog');
  redirect('/blog');
}

3.2 useActionState 与表单状态管理

Next.js 15 引入了 useActionState,提供更优雅的表单状态处理:

// app/components/CreatePostForm.tsx
'use client';

import { useActionState } from 'react';
import { createPost } from '@/app/actions/post';

type State = {
  errors?: {
    title?: string[];
    content?: string[];
  };
  message?: string;
  success?: boolean;
};

export function CreatePostForm() {
  const [state, formAction, isPending] = useActionState<State, FormData>(
    async (prevState, formData) => {
      try {
        await createPost(formData);
        return { success: true, message: '文章发布成功!' };
      } catch (error) {
        if (error instanceof z.ZodError) {
          return { errors: error.flatten().fieldErrors };
        }
        return { message: '发布失败,请重试' };
      }
    },
    {}
  );

  return (
    <form action={formAction}>
      <input name="title" placeholder="文章标题" />
      {state.errors?.title && (
        <p className="text-red-500">{state.errors.title[0]}</p>
      )}
      <textarea name="content" placeholder="文章内容" />
      {state.errors?.content && (
        <p className="text-red-500">{state.errors.content[0]}</p>
      )}
      <button type="submit" disabled={isPending}>
        {isPending ? '发布中...' : '发布文章'}
      </button>
      {state.message && (
        <p className={state.success ? 'text-green-500' : 'text-red-500'}>
          {state.message}
        </p>
      )}
    </form>
  );
}

3.3 Server Actions 的高级用法

结合 useOptimistic 实现乐观更新,提升用户体验:

'use client';

import { useOptimistic } from 'react';
import { updatePostTitle } from '@/app/actions/post';

export function PostTitle({ id, title }: { id: string; title: string }) {
  const [optimisticTitle, setOptimisticTitle] = useOptimistic(title);

  async function handleSubmit(formData: FormData) {
    const newTitle = formData.get('title') as string;
    setOptimisticTitle(newTitle); // 立即更新 UI
    await updatePostTitle(id, newTitle); // 服务端持久化
  }

  return (
    <form action={handleSubmit}>
      <input name="title" defaultValue={optimisticTitle} />
      <button type="submit">保存</button>
    </form>
  );
}

四、Vercel AI SDK 集成实战

4.1 搭建 AI 聊天接口

Vercel AI SDK 是构建 AI 应用的首选工具库,与 Next.js 深度集成:

npm install ai @ai-sdk/openai
// app/api/ai/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-4o'),
    system: '你是一个专业的技术助手,擅长回答编程问题。请用中文回答。',
    messages,
  });

  return result.toDataStreamResponse();
}

4.2 构建聊天界面

// app/components/ChatWindow.tsx
'use client';

import { useChat } from 'ai/react';

export function ChatWindow() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } =
    useChat();

  return (
    <div className="flex flex-col h-screen max-w-2xl mx-auto">
      <div className="flex-1 overflow-y-auto p-4">
        {messages.map((message) => (
          <div
            key={message.id}
            className={`mb-4 ${
              message.role === 'user' ? 'text-right' : 'text-left'
            }`}
          >
            <span
              className={`inline-block rounded-lg px-4 py-2 ${
                message.role === 'user'
                  ? 'bg-blue-500 text-white'
                  : 'bg-gray-100'
              }`}
            >
              {message.content}
            </span>
          </div>
        ))}
        {isLoading && (
          <div className="text-gray-400 animate-pulse">AI 正在思考...</div>
        )}
      </div>
      <form onSubmit={handleSubmit} className="p-4 border-t">
        <div className="flex gap-2">
          <input
            value={input}
            onChange={handleInputChange}
            placeholder="输入你的问题..."
            className="flex-1 border rounded-lg px-4 py-2"
          />
          <button
            type="submit"
            disabled={isLoading}
            className="bg-blue-500 text-white px-6 py-2 rounded-lg"
          >
            发送
          </button>
        </div>
      </form>
    </div>
  );
}

4.3 结构化输出与工具调用

AI SDK 支持结构化输出,让 AI 返回符合 Schema 的数据:

// app/api/ai/analyze/route.ts
import { openai } from '@ai-sdk/openai';
import { generateObject } from 'ai';
import { z } from 'zod';

const CodeAnalysisSchema = z.object({
  summary: z.string(),
  complexity: z.enum(['low', 'medium', 'high']),
  suggestions: z.array(
    z.object({
      type: z.enum(['performance', 'security', 'readability']),
      description: z.string(),
      code: z.string().optional(),
    })
  ),
});

export async function POST(req: Request) {
  const { code } = await req.json();

  const { object } = await generateObject({
    model: openai('gpt-4o'),
    schema: CodeAnalysisSchema,
    prompt: `分析以下代码的质量并给出改进建议:\n\n${code}`,
  });

  return Response.json(object);
}

五、中间件认证实现

5.1 基于 JWT 的认证中间件

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyToken } from '@/lib/auth';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('auth-token')?.value;

  // 公开路由白名单
  const publicPaths = ['/login', '/register', '/api/auth'];
  const isPublicPath = publicPaths.some((path) =>
    request.nextUrl.pathname.startsWith(path)
  );

  if (isPublicPath) {
    return NextResponse.next();
  }

  if (!token) {
    const loginUrl = new URL('/login', request.url);
    loginUrl.searchParams.set('callbackUrl', request.nextUrl.pathname);
    return NextResponse.redirect(loginUrl);
  }

  try {
    const payload = verifyToken(token);
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-user-id', payload.userId);
    requestHeaders.set('x-user-role', payload.role);

    return NextResponse.next({
      request: { headers: requestHeaders },
    });
  } catch {
    const response = NextResponse.redirect(new URL('/login', request.url));
    response.cookies.delete('auth-token');
    return response;
  }
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|assets).*)',
  ],
};

5.2 基于角色的路由保护

// app/admin/layout.tsx
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';

export default async function AdminLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const headersList = await headers();
  const role = headersList.get('x-user-role');

  if (role !== 'admin') {
    redirect('/unauthorized');
  }

  return (
    <div className="admin-layout">
      <aside>{/* 管理后台侧边栏 */}</aside>
      <main>{children}</main>
    </div>
  );
}

六、高级优化策略

6.1 静态生成与增量再验证

对于内容不频繁变化的页面,使用 ISR 大幅提升性能:

// app/blog/page.tsx
export const revalidate = 3600; // 每小时重新验证

export default async function BlogPage() {
  const posts = await db.post.findMany({
    orderBy: { createdAt: 'desc' },
  });

  return (
    <div>
      {posts.map((post) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

6.2 流式渲染与部分预渲染(PPR)

Next.js 15 引入了部分预渲染(Partial Prerendering),将静态 Shell 与动态内容结合:

// next.config.ts
const nextConfig = {
  experimental: {
    ppr: 'incremental',
  },
};

// app/dashboard/page.tsx
export const experimental_ppr = true;

export default function Dashboard() {
  return (
    <div>
      <StaticHeader />          {/* 静态部分,立即返回 */}
      <Suspense fallback={<Skeleton />}>
        <DynamicStats />        {/* 动态部分,流式加载 */}
      </Suspense>
    </div>
  );
}

总结

Next.js 15 通过 Server Components、Server Actions 和 AI SDK 的组合,为全栈开发提供了完整的解决方案。核心要点:

掌握这些技术,你就能构建出高性能、可维护的现代全栈应用。