Next.js 15 基础教程

学习 Next.js 15 的核心概念,包括 App Router、Server Components、流式渲染等最新特性

什么是 Next.js?

Next.js 是一个基于 React 的全栈框架,由 Vercel 开发和维护。它为 React 应用提供了 生产级的功能和优化,让开发者能够轻松构建现代 Web 应用程序。

Next.js 解决了什么问题?

性能问题

  • • SEO 优化困难
  • • 首屏加载速度慢
  • • 代码分割复杂
  • • 图片优化手动处理

开发体验问题

  • • 路由配置复杂
  • • 构建配置繁琐
  • • 热重载不稳定
  • • TypeScript 配置困难

部署问题

  • • 服务器配置复杂
  • • CDN 配置手动
  • • 环境变量管理困难
  • • 缓存策略难以实现

全栈开发问题

  • • API 路由分离
  • • 数据获取复杂
  • • 状态管理困难
  • • 类型安全缺失

Next.js 15 的核心特性

App Router

基于文件系统的新路由方式,支持布局和嵌套路由

Server Components

服务端渲染的 React 组件,更好的性能和 SEO

流式渲染

渐进式页面加载,提升用户体验

App Router 文件系统路由

Next.js 15 使用 app 目录结构来定义路由,每个文件夹代表一个路由段

App Router 路由演示

这是首页内容

Server Components vs Client Components

了解服务端组件和客户端组件的区别,以及何时使用它们

Server Component (服务端组件)

• 在服务器端渲染

• 可以直接访问数据库

• 更小的客户端包体积

• 无法使用浏览器 API

当前时间: --:--:--

Client Component (客户端组件)

• 在浏览器中渲染

• 可以使用 React 状态和生命周期

• 支持交互和事件处理

• 使用 'use client' 指令

Loading 和 Error 边界

Next.js 15 提供了内置的 loading 和 error 处理机制

内容已加载完成

动态路由和参数处理

体验 Next.js 的动态路由功能,包括路径参数和查询参数的处理

/blog/nextjs-guide
单个动态参数
app/blog/[slug]/page.tsx
/users/123/posts/456
多级动态参数
app/users/[id]/posts/[postId]/page.tsx
/docs/react/hooks/useState
捕获所有路由
app/docs/[...sections]/page.tsx
/products
可选捕获所有路由
app/products/[[...filters]]/page.tsx

当前路由信息:

路径参数 (params):
{
  "slug": "nextjs-guide"
}
查询参数 (searchParams):
{
  "category": "tech",
  "tags": "react,nextjs"
}
完整 URL:
/blog/nextjs-guide?category=tech&tags=react,nextjs

现代化数据获取

演示 Next.js 15 中的各种数据获取方式和缓存策略

服务端组件 演示

缓存策略: 优先使用缓存

中间件 (Middleware)

体验 Next.js 中间件的强大功能,包括认证、权限控制和请求处理

用户状态

路由导航

中间件执行日志

点击路由按钮查看中间件执行过程
当前页面状态

当前路径: /dashboard

认证状态: 未登录

用户角色: guest

页面类型: 受保护页面

路由系统:App Router vs Pages Router

Next.js 提供了两种路由系统:传统的 Pages Router 和新的 App Router。 App Router 是 Next.js 13+ 引入的新路由系统,提供了更强大的功能。

Pages Router (传统)

文件结构:
pages/
├── index.js → /
├── about.js → /about
├── blog/
│ ├── index.js → /blog
│ └── [slug].js → /blog/:slug
└── api/
    └── users.js → /api/users
特点:
  • • 简单直观的文件路由
  • • getServerSideProps
  • • getStaticProps
  • • 成熟稳定

App Router (推荐)

文件结构:
app/
├── layout.js → 根布局
├── page.js → /
├── about/
│ └── page.js → /about
├── blog/
│ ├── layout.js → 博客布局
│ ├── page.js → /blog
│ └── [slug]/
│     └── page.js → /blog/:slug
└── api/
    └── users/
        └── route.js → /api/users
特点:
  • • 支持嵌套布局
  • • Server Components
  • • 流式渲染
  • • 更强大的数据获取
