Skip to content

第13章:Next.js 进阶配置与优化

在本章中,我们将深入探讨 Next.js 的进阶配置和优化技巧,帮助你构建性能更好、用户体验更佳的应用。

13.1 next.config.js 进阶配置

next.config.js 是 Next.js 项目的核心配置文件,它允许你自定义 Next.js 的行为。

13.1.1 基础配置

javascript
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
};

module.exports = nextConfig;

13.1.2 进阶配置

13.1.2.1 服务器配置

javascript
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 配置服务器端口
  serverPort: 3000,
  
  // 配置服务器主机
  serverHost: '0.0.0.0',
  
  // 配置 API 路由前缀
  basePath: '/app',
  
  // 配置资产前缀
  assetPrefix: process.env.NODE_ENV === 'production' ? 'https://cdn.example.com' : '',
};

module.exports = nextConfig;

13.1.2.2 构建配置

javascript
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 启用增量静态再生
  trailingSlash: true,
  
  // 配置页面导出
  exportPathMap: async function (defaultPathMap, { dev, dir, outDir, distDir, buildId }) {
    return {
      '/': { page: '/' },
      '/about': { page: '/about' },
      '/404': { page: '/404' },
    };
  },
  
  // 配置构建输出目录
  distDir: 'build',
  
  // 配置编译选项
  compiler: {
    // 启用 styled-components 支持
    styledComponents: true,
    
    // 启用 emotion 支持
    emotion: true,
    
    // 配置 React 运行时
    reactRemoveProperties: process.env.NODE_ENV === 'production',
    
    // 移除 console.log
    removeConsole: process.env.NODE_ENV === 'production',
  },
};

module.exports = nextConfig;

13.1.2.3 插件配置

javascript
// next.config.js
const withPlugins = require('next-compose-plugins');
const withImages = require('next-images');
const withSass = require('@zeit/next-sass');

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
};

module.exports = withPlugins([
  withImages,
  withSass,
], nextConfig);

13.1.3 环境变量配置

javascript
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 配置环境变量
  env: {
    API_URL: process.env.API_URL,
    APP_NAME: process.env.APP_NAME,
  },
};

module.exports = nextConfig;

13.2 性能优化技巧

13.2.1 代码分割

13.2.1.1 自动分割

Next.js 会自动对代码进行分割,将不同页面的代码分离,减少初始加载时间。

13.2.1.2 手动分割

使用 React.lazySuspense 进行组件懒加载:

javascript
// components/LazyComponent.jsx
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

export default function LazyComponent() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

13.2.2 图片、字体、脚本优化

13.2.2.1 图片优化

使用 Next.js 的 Image 组件优化图片加载:

javascript
import Image from 'next/image';

export default function ImageOptimization() {
  return (
    <div>
      <Image
        src="/images/hero.jpg"
        alt="Hero image"
        width={1200}
        height={600}
        priority
        placeholder="blur"
        blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
      />
    </div>
  );
}

13.2.2.2 字体优化

使用 next/font 优化字体加载:

javascript
// app/layout.js
import { Inter } from 'next/font/google';

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

