跳到主要内容

部署与优化

本章将介绍 Next.js 应用的部署方式和性能优化技巧。

部署方式

Vercel 部署(推荐)

Vercel 是 Next.js 的官方托管平台,提供最佳的开发体验。

步骤:

  1. 将代码推送到 GitHub
  2. 在 Vercel 导入项目
  3. 自动检测 Next.js 并配置
  4. 点击部署

优势:

  • 零配置部署
  • 自动 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>
);
}

小结

本章我们学习了:

  1. Vercel 和自托管部署方式
  2. Docker 部署配置
  3. 图片和字体优化
  4. 代码分割和懒加载
  5. 缓存策略
  6. 性能监控

练习

  1. 将一个 Next.js 项目部署到 Vercel
  2. 使用 Docker 部署 Next.js 应用
  3. 分析并优化一个大型组件的加载性能
  4. 配置数据缓存策略