App Router 路由示例
// App Router 示例
// app/layout.tsx - 根布局
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh">
      <body>
        <header>全局导航</header>
        {children}
        <footer>全局页脚</footer>
      </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>
  );
}

// app/blog/[slug]/page.tsx - 动态路由
export default function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  return <h1>博客文章: {params.slug}</h1>;
}

渲染模式对比与选择

深度理解 SSR、SSG、ISR、CSR 四种渲染模式的特点、优势和适用场景

SSR (服务端渲染)

每次请求时在服务器渲染页面

优势

  • SEO 友好
  • 首屏加载快
  • 实时数据

劣势

  • 服务器负载高
  • TTFB 较慢
  • 缓存困难
适用场景:动态内容、用户个性化页面

渲染流程

1
用户请求页面
浏览器向服务器发送请求
0ms
2
服务器获取数据
从数据库或 API 获取最新数据
50ms
3
服务器渲染 HTML
在服务器上执行 React 组件
100ms
4
返回完整 HTML
发送渲染好的 HTML 到浏览器
150ms
5
客户端激活
下载 JavaScript 并激活交互
300ms

渲染模式选择指南

Next.js 支持多种渲染模式,每种模式都有其特定的使用场景和优势。 了解这些模式可以帮助你选择最适合的渲染策略。

SSR (服务端渲染)

特点: 每次请求时在服务器生成 HTML

适用: 需要实时数据的页面

优势: SEO 友好,首屏快速显示

劣势: 服务器负载高,TTFB 较慢

SSG (静态生成)

特点: 构建时预生成静态 HTML

适用: 内容相对固定的页面

优势: 性能最佳,CDN 友好

劣势: 需要重新构建更新内容

ISR (增量静态再生)

特点: 静态生成 + 后台更新

适用: 需要定期更新的静态内容

优势: 兼顾性能和内容新鲜度

劣势: 实现相对复杂

CSR (客户端渲染)

特点: 在浏览器中渲染内容

适用: 高度交互的应用界面

优势: 丰富的交互体验

劣势: SEO 需要特殊处理

不同渲染模式示例
// SSR 示例 (App Router)
export default async function ProductPage({ params }: { params: { id: string } }) {
  // 每次请求时获取数据
  const product = await getProduct(params.id);

  return <ProductDetails product={product} />;
}

// SSG 示例
export async function generateStaticParams() {
  const products = await getProducts();
  return products.map((product) => ({
    id: product.id,
  }));
}

export default async function StaticProductPage({ params }: { params: { id: string } }) {
  // 构建时生成静态页面
  const product = await getProduct(params.id);

  return <ProductDetails product={product} />;
}

// ISR 示例 (Pages Router)
export async function getStaticProps() {
  const data = await fetchData();

  return {
    props: { data },
    revalidate: 60, // 60秒后重新生成
  };
}

数据获取策略

Next.js 提供了多种数据获取方法,支持不同的渲染策略和使用场景。 App Router 简化了数据获取,使其更加直观和强大。

App Router 数据获取 (推荐)

服务端组件

  • • 直接使用 async/await
  • • 自动缓存优化
  • • 支持并行数据获取
  • • 零客户端 JavaScript

客户端组件

  • • 使用 use 钩子
  • • SWR 或 React Query
  • • 传统的 useEffect
  • • 支持实时更新

Pages Router 数据获取 (传统)

getStaticProps

构建时数据获取

getServerSideProps

请求时数据获取

getStaticPaths

动态路由预生成

App Router 数据获取示例
// App Router 数据获取示例

// 1. 服务端组件 - 直接 async/await
export default async function PostsPage() {
  // 并行获取数据
  const [posts, categories] = await Promise.all([
    fetch('https://api.example.com/posts').then(res => res.json()),
    fetch('https://api.example.com/categories').then(res => res.json())
  ]);

  return (
    <div>
      <h1>博客文章</h1>
      <CategoryFilter categories={categories} />
      <PostList posts={posts} />
    </div>
  );
}

