Appearance
第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.lazy 和 Suspense 进行组件懒加载:
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-intl13.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 最佳实践
- 代码组织:将相关功能模块放在一起,使用清晰的目录结构
- 性能监控:使用 Chrome DevTools 和 Lighthouse 监控应用性能
- 安全措施:使用 HTTPS、CSRF 保护、XSS 防护等安全措施
- 测试:编写单元测试和集成测试,确保代码质量
- 文档:为代码添加注释和文档,提高可维护性
- CI/CD:配置持续集成和持续部署,自动化构建和部署流程
通过本章的学习,你已经掌握了 Next.js 的进阶配置和优化技巧。这些知识将帮助你构建更加高性能、可维护的 Next.js 应用。在实际开发中,你应该根据项目的具体需求,选择合适的配置和优化策略,不断提升应用的质量和用户体验。