export default function RootLayout({ children }) {
  return (
    <html lang="zh-CN" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

13.2.2.3 脚本优化

使用 next/script 优化脚本加载:

javascript
// app/layout.js
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html lang="zh-CN">
      <body>
        {children}
        <Script
          src="https://www.google-analytics.com/analytics.js"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

13.2.3 缓存策略优化

13.2.3.1 ISR 配置

使用增量静态再生(ISR)优化静态页面更新:

javascript
// app/blog/[slug]/page.js
export const revalidate = 60; // 60秒重新验证

export default function PostPage({ params }) {
  // 页面内容
}

13.2.3.2 SWR 缓存

使用 SWR 进行数据缓存和自动刷新:

javascript
// components/PostList.jsx
import useSWR from 'swr';

const fetcher = (url) => fetch(url).then((res) => res.json());

export default function PostList() {
  const { data, error, isLoading } = useSWR('/api/posts', fetcher, {
    revalidateOnFocus: false,
    revalidateOnReconnect: true,
    refreshInterval: 30000, // 30秒刷新一次
  });

  if (error) return <div>Failed to load posts</div>;
  if (isLoading) return <div>Loading...</div>;

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

13.3 自定义中间件

13.3.1 全局中间件

创建 middleware.js 文件来定义全局中间件:

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

export function middleware(request) {
  // 日志记录
  console.log('Request URL:', request.url);
  
  // 重定向逻辑
  if (request.nextUrl.pathname === '/old-path') {
    return NextResponse.redirect(new URL('/new-path', request.url));
  }
  
  // 响应修改
  const response = NextResponse.next();
  response.headers.set('X-Custom-Header', 'Hello from middleware');
  return response;
}

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

13.3.2 路由中间件

为特定路由配置中间件:

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

export function middleware(request) {
  const { pathname } = request.nextUrl;
  
  // 保护 admin 路由
  if (pathname.startsWith('/admin')) {
    const token = request.cookies.get('auth-token');
    
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
    
    // 验证 token
    // const isValid = verifyToken(token.value);
    // if (!isValid) {
    //   return NextResponse.redirect(new URL('/login', request.url));
    // }
  }
  
  return NextResponse.next();
}

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

13.3.3 权限控制

使用中间件实现权限控制:

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

export function middleware(request) {
  const { pathname } = request.nextUrl;
  
  // 定义需要权限的路由
  const protectedRoutes = ['/dashboard', '/profile', '/settings'];
  
  if (protectedRoutes.some(route => pathname.startsWith(route))) {
    const token = request.cookies.get('auth-token');
    
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }
  
  return NextResponse.next();
}

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

13.3.4 日志记录

使用中间件实现请求日志记录:

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

export function middleware(request) {
  const start = Date.now();
  
  const response = NextResponse.next();
  
  // 记录请求信息
  console.log({
    method: request.method,
    url: request.url,
    headers: Object.fromEntries(request.headers),
    timestamp: new Date().toISOString(),
  });
  
  // 记录响应时间
  response.headers.set('X-Response-Time', `${Date.now() - start}ms`);
  
  return response;
}

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

13.4 错误处理

13.4.1 error.js 页面

创建 error.js 文件来处理页面级错误:

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

export default function Error({ error, reset }) {
  return (
    <div className="container mx-auto px-4 py-8 text-center">
      <h1 className="text-4xl font-bold mb-4">发生错误</h1>
      <p className="text-lg mb-6">{error.message}</p>
      <button
        onClick={() => reset()}
        className="bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700"
      >
        重试
      </button>
    </div>
  );
}

13.4.2 边界错误捕获

使用 React 的 Error Boundary 捕获组件级错误:

javascript
// components/ErrorBoundary.jsx
'use client';

import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by ErrorBoundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="p-4 bg-red-100 border border-red-400 rounded-md">
          <h3 className="text-red-800 font-bold mb-2">组件发生错误</h3>
          <p className="text-red-700">{this.state.error?.message}</p>
          <button
            onClick={() => this.setState({ hasError: false, error: null })}
            className="mt-4 bg-red-500 text-white px-3 py-1 rounded"
          >
            重试
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

使用 Error Boundary:

javascript
// components/SomeComponent.jsx
import ErrorBoundary from './ErrorBoundary';
import PotentiallyBuggyComponent from './PotentiallyBuggyComponent';

export default function SomeComponent() {
  return (
    <ErrorBoundary>
      <PotentiallyBuggyComponent />
    </ErrorBoundary>
  );
}

13.5 国际化配置

13.5.1 安装依赖

bash
npm install next-intl

13.5.2 配置国际化

创建国际化配置文件:

javascript
// i18n.js
import { getRequestConfig } from 'next-intl/server';

export default getRequestConfig(async ({ locale }) => ({
  messages: (await import(`./messages/${locale}.json`)).default,
}));

创建消息文件:

json
// messages/en.json
{
  "Home": {
    "title": "Welcome to my app",
    "description": "This is a Next.js app with internationalization"
  }
}
json
// messages/zh.json
{
  "Home": {
    "title": "欢迎使用我的应用",
    "description": "这是一个带有国际化功能的 Next.js 应用"
  }
}

13.5.3 配置 Next.js

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

module.exports = nextConfig;

13.5.4 使用国际化

javascript
// app/page.js
import { useTranslations } from 'next-intl';

export default function Home() {
  const t = useTranslations('Home');
  
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

13.5.5 语言切换

javascript
// components/LanguageSwitcher.jsx
import { useRouter } from 'next/router';

export default function LanguageSwitcher() {
  const router = useRouter();
  const { locale, locales } = router;
  
  const changeLanguage = (lang) => {
    router.push(router.asPath, router.asPath, { locale: lang });
  };
  
  return (
    <div className="flex space-x-4">
      {locales.map((lang) => (
        <button
          key={lang}
          onClick={() => changeLanguage(lang)}
          className={`px-3 py-1 rounded-full ${locale === lang ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-700'}`}
        >
          {lang === 'en' ? 'English' : '中文'}
        </button>
      ))}
    </div>
  );
}

13.6 最佳实践

  1. 代码组织:将相关功能模块放在一起,使用清晰的目录结构
  2. 性能监控:使用 Chrome DevTools 和 Lighthouse 监控应用性能
  3. 安全措施:使用 HTTPS、CSRF 保护、XSS 防护等安全措施
  4. 测试:编写单元测试和集成测试,确保代码质量
  5. 文档:为代码添加注释和文档,提高可维护性
  6. CI/CD:配置持续集成和持续部署,自动化构建和部署流程

通过本章的学习,你已经掌握了 Next.js 的进阶配置和优化技巧。这些知识将帮助你构建更加高性能、可维护的 Next.js 应用。在实际开发中,你应该根据项目的具体需求,选择合适的配置和优化策略,不断提升应用的质量和用户体验。

© 2026 编程马·菜鸟教程 版权所有