Next.js 教程

项目概述

Next.js 是一款基于 React 的全栈框架,用于构建现代化的 Web 应用。它由 Vercel 公司开发并维护,提供了服务端渲染、静态网站生成、API 路由等功能,帮助开发者构建高性能、SEO 友好的 Web 应用。Next.js 简化了 React 应用的构建过程,提供了良好的开发体验和优化的生产构建。

主要特点

  • **服务端渲染 (SSR)**:提高首屏加载速度和 SEO 友好性
  • **静态网站生成 (SSG)**:预渲染页面,提供极致的加载性能
  • **增量静态再生 (ISR)**:结合 SSR 和 SSG 的优点,支持动态内容的静态生成
  • API 路由:在同一项目中构建 API 端点
  • 中间件:在请求处理过程中执行代码
  • 自动代码分割:优化加载性能
  • 文件系统路由:基于文件系统的路由系统,简化路由配置
  • TypeScript 支持:内置 TypeScript 支持
  • 优化的构建系统:自动优化生产构建

适用场景

  • 构建 SEO 友好的网站
  • 开发需要服务端渲染的应用
  • 构建静态网站
  • 全栈应用开发
  • 需要高性能的 Web 应用

安装与设置

方法一:使用 create-next-app

# 创建新的 Next.js 项目
npx create-next-app@latest my-app

# 进入项目目录
cd my-app

# 启动开发服务器
npm run dev

方法二:手动设置

# 创建项目目录
mkdir my-app
cd my-app

# 初始化 npm 项目
npm init -y

# 安装依赖
npm install next react react-dom

# 在 package.json 中添加脚本

在 package.json 中添加以下脚本:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "export": "next export"
  }
}

项目结构

Next.js 项目的基本结构如下:

my-app/
├── app/           # App Router(Next.js 13+)
├── pages/         # Pages Router
│   ├── api/       # API 路由
│   └── index.js   # 首页
├── public/        # 静态资源
├── styles/        # 样式文件
├── components/    # 组件
├── lib/           # 工具函数
├── next.config.js # Next.js 配置
└── package.json   # 项目配置

核心概念

1. 路由系统

Next.js 提供了两种路由系统:Pages Router 和 App Router。

Pages Router

基于文件系统的路由,文件存放在 pages 目录中:

// pages/index.js - 首页
import React from 'react';

export default function Home() {
  return (
    <div>
      <h1>Welcome to Next.js!</h1>
      <p>This is the homepage.</p>
    </div>
  );
}

// pages/about.js - 关于页
import React from 'react';

export default function About() {
  return (
    <div>
      <h1>About Page</h1>
      <p>This is the about page.</p>
    </div>
  );
}

// pages/blog/[id].js - 动态路由
import React from 'react';
import { useRouter } from 'next/router';

export default function BlogPost() {
  const router = useRouter();
  const { id } = router.query;

  return (
    <div>
      <h1>Blog Post {id}</h1>
      <p>This is the blog post with id {id}.</p>
    </div>
  );
}

App Router

Next.js 13+ 引入的新路由系统,基于 React Server Components:

// app/page.js - 首页
import React from 'react';

export default function Home() {
  return (
    <div>
      <h1>Welcome to Next.js!</h1>
      <p>This is the homepage.</p>
    </div>
  );
}

// app/about/page.js - 关于页
import React from 'react';

export default function About() {
  return (
    <div>
      <h1>About Page</h1>
      <p>This is the about page.</p>
    </div>
  );
}

// app/blog/[id]/page.js - 动态路由
import React from 'react';

export default function BlogPost({ params }) {
  const { id } = params;

  return (
    <div>
      <h1>Blog Post {id}</h1>
      <p>This is the blog post with id {id}.</p>
    </div>
  );
}

2. 数据获取

Next.js 提供了多种数据获取方法,适用于不同的渲染策略。

Pages Router 中的数据获取

  • getStaticProps:在构建时获取数据,用于静态生成页面
  • getServerSideProps:在每个请求时获取数据,用于服务端渲染页面
  • getStaticPaths:为动态路由生成静态路径
// pages/blog/[id].js
import React from 'react';

