Chenyj Space
返回博客
·20 分钟

Next.js 15 全栈开发实战指南

Next.jsReact全栈TypeScriptServer Components全栈

Next.js 15 核心特性

App Router 架构

App Router 是 Next.js 13+ 引入的新路由系统,基于文件系统的路由,支持嵌套布局、加载状态、错误处理等。

code
app/
├── layout.tsx          # 根布局
├── page.tsx            # 首页
├── loading.tsx         # 加载状态
├── error.tsx           # 错误处理
├── not-found.tsx       # 404 页面
├── blog/
│   ├── layout.tsx      # 博客布局
│   ├── page.tsx        # 博客列表
│   └── [slug]/
│       └── page.tsx    # 博客详情
└── api/
    └── users/
        └── route.ts    # API 路由

Server Components vs Client Components

tsx
// Server Component (默认) - 服务端渲染
// 可以直接访问数据库、文件系统等
async function BlogList() {
  const posts = await db.query('SELECT * FROM posts');
  
  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

// Client Component - 客户端渲染
// 需要 'use client' 指令
'use client';

import { useState } from 'react';

function LikeButton({ postId }: { postId: string }) {
  const [likes, setLikes] = useState(0);
  
  return (
    <button onClick={() => setLikes(likes + 1)}>
      👍 {likes}
    </button>
  );
}

Server Actions 详解

Server Actions 是 Next.js 14+ 引入的服务端函数,可以直接在组件中调用服务端逻辑。

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

import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;
  
  await db.posts.create({
    data: { title, content }
  });
  
  revalidatePath('/blog');
}

export async function deletePost(postId: string) {
  await db.posts.delete({ where: { id: postId } });
  revalidatePath('/blog');
}
tsx
// app/blog/create/page.tsx
import { createPost } from '@/app/actions/posts';

export default function CreatePostPage() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="文章标题" required />
      <textarea name="content" placeholder="文章内容" required />
      <button type="submit">发布文章</button>
    </form>
  );
}

数据获取策略

服务端数据获取

tsx
// 静态生成 (SSG)
async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);
  
  return <article>{post.content}</article>;
}

// 动态渲染 (SSR)
async function UserDashboard() {
  // 强制动态渲染
  const session = await getServerSession();
  
  return <div>Welcome, {session.user.name}</div>;
}

// 增量静态再生 (ISR)
async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id);
  
  return <ProductDetail product={product} />;
}

// 重新验证时间
export const revalidate = 3600; // 每小时重新生成

客户端数据获取

tsx
'use client';

import useSWR from 'swr';

function UserList() {
  const { data, error, isLoading } = useSWR('/api/users', fetcher);
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

API Routes 实现

typescript
// app/api/users/route.ts
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const page = parseInt(searchParams.get('page') || '1');
  const limit = parseInt(searchParams.get('limit') || '10');
  
  const users = await db.users.findMany({
    skip: (page - 1) * limit,
    take: limit,
  });
  
  return NextResponse.json(users);
}

export async function POST(request: Request) {
  const body = await request.json();
  
  const user = await db.users.create({
    data: body,
  });
  
  return NextResponse.json(user, { status: 201 });
}
typescript
// app/api/users/[id]/route.ts
import { NextResponse } from 'next/server';

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const user = await db.users.findUnique({
    where: { id: params.id },
  });
  
  if (!user) {
    return NextResponse.json(
      { error: 'User not found' },
      { status: 404 }
    );
  }
  
  return NextResponse.json(user);
}

export async function PUT(
  request: Request,
  { params }: { params: { id: string } }
) {
  const body = await request.json();
  
  const user = await db.users.update({
    where: { id: params.id },
    data: body,
  });
  
  return NextResponse.json(user);
}

export async function DELETE(
  request: Request,
  { params }: { params: { id: string } }
) {
  await db.users.delete({
    where: { id: params.id },
  });
  
  return NextResponse.json(null, { status: 204 });
}

中间件与认证

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

export async function middleware(request: NextRequest) {
  const token = request.cookies.get('token')?.value;
  
  // 保护路由
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
    
    try {
      await verifyToken(token);
      return NextResponse.next();
    } catch {
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }
  
  // API 路由认证
  if (request.nextUrl.pathname.startsWith('/api')) {
    if (!token) {
      return NextResponse.json(
        { error: 'Unauthorized' },
        { status: 401 }
      );
    }
  }
  
  return NextResponse.next();
}

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

部署与优化

环境变量配置

bash
# .env.local
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
NEXTAUTH_SECRET="your-secret-key"
NEXTAUTH_URL="http://localhost:3000"

性能优化

tsx
// 使用 Image 组件优化图片
import Image from 'next/image';

function Avatar({ src, alt }: { src: string; alt: string }) {
  return (
    <Image
      src={src}
      alt={alt}
      width={48}
      height={48}
      placeholder="blur"
      blurDataURL="/placeholder.png"
    />
  );
}

// 使用 Link 组件预加载
import Link from 'next/link';

function Navigation() {
  return (
    <nav>
      <Link href="/blog" prefetch={true}>
        Blog
      </Link>
    </nav>
  );
}

// 字体优化
import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin'] });

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}

总结

Next.js 15 提供了完整的全栈开发解决方案:

  1. App Router:基于文件系统的路由,支持嵌套布局
  2. Server Components:服务端渲染,减少客户端 JavaScript
  3. Server Actions:简化服务端逻辑,无需 API Routes
  4. 数据获取:支持 SSG、SSR、ISR 多种策略
  5. 中间件:请求拦截、认证、重定向
  6. 性能优化:图片、字体、代码分割

掌握这些核心特性,就能构建出高性能、可扩展的现代化 Web 应用。