// 2. 缓存配置
async function getPosts() {
  const res = await fetch('https://api.example.com/posts', {
    cache: 'force-cache', // 强制缓存
    // cache: 'no-store', // 不缓存
    // next: { revalidate: 60 } // 60秒重新验证
  });

  return res.json();
}

// 3. 客户端组件数据获取
'use client';

import { use } from 'react';

function ClientPosts({ postsPromise }: { postsPromise: Promise<Post[]> }) {
  const posts = use(postsPromise); // React 18+ use 钩子

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

// 4. 错误处理和加载状态
export default async function PostsPageWithErrorHandling() {
  try {
    const posts = await getPosts();
    return <PostList posts={posts} />;
  } catch (error) {
    return <div>加载失败: {error.message}</div>;
  }
}

API 调用机制与模式

比较 REST、GraphQL、tRPC、SWR 等不同的 API 调用模式和数据获取策略

REST API 演示

传统的 RESTful API 调用模式

服务器端代码:
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const page = parseInt(searchParams.get('page') || '1');
    const limit = parseInt(searchParams.get('limit') || '10');
    
    const users = await prisma.user.findMany({
      skip: (page - 1) * limit,
      take: limit,
      select: {
        id: true,
        name: true,
        email: true,
        createdAt: true
      }
    });
    
    const total = await prisma.user.count();
    
    return NextResponse.json({
      users,
      pagination: {
        page,
        limit,
        total,
        pages: Math.ceil(total / limit)
      }
    });
  } catch (error) {
    return NextResponse.json(
      { error: 'Internal Server Error' },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    
    const user = await prisma.user.create({
      data: {
        name: body.name,
        email: body.email
      }
    });
    
    return NextResponse.json(user, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to create user' },
      { status: 400 }
    );
  }
}
客户端代码:
// 客户端调用
async function fetchUsers(page = 1, limit = 10) {
  const response = await fetch(`/api/users?page=${page}&limit=${limit}`);
  
  if (!response.ok) {
    throw new Error('Failed to fetch users');
  }
  
  return response.json();
}

async function createUser(userData) {
  const response = await fetch('/api/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(userData)
  });
  
  if (!response.ok) {
    throw new Error('Failed to create user');
  }
  
  return response.json();
}

// React 组件中使用
function UsersPage() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchUsers()
      .then(data => {
        setUsers(data.users);
        setLoading(false);
      })
      .catch(error => {
        console.error('Error:', error);
        setLoading(false);
      });
  }, []);
  
  const handleCreateUser = async (userData) => {
    try {
      const newUser = await createUser(userData);
      setUsers(prev => [...prev, newUser]);
    } catch (error) {
      console.error('Create error:', error);
    }
  };
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <div>
      {users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

API Routes (API 路由)

Next.js 允许你在同一个项目中创建 API 端点,实现全栈开发。 API Routes 让你可以轻松构建后端功能,而无需单独的服务器。

Pages Router API

文件位置: pages/api/

路由示例:

  • pages/api/users.js/api/users
  • pages/api/posts/[id].js/api/posts/:id

App Router API

文件位置: app/api/

路由示例:

  • app/api/users/route.js/api/users
  • app/api/posts/[id]/route.js/api/posts/:id
API Routes 完整示例
// App Router API Route 示例
// app/api/users/route.ts

import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';

// GET /api/users
export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const page = parseInt(searchParams.get('page') || '1');
    const limit = parseInt(searchParams.get('limit') || '10');

    const users = await prisma.user.findMany({
      skip: (page - 1) * limit,
      take: limit,
      select: {
        id: true,
        name: true,
        email: true,
        createdAt: true,
      },
    });

    return NextResponse.json({
      success: true,
      data: users,
      pagination: {
        page,
        limit,
        total: await prisma.user.count(),
      },
    });
  } catch (error) {
    return NextResponse.json(
      { success: false, error: '获取用户失败' },
      { status: 500 }
    );
  }
}