export default function BlogPost({ post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

// 为动态路由生成静态路径
export async function getStaticPaths() {
  // 获取所有博客文章 ID
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  // 生成路径
  const paths = posts.map((post) => ({
    params: { id: post.id.toString() },
  }));

  return {
    paths,
    fallback: false, // 404 for paths not generated
  };
}

// 在构建时获取数据
export async function getStaticProps({ params }) {
  // 获取单个博客文章
  const res = await fetch(`https://api.example.com/posts/${params.id}`);
  const post = await res.json();

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

App Router 中的数据获取

使用 React Server Components 和 fetch API:

// app/blog/[id]/page.js
import React from 'react';

async function getPost(id) {
  const res = await fetch(`https://api.example.com/posts/${id}`, {
    next: { revalidate: 60 }, // 60秒后重新验证
  });
  if (!res.ok) {
    throw new Error('Failed to fetch post');
  }
  return res.json();
}

export default async function BlogPost({ params }) {
  const post = await getPost(params.id);

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

3. API 路由

Next.js 允许在 pages/api 目录中创建 API 端点:

// pages/api/hello.js
import React from 'react';

export default function handler(req, res) {
  res.status(200).json({ name: 'John Doe' });
}

// pages/api/users/[id].js
export default function handler(req, res) {
  const { id } = req.query;
  res.status(200).json({ id, name: `User ${id}` });
}

在 App Router 中,API 路由位于 app/api 目录:

// app/api/hello/route.js
export async function GET(request) {
  return Response.json({ name: 'John Doe' });
}

export async function POST(request) {
  const data = await request.json();
  return Response.json({ ...data, id: 1 });
}

4. 中间件

Next.js 中间件允许在请求处理过程中执行代码:

// middleware.js
import { NextResponse } from 'next/server';

export function middleware(request) {
  // 检查是否有认证令牌
  const token = request.cookies.get('token')?.value;

  // 如果没有令牌,重定向到登录页
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

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

5. 静态资源

静态资源存放在 public 目录中,可以通过根路径访问:

// 访问 public/images/logo.png
<img src="/images/logo.png" alt="Logo" />

常用功能

1. 导航

使用 next/link 组件进行客户端导航:

import Link from 'next/link';

export default function Navigation() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
      <Link href="/blog/1">Blog Post 1</Link>
    </nav>
  );
}

2. 图片优化

使用 next/image 组件优化图片:

import Image from 'next/image';

export default function ImageExample() {
  return (
    <div>
      <Image
        src="/images/photo.jpg"
        width={500}
        height={300}
        alt="Photo"
        priority
      />
    </div>
  );
}

3. 字体优化

使用 next/font 优化字体:

import { Inter } from 'next/font/google';

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

export default function FontExample() {
  return (
    <div className={inter.className}>
      <h1>Hello with Inter font</h1>
      <p>This text uses the Inter font.</p>
    </div>
  );
}

4. 布局

在 App Router 中使用布局组件:

// app/layout.js
import React from 'react';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <header>
          <h1>My Website</h1>
        </header>
        <main>{children}</main>
        <footer>
          <p>© 2024 My Website</p>
        </footer>
      </body>
    </html>
  );
}

5. 错误处理

在 App Router 中使用错误边界:

// app/error.js
'use client';

export default function Error({ error, reset }) {
  return (
    <div>
      <h1>Something went wrong!</h1>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

高级功能

1. 国际化

Next.js 支持国际化路由:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  i18n: {
    locales: ['en', 'zh'],
    defaultLocale: 'en',
  },
};

module.exports = nextConfig;

创建国际化页面:

pages/
├── index.js        # 默认语言 (en)
├── about.js        # 默认语言 (en)
├── zh/
│   ├── index.js    # 中文
│   └── about.js    # 中文

2. 认证

实现基于 JWT 的认证系统:

// pages/api/auth/login.js
export default async function handler(req, res) {
  const { email, password } = req.body;

  // 验证用户
  if (email === 'user@example.com' && password === 'password') {
    // 生成令牌
    const token = 'your-jwt-token';

    // 设置cookie
    res.setHeader('Set-Cookie', `token=${token}; HttpOnly; Path=/; Max-Age=86400`);
    res.status(200).json({ success: true });
  } else {
    res.status(401).json({ success: false, message: 'Invalid credentials' });
  }
}

// middleware.js
import { NextResponse } from 'next/server';

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

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

3. 数据库集成

集成 PostgreSQL 数据库:

npm install pg
// lib/db.js
import { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

export default async function query(text, params) {
  const client = await pool.connect();
  try {
    const res = await client.query(text, params);
    return res;
  } finally {
    client.release();
  }
}

// pages/api/users.js
import query from '../../lib/db';

export default async function handler(req, res) {
  try {
    const result = await query('SELECT * FROM users');
    res.status(200).json(result.rows);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
}

4. 部署

部署到 Vercel

  1. 推送代码到 GitHub 仓库
  2. 在 Vercel 上导入仓库
  3. 配置构建选项
  4. 部署应用

部署到其他平台

# 构建应用
npm run build

# 导出静态网站(可选)
npm run export

性能优化

1. 代码分割

Next.js 自动进行代码分割,也可以手动分割代码:

import dynamic from 'next/dynamic';

// 懒加载组件
const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
  loading: () => <p>Loading...</p>,
});

export default function Page() {
  return (
    <div>
      <HeavyComponent />
    </div>
  );
}

2. 图片优化

使用 next/image 组件:

import Image from 'next/image';

export default function ProductPage() {
  return (
    <div>
      <Image
        src="/products/1.jpg"
        width={800}
        height={600}
        alt="Product"
        priority
      />
    </div>
  );
}

3. 缓存策略

使用 revalidate 选项缓存数据:

// 静态生成页面,每60秒重新验证
export async function getStaticProps() {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  return {
    props: { data },
    revalidate: 60, // 60秒
  };
}

// App Router 中
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 60 },
  });
  return res.json();
}

