部署与优化
本章将介绍 Next.js 应用的部署方式和性能优化技巧。
部署方式
Vercel 部署(推荐)
Vercel 是 Next.js 的官方托管平台,提供最佳的开发体验。
步骤:
- 将代码推送到 GitHub
- 在 Vercel 导入项目
- 自动检测 Next.js 并配置
- 点击部署
优势:
- 零配置部署
- 自动 HTTPS
- 全球 CDN
- 预览部署
- 自动 CI/CD
自托管部署
Docker 部署
创建 Dockerfile:
FROM node:20-alpine AS base
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
配置 next.config.ts:
const nextConfig = {
output: "standalone",
};
构建和运行:
docker build -t my-next-app .
docker run -p 3000:3000 my-next-app
静态导出
// next.config.ts
const nextConfig = {
output: "export",
};
构建:
npm run build
输出到 out 目录,可以部署到任何静态托管服务。
Node.js 服务器
npm run build
npm run start
使用 PM2 管理进程:
npm install -g pm2
pm2 start npm --name "next-app" -- start
性能优化
图片优化
import Image from "next/image";
// 使用 priority 加载首屏图片
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />
// 使用 sizes 属性优化响应式图片
<Image
src="/photo.jpg"
alt="Photo"
width={800}
height={600}
sizes="(max-width: 768px) 100vw, 50vw"
/>
// 使用模糊占位符
<Image
src="/photo.jpg"
alt="Photo"
width={800}
height={600}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
字体优化
import { Inter } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
display: "swap", // 避免字体加载时的闪烁
});
代码分割
动态导入
import dynamic from "next/dynamic";
const DynamicChart = dynamic(() => import("@/components/Chart"), {
loading: () => <p>加载中...</p>,
ssr: false, // 禁用服务端渲染
});
export default function Page() {
return (
<div>
<DynamicChart />
</div>
);
}
按需加载第三方库
"use client";
import { useState } from "react";
export default function Page() {
const [chart, setChart] = useState(null);
const loadChart = async () => {
const module = await import("heavy-chart-library");
setChart(module.Chart);
};
return (
<div>
<button onClick={loadChart}>加载图表</button>
{chart && <chart data={data} />}
</div>
);
}
缓存策略
数据缓存
// 缓存 60 秒
const res = await fetch("https://api.example.com/data", {
next: { revalidate: 60 },
});
// 永久缓存
const res = await fetch("https://api.example.com/static", {
cache: "force-cache",
});
// 不缓存
const res = await fetch("https://api.example.com/realtime", {
cache: "no-store",
});
路由缓存
// 页面级缓存
export const revalidate = 60; // 60 秒重新验证
export default function Page() {
return <div>页面内容</div>;
}
懒加载
"use client";
import { lazy, Suspense } from "react";
const HeavyComponent = lazy(() => import("@/components/Heavy"));
export default function Page() {
return (
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
);
}
预加载
import Link from "next/link";
// 默认预加载视口内的链接
<Link href="/about">关于</Link>
// 禁用预加载
<Link href="/about" prefetch={false}>关于</Link>
脚本优化
import Script from "next/script";
export default function Page() {
return (
<>
{/* 页面加载后执行 */}
<Script src="/analytics.js" strategy="afterInteractive" />
{/* 浏览器空闲时执行 */}
<Script src="/chat.js" strategy="lazyOnload" />
{/* 阻塞渲染 */}
<Script src="/critical.js" strategy="beforeInteractive" />
</>
);
}
监控与分析
性能分析
# 构建分析
npm run build -- --analyze
# 或使用 @next/bundle-analyzer
npm install -D @next/bundle-analyzer
配置:
// next.config.ts
import analyzer from "@next/bundle-analyzer";
const withBundleAnalyzer = analyzer({
enabled: process.env.ANALYZE === "true",
});
export default withBundleAnalyzer({
// 其他配置
});
日志记录
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const start = Date.now();
const response = NextResponse.next();
const duration = Date.now() - start;
console.log(`${request.method} ${request.nextUrl.pathname} - ${duration}ms`);
return response;
}
错误追踪
使用 Sentry 等工具:
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
});
最佳实践
1. 使用服务端组件
优先使用服务端组件,减少客户端 JavaScript:
// 服务端组件(默认)
export default async function Page() {
const data = await fetchData();
return <div>{data}</div>;
}
2. 合理使用缓存
// 静态数据:永久缓存
export const revalidate = false;
// 动态数据:定时重新验证
export const revalidate = 3600;
// 实时数据:不缓存
export const dynamic = "force-dynamic";
3. 优化第三方脚本
import Script from "next/script";
// 延迟加载非关键脚本
<Script src="/analytics.js" strategy="lazyOnload" />
4. 使用流式渲染
import { Suspense } from "react";
export default function Page() {
return (
<div>
<h1>标题</h1>
<Suspense fallback={<div>加载中...</div>}>
<SlowComponent />
</Suspense>
</div>
);
}
小结
本章我们学习了:
- Vercel 和自托管部署方式
- Docker 部署配置
- 图片和字体优化
- 代码分割和懒加载
- 缓存策略
- 性能监控
练习
- 将一个 Next.js 项目部署到 Vercel
- 使用 Docker 部署 Next.js 应用
- 分析并优化一个大型组件的加载性能
- 配置数据缓存策略