// POST /api/users
export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { name, email } = body;

    // 验证输入
    if (!name || !email) {
      return NextResponse.json(
        { success: false, error: '姓名和邮箱为必填项' },
        { status: 400 }
      );
    }

    // 创建用户
    const user = await prisma.user.create({
      data: { name, email },
    });

    return NextResponse.json(
      { success: true, data: user },
      { status: 201 }
    );
  } catch (error) {
    return NextResponse.json(
      { success: false, error: '创建用户失败' },
      { status: 500 }
    );
  }
}

// 动态路由示例
// app/api/users/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const user = await prisma.user.findUnique({
      where: { id: params.id },
      include: {
        posts: true,
        _count: {
          select: { posts: true, comments: true },
        },
      },
    });

    if (!user) {
      return NextResponse.json(
        { success: false, error: '用户不存在' },
        { status: 404 }
      );
    }

    return NextResponse.json({ success: true, data: user });
  } catch (error) {
    return NextResponse.json(
      { success: false, error: '获取用户失败' },
      { status: 500 }
    );
  }
}

// 中间件示例
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // API 路由的 CORS 处理
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const response = NextResponse.next();

    response.headers.set('Access-Control-Allow-Origin', '*');
    response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');

    return response;
  }

  return NextResponse.next();
}

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

网络请求监控 & 性能分析

实时观察不同渲染模式下的网络请求瀑布图、性能指标和 Core Web Vitals

访问页面
访问页面
访问页面
访问页面

网络监控

访问演示页面

网络请求瀑布图

点击"开始监控"来查看网络请求

Core Web Vitals

运行监控以查看性能指标

请求统计

暂无请求数据

优化建议

考虑使用缓存减少服务器响应时间
优化数据库查询以提高 SSR 性能

Next.js 内置优化演示

体验 Next.js 提供的各种性能优化功能,包括图片优化、代码分割、预取等

图片优化 演示

<Image src="/hero.jpg" alt="演示图片" width={400} height={300} format="webp" priority placeholder="blur" />
优化: 自动格式转换、响应式尺寸、懒加载、占位符

Next.js 内置优化详解

Next.js 提供了许多开箱即用的性能优化功能,让你的应用自动获得最佳性能, 而无需手动配置复杂的优化设置。

图片优化

  • • 自动格式转换 (WebP, AVIF)
  • • 响应式图片
  • • 懒加载
  • • 尺寸优化

代码分割

  • • 自动页面分割
  • • 动态导入
  • • 共享代码提取
  • • 按需加载

字体优化

  • • 字体预加载
  • • FOUT 防护
  • • Google Fonts 优化
  • • 子集化

脚本优化

  • • 第三方脚本优化
  • • 策略加载
  • • 内联优化
  • • 预连接

缓存策略

  • • 自动静态优化
  • • 数据缓存
  • • 路由缓存
  • • 组件缓存

预取和预加载

  • • 链接预取
  • • 路由预加载
  • • 智能预测
  • • 关键资源优先
Next.js 内置优化示例
// 1. 图片优化示例
import Image from 'next/image';

function OptimizedImage() {
  return (
    <Image
      src="/hero.jpg"
      alt="英雄图片"
      width={800}
      height={600}
      priority // 优先加载
      placeholder="blur" // 模糊占位符
      blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..." // 占位符数据
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" // 响应式尺寸
    />
  );
}

// 2. 字体优化示例
import { Inter, Roboto_Mono } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap', // 字体交换策略
  variable: '--font-inter', // CSS 变量
});

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-roboto-mono',
});

export default function RootLayout({ children }) {
  return (
    <html lang="zh" className={`${inter.variable} ${robotoMono.variable}`}>
      <body className="font-sans">{children}</body>
    </html>
  );
}

// 3. 脚本优化示例
import Script from 'next/script';