4. 字体优化

使用 next/font

import { Inter, Roboto } from 'next/font/google';

const inter = Inter({ subsets: ['latin'] });
const roboto = Roboto({ subsets: ['latin'], weight: ['400', '700'] });

export default function Page() {
  return (
    <div className={inter.className}>
      <h1 style={{ fontFamily: roboto.style.fontFamily }}>Hello</h1>
      <p>This text uses Inter font</p>
    </div>
  );
}

最佳实践

1. 项目结构

  • 按功能模块组织代码
  • 使用文件夹结构管理组件
  • 分离关注点(UI、逻辑、数据)
  • 为组件添加清晰的文档和注释

2. 数据获取

  • 根据页面类型选择合适的数据获取方法
  • 使用 revalidate 选项缓存数据
  • 避免在客户端获取敏感数据
  • 优化 API 请求,减少网络延迟

3. 性能

  • 使用 next/image 优化图片
  • 使用 next/font 优化字体
  • 合理使用代码分割
  • 优化首屏加载速度

4. 安全性

  • 使用环境变量存储敏感信息
  • 实现适当的认证和授权
  • 验证用户输入
  • 防止 CSRF 和 XSS 攻击

5. 测试

  • 为组件编写单元测试
  • 测试 API 路由
  • 测试中间件逻辑
  • 进行端到端测试

常见问题与解决方案

1. 路由问题

问题:路由不工作或参数获取不到。

解决方案

  • 检查文件路径是否正确
  • 确保动态路由文件名正确(如 [id].js
  • 在 App Router 中,使用 params 属性获取路由参数
  • 在 Pages Router 中,使用 useRouter 钩子获取路由参数

2. 数据获取问题

问题:数据获取失败或不更新。

解决方案

  • 检查 API 端点是否可达
  • 验证数据获取方法是否正确
  • 检查 revalidate 配置
  • 确保错误处理正确

3. 部署问题

问题:部署后应用不工作。

解决方案

  • 检查环境变量配置
  • 验证构建过程是否成功
  • 检查服务器日志
  • 确保 API 路由配置正确

4. 性能问题

问题:应用加载缓慢。

解决方案

  • 使用 next/image 优化图片
  • 实现代码分割
  • 优化数据获取
  • 使用静态生成或增量静态再生

5. 样式问题

问题:样式不应用或不一致。

解决方案

  • 检查样式导入是否正确
  • 确保 CSS 模块命名正确
  • 验证 Tailwind CSS 配置(如果使用)
  • 检查样式优先级

参考资源

总结

Next.js 是一款功能强大的 React 全栈框架,提供了服务端渲染、静态网站生成、API 路由等功能,帮助开发者构建高性能、SEO 友好的 Web 应用。通过本教程的学习,你应该已经掌握了 Next.js 的核心概念、使用方法和最佳实践。

Next.js 的设计理念是"约定优于配置",通过合理的默认配置和文件系统路由,简化了 React 应用的构建过程。它的自动代码分割、图片优化、字体优化等功能,帮助开发者构建性能优异的应用。

作为 React 生态系统中最流行的框架之一,Next.js 拥有庞大的开发者社区和完善的文档,这使得它成为构建现代化 Web 应用的理想选择。无论是简单的静态网站还是复杂的全栈应用,Next.js 都能满足你的需求。

随着 Next.js 的不断发展,它也在持续改进和更新,添加新的功能和优化性能。建议你持续关注 Next.js 的官方文档和社区动态,以便及时了解最新的功能和最佳实践。

« 上一篇 Material-UI 教程 - 基于 Material Design 的 React 组件库 下一篇 » Express.js 教程 - 基于 Node.js 的 Web 框架