function Analytics() {
  return (
    <>
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
        strategy="afterInteractive" // 页面交互后加载
      />
      <Script id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'GA_MEASUREMENT_ID');
        `}
      </Script>
    </>
  );
}

// 4. 动态导入和代码分割
import dynamic from 'next/dynamic';
import { Suspense } from 'react';

// 动态导入组件
const DynamicChart = dynamic(() => import('@/components/Chart'), {
  loading: () => <p>加载图表中...</p>,
  ssr: false, // 禁用服务端渲染
});

// 条件加载
const ConditionalComponent = dynamic(
  () => import('@/components/HeavyComponent'),
  { ssr: false }
);

function Dashboard() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <h1>仪表板</h1>
      {showChart && (
        <Suspense fallback={<div>加载中...</div>}>
          <DynamicChart />
        </Suspense>
      )}
    </div>
  );
}

// 5. 链接预取
import Link from 'next/link';

function Navigation() {
  return (
    <nav>
      <Link
        href="/about"
        prefetch={true} // 预取页面
      >
        关于我们
      </Link>
      <Link
        href="/heavy-page"
        prefetch={false} // 禁用预取
      >
        重型页面
      </Link>
    </nav>
  );
}

流式渲染 (Streaming)

流式渲染是 Next.js 13+ 的强大特性,允许页面内容逐步加载,提供更好的用户体验。 用户可以立即看到页面的一部分,而其他部分仍在加载中。

流式渲染示例
// 流式渲染示例
import { Suspense } from 'react';

function SlowComponent() {
  // 模拟慢组件
  return <div>这是一个慢加载的组件</div>;
}

export default function StreamingPage() {
  return (
    <div>
      <h1>页面标题 (立即显示)</h1>

      <Suspense fallback={<div>加载中...</div>}>
        <SlowComponent />
      </Suspense>

      <p>其他内容 (立即显示)</p>
    </div>
  );
}

在线代码编辑器

尝试修改下面的代码,体验 Next.js 15 的特性。代码将实时渲染在下方的预览区域:

Next.js 15 组件示例 - 可预览沙箱

加载编辑器中...
预览结果

Next.js 核心概念总结

路由系统

  • App Router:支持嵌套布局
  • 文件系统路由:直观易懂
  • 动态路由:灵活的参数处理
  • 特殊文件:loading、error、layout

渲染模式

  • SSR:服务端渲染,SEO 友好
  • SSG:静态生成,性能最佳
  • ISR:增量静态再生
  • CSR:客户端渲染,交互丰富

数据获取

  • async/await:直接在组件中
  • 自动缓存:智能缓存策略
  • 并行获取:Promise.all 支持
  • 错误处理:内置错误边界

API Routes

  • 全栈开发:前后端一体化
  • RESTful API:标准 HTTP 方法
  • 中间件支持:请求拦截处理
  • 类型安全:TypeScript 支持

性能优化

  • 自动优化:开箱即用
  • 代码分割:按需加载
  • 图片优化:WebP、懒加载
  • 字体优化:预加载、子集化

开发体验

  • 热重载:快速开发反馈
  • TypeScript:开箱即用
  • ESLint:代码质量保证
  • 零配置:快速上手

💡 Next.js 最佳实践建议

路由设计:合理规划页面结构,利用嵌套布局

渲染策略:根据内容特性选择合适的渲染模式

数据获取:在服务端获取数据,减少客户端请求

性能优化:使用 Next.js Image 组件优化图片

代码分割:使用动态导入进行组件懒加载

SEO 优化:合理使用 metadata API 提升搜索排名

🚀 快速访问演示页面

点击下方按钮直接访问不同渲染模式的演示页面,建议打开浏览器开发者工具的 Network 面板观察加载过程

SSR 演示

服务端渲染演示页面

服务端数据获取
快速首屏渲染
SEO 友好
访问演示

SSG 演示

静态站点生成演示页面

最快加载速度
CDN 友好
构建时生成
访问演示

ISR 演示

增量静态再生演示页面

静态性能
自动更新
数据新鲜度
访问演示

CSR 演示

客户端渲染演示页面

动态交互
实时数据
用户状态
访问演示

💡 网络监控小贴士

访问演示页面时,建议按 F12 打开开发者工具, 切换到 Network 面板,然后刷新页面观察不同渲染模式的网络请求瀑布图和加载时序。 您会发现 SSG 加载最快,CSR 有更多 API 请求,SSR 在服务器端完成渲染等不同特征。

准备好了吗?

现在你已经了解了 Next.js 15 的基础概念,让我们继续学习 TypeScript 集成。

学习 TypeScript 集成