ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Next.js 自托管部署完全指南:从 standalone 输出、Docker 多阶段构建到多实例 ISR 缓存

Next.js 自托管部署完全指南:从 standalone 输出、Docker 多阶段构建到多实例 ISR 缓存 前端UI组件【免费下载链接】next-shadcn-dashboard-starterFree, open source, AI-friendly admin dashboard template built with Next.js 16, shadcn/ui, Tailwind CSS, and TypeScript. Production-ready tables, forms, auth, and billing. MIT licensed.项目地址https://gitcode.com/gh_mirrors/ne/next-shadcn-dashboard-starter点击查看免费下载导读本文是围绕 next-shadcn-dashboard-starter 项目中.agents/skills/next-best-practices/self-hosting.md技能文档展开的自托管部署实战指南覆盖「脱离 Vercel、在自有服务器或容器环境运行 Next.js 16 应用」的完整路径。你将掌握output: standalone最小产物原理、Docker 多阶段构建与 Compose 编排、PM2 集群部署、多实例下的 ISR 共享缓存方案Redis / S3 自定义 cache handler、图片优化与运行期配置的取舍并得到一份可直接落地的部署前检查清单。文中所有配置均可对照本仓库的 Dockerfile、next.config.ts 等真实文件逐行验证。一、为什么要在 Vercel 之外自托管Next.js 默认的托管方式Vercel开箱即用但企业内网、私有化交付、合规要求、成本控制以及需要紧贴自有基础设施的场景往往要求把应用部署在自有服务器、Kubernetes 集群或云厂商容器服务上。此时需要主动处理几件 Vercel 替你完成的事产物形态默认构建输出体积大、耦合开发依赖不适合容器镜像静态资源.next/static与public/需要手动跟随产物拷贝多实例状态ISR 的文件系统缓存与实例本地磁盘绑定负载均衡下会相互矛盾运行期配置NEXT_PUBLIC_*变量在构建期被编译进产物无法热改。本仓库的部署配套两个 Dockerfile、BUILD_STANDALONE开关、非 root 运行用户、健康检查就绪的 Compose 思路正是围绕上述问题给出的工程化答案下文将逐一展开。二、Quick Start开启 Standalone 输出模式对 Docker 或任何容器化部署第一步是在 Next 配置中开启 standalone 输出// next.config.js module.exports { output: standalone };开启后构建会生成一个只包含生产依赖的最小standalone目录结构如下.next/ ├── standalone/ │ ├── server.js # Entry point │ ├── node_modules/ # Only production deps │ └── .next/ # Build output └── static/ # Must be copied separately要点解读server.js是独立的 Node 入口不再需要next start直接node server.js即可启动node_modules只保留生产依赖镜像体积显著缩小.next/static与public/不会被自动包含在 standalone 产物内容器镜像构建时必须单独COPY否则样式、图片、favicon 全部丢失HOSTNAME需显式设置为0.0.0.0容器内默认监听 localhost 会导致端口无法对外暴露。本仓库的实际做法用环境变量控制开关本仓库没有把output写死而是在 next.config.ts 中用环境变量动态开启output: process.env.BUILD_STANDALONE true ? standalone : undefined,对应的变量说明见 env.example.txt# Set to true when deploying with Docker or self-hosting on a VPS. # This enables Next.js standalone output mode for smaller deployments. BUILD_STANDALONE # Example: true这样本地npm run dev/npm run build走常规模式只有显式声明BUILD_STANDALONEtrue时才产出 standalone 目录。两个 Dockerfile 均在构建阶段通过ENV BUILD_STANDALONEtrue激活此开关见 Dockerfile 与 Dockerfile.bun。三、Docker 部署多阶段构建1. 标准 Dockerfile技能文档给出的多阶段 Dockerfile 是通用基线注释已补充各阶段职责FROM node:20-alpine AS base # Install dependencies FROM base AS deps WORKDIR /app COPY package.json package-lock.json* ./ RUN npm ci # Build FROM base AS builder WORKDIR /app COPY --fromdeps /app/node_modules ./node_modules COPY . . RUN npm run build # Production FROM base AS runner WORKDIR /app ENV NODE_ENVproduction # Create non-root user RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs # Copy standalone output COPY --frombuilder /app/.next/standalone ./ COPY --frombuilder /app/.next/static ./.next/static COPY --frombuilder /app/public ./public USER nextjs EXPOSE 3000 ENV PORT3000 ENV HOSTNAME0.0.0.0 CMD [node, server.js]设计要点三阶段隔离deps只装依赖善用 Docker 层缓存builder负责编译runner只保留运行所需文件避免构建工具与源码进入最终镜像非 root 运行创建nodejs组与nextjs用户后USER nextjs降低容器逃逸风险静态资源补齐standalone、.next/static、public三份拷贝缺一不可。2. 本仓库 Dockerfile 的增强实现本仓库的 Dockerfile 在该基线上做了几处贴合项目实际的增强可直接对照阅读Node 版本升级到22-slimARG NODE_VERSION22-slim允许在docker build --build-arg NODE_VERSION...时覆盖使用 bun 安装依赖npm install -g bun后执行bun install --no-save --frozen-lockfile并使用 BuildKit 缓存挂载RUN --mounttypecache,target/root/.bun/install/cache加速重复构建--frozen-lockfile与仓库根目录的 bun.lock 对应保证可复现构建构建期参数下沉将 Clerk、Sentry 相关变量声明为ARG允许通过--build-arg或 compose 注入ARG NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ARG NEXT_PUBLIC_CLERK_SIGN_IN_URL/auth/sign-in ARG NEXT_PUBLIC_CLERK_SIGN_UP_URL/auth/sign-up ARG NEXT_PUBLIC_SENTRY_DISABLEDtrue预创建.next目录并授权RUN mkdir .next chown node:node .next确保 Node 运行期有权限写 ISR 预渲染缓存--chownnode:node拷贝所有COPY --frombuilder --chownnode:node ...保证非 root 用户可读关闭遥测ENV NEXT_TELEMETRY_DISABLED1。若你的环境以 bun 为运行时仓库还提供了 Dockerfile.bun基础镜像换成oven/bun:1安装步骤去掉npm install -g bun运行入口改为CMD [bun, server.js]其余结构完全一致。构建与启动命令docker build -t next-shadcn-dashboard . docker run -p 3000:3000 next-shadcn-dashboard3. Docker Compose 编排技能文档给出了带健康检查的 Compose 配置version: 3.8 services: web: build: . ports: - 3000:3000 environment: - NODE_ENVproduction restart: unless-stopped healthcheck: test: [CMD, wget, -q, --spider, http://localhost:3000/api/health] interval: 30s timeout: 10s retries: 3健康检查要求应用提供GET /api/health端点本文第十节给出实现。若镜像内没有wget如 distroless 或未装 busybox 的 Alpine可将test替换为[CMD, node, -e, fetch(http://localhost:3000/api/health).then(r{if(!r.ok)process.exit(1)}).catch(()process.exit(1))]这类 Node 内置 fetch 写法。四、PM2 部署传统服务器场景不采用容器时PM2 是经典的守护进程 进程管理方案。技能文档给出的 ecosystem 配置如下// ecosystem.config.js module.exports { apps: [ { name: nextjs, script: .next/standalone/server.js, instances: max, exec_mode: cluster, env: { NODE_ENV: production, PORT: 3000 } } ] };启动流程npm run build pm2 start ecosystem.config.js关键参数说明script指向 standalone 入口前提是构建时开启了output: standalone本仓库通过BUILD_STANDALONEtrue触发见 next.config.tsinstances: maxexec_mode: cluster按 CPU 核数拉起多进程由 PM2 内置负载均衡分发请求多进程注意一旦超过单进程ISR 文件系统缓存问题就会出现必须配合第五节的自定义缓存处理器对无状态 SSR 应用则无额外要求。补充运维建议配合pm2 save与pm2 startup实现开机自启如需零停机发布可在ecosystem.config.js中加入max_restarts、wait_ready等字段。五、ISR 与自定义缓存处理器多实例的正确姿势1. 问题本质ISRIncremental Static Regeneration默认使用文件系统缓存这在单实例下工作正常但多实例部署时会直接失效实例 A 重新生成页面 → 写入 A 的本地磁盘实例 B 继续返回旧页面 → 看不到 A 的缓存负载均衡把用户随机分发到不同实例 → 同一 URL 出现不一致内容。2. 开启自定义缓存处理器Next.js 14 支持通过配置挂载自定义 cache handler把缓存从「每实例磁盘」提升为「共享存储」// next.config.js module.exports { cacheHandler: require.resolve(./cache-handler.js), cacheMaxMemorySize: 0 // Disable in-memory cache };cacheMaxMemorySize: 0用于关闭内存缓存层确保读写全部走共享存储避免多实例间内存缓存不一致。3. Redis 缓存处理器实现// cache-handler.js const Redis require(ioredis); const redis new Redis(process.env.REDIS_URL); const CACHE_PREFIX nextjs:; module.exports class CacheHandler { constructor(options) { this.options options; } async get(key) { const data await redis.get(CACHE_PREFIX key); if (!data) return null; const parsed JSON.parse(data); return { value: parsed.value, lastModified: parsed.lastModified }; } async set(key, data, ctx) { const cacheData { value: data, lastModified: Date.now() }; // Set TTL based on revalidate option if (ctx?.revalidate) { await redis.setex(CACHE_PREFIX key, ctx.revalidate, JSON.stringify(cacheData)); } else { await redis.set(CACHE_PREFIX key, JSON.stringify(cacheData)); } } async revalidateTag(tags) { // Implement tag-based invalidation // This requires tracking which keys have which tags } };实现要点所有键统一加CACHE_PREFIX前缀避免与其他业务键冲突get必须返回{ value, lastModified }结构Next.js 内部据此判断页面是否过期set接收第三个参数ctx当ctx.revalidate存在时用setex写入 TTL对应页面设置的 revalidate 秒数实现缓存自动过期revalidateTag是revalidateTag()触发点需要在写入缓存时额外记录「key → tags」的索引如 Redis Set再在撤销时批量删除关联 key——文档中已明确标注这是需要补齐的部分切勿在未实现索引的情况下调用revalidateTag。4. S3 缓存处理器实现无 Redis 时对象存储同样可行注意其按量计费与延迟特性更适合低 QPS 场景// cache-handler.js const { S3Client, GetObjectCommand, PutObjectCommand } require(aws-sdk/client-s3); const s3 new S3Client({ region: process.env.AWS_REGION }); const BUCKET process.env.CACHE_BUCKET; module.exports class CacheHandler { async get(key) { try { const response await s3.send( new GetObjectCommand({ Bucket: BUCKET, Key: cache/${key} }) ); const body await response.Body.transformToString(); return JSON.parse(body); } catch (err) { if (err.name NoSuchKey) return null; throw err; } } async set(key, data, ctx) { await s3.send( new PutObjectCommand({ Bucket: BUCKET, Key: cache/${key}, Body: JSON.stringify({ value: data, lastModified: Date.now() }), ContentType: application/json }) ); } };要点读取时把NoSuchKey映射为null缓存未命中其余错误应抛出以便监控发现S3 键空间使用cache/${key}前缀组织set中同样应依据ctx.revalidate设置对象生命周期如 Lifecycle 规则或Expires头来模拟 TTL。5. 缓存处理器上线前必须验证技能文档特别强调每次升级 Next.js 后都要重测缓存处理器。标准验证流程是多实例对比法# Start multiple instances PORT3001 node .next/standalone/server.js PORT3002 node .next/standalone/server.js # Trigger ISR revalidation curl http://localhost:3001/api/revalidate?path/posts # Verify both instances see the update curl http://localhost:3001/posts curl http://localhost:3002/posts # Should return identical content只有两个端口返回一致内容才证明 ISR 缓存已真正共享。六、开箱即用 vs 需要额外配置技能文档用一张表格界定了「单实例 / 多实例」两种拓扑下的能力边界这是规划部署形态时最重要的参考FeatureSingle InstanceMulti-InstanceNotesSSRYesYesNo special setupSSGYesYesBuilt at deploy timeISRYesNeeds cache handlerFilesystem cache breaksImage OptimizationYesYesCPU-intensive, consider CDNMiddlewareYesYesRuns on Node.jsEdge RuntimeLimitedLimitedSome features Node-onlyrevalidatePath/TagYesNeeds cache handlerMust share cachenext/fontYesYesFonts bundled at buildDraft ModeYesYesCookie-based结合本仓库的补充说明MiddlewareNext.js 16 已将middleware.ts更名为proxy.ts本仓库的路由保护逻辑位于 src/proxy.ts它基于clerkMiddleware保护/dashboard(.*)路径自托管时该逻辑在 Node.js 运行时执行无需特殊处理revalidatePath/revalidateTag本仓库的 dashboard 页面大量依赖动态渲染若横向扩容超过一个实例必须与第五节缓存处理器同步落地next/font字体在构建期被打包不产生运行时外部请求自托管无额外成本——本仓库在 next.config.ts 中配置了transpilePackages: [geist]以保证字体包正确编译。七、图片优化内置 vs 外部 LoaderNext.js 图片优化next/image默认开箱即用但优化过程由 CPU 完成高流量下会消耗大量计算资源。方案一内置优化简单通过配置限制生成尺寸、延长缓存减少重复计算// next.config.js module.exports { images: { minimumCacheTTL: 60 * 60 * 24, // 24 hours deviceSizes: [640, 750, 1080, 1920] // Limit sizes } };deviceSizes决定为响应式布局生成的候选宽度条目越少生成的变体越少minimumCacheTTL控制优化结果的缓存时长值越大重复计算越少。方案二外部 Loader规模化推荐把优化职责外包给 Cloudinary、Imgix 等 CDN// next.config.js module.exports { images: { loader: custom, loaderFile: ./lib/image-loader.js } };// lib/image-loader.js export default function cloudinaryLoader({ src, width, quality }) { const params [f_auto, c_limit, w_${width}, q_${quality || auto}]; return https://res.cloudinary.com/demo/image/upload/${params.join(,)}${src}; }loaderFile导出的函数接收{ src, width, quality }拼接出 CDN 的转换 URLnext/image会按此 URL 直接请求本地不再执行图像处理。本仓库的远程图片配置本仓库采用内置优化但对外部域名图片做了白名单配置见 next.config.tsimages: { remotePatterns: [ { protocol: https, hostname: api.slingacademy.com, port: }, { protocol: https, hostname: img.clerk.com, port: }, { protocol: https, hostname: clerk.com, port: } ] }自托管后若沿用内置优化务必同步维护这份remotePatterns白名单——否则next/image对未授权域名的远程图会直接报错。依赖sharp见 package.json 依赖清单的本地优化对内存敏感建议对实例内存设上限或在负载均衡层做并发控制。八、环境变量构建期 vs 运行时1. 两类变量的本质区别// Available at build time only (baked into bundle) NEXT_PUBLIC_API_URLhttps://api.example.com // Available at runtime (server-side only) DATABASE_URLpostgresql://... API_SECRET...NEXT_PUBLIC_*前缀的变量在构建时被内联进客户端 bundle构建后修改不生效改完必须重新构建镜像无前缀变量只在服务端可用运行容器时可通过-e/ Composeenvironment动态注入。2. 运行时配置的标准姿势需要「真·运行时可变」的前端配置时不要用NEXT_PUBLIC_*而是暴露一个配置接口由客户端启动时拉取// app/api/config/route.ts export async function GET() { return Response.json({ apiUrl: process.env.API_URL, features: process.env.FEATURES?.split(,) }); }在本仓库中对应路径为src/app/api/config/route.ts沿用仓库src/app/api/...的路由约定参照现有 products 与 users 路由的写法。3. 本仓库的变量全景env.example.txt 完整登记了本项目的环境变量按主题可归纳为分组变量说明认证ClerkNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY、CLERK_SECRET_KEY支持 keyless 模式留空可先跑起来再领取密钥认证跳转NEXT_PUBLIC_CLERK_SIGN_IN_URL、NEXT_PUBLIC_CLERK_SIGN_UP_URL、NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL、NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL控制登录/注册后的去向构建BUILD_STANDALONEtrue时启用 standalone 输出Docker / VPS 自托管必须设置错误追踪SentryNEXT_PUBLIC_SENTRY_DSN、NEXT_PUBLIC_SENTRY_ORG、NEXT_PUBLIC_SENTRY_PROJECT、SENTRY_AUTH_TOKEN、NEXT_PUBLIC_SENTRY_DISABLED前三者在 src/instrumentation.ts 与 src/instrumentation-client.ts 中被读取NEXT_PUBLIC_SENTRY_DISABLEDtrue时整体关闭 Sentry 逻辑计费 WebhookWEBHOOK_SECRET生产环境 Clerk Webhook 验签用对应到 Docker 构建链路NEXT_PUBLIC_*变量通过 Dockerfile 的ARG在构建期注入见 Dockerfile运行期仅由容器环境提供服务端变量——这是自托管最容易踩坑的地方构建前务必核对所有NEXT_PUBLIC_*的值。九、OpenNext无需 Vercel 的 Serverless 部署若目标是 Serverless 形态无服务器容器、按请求计费OpenNext 负责把 Next.js 适配到 AWS Lambda、Cloudflare Workers 等平台npx create-sstlatest # or npx opennextjs/aws build支持的平台包括AWS Lambda CloudFrontCloudflare WorkersNetlify FunctionsDeno DeployOpenNext 与 standalone 方案的选择逻辑容器/虚拟机环境优先 standalone本文第三节需要 Serverless 弹性伸缩时再评估 OpenNext两者的 ISR 多实例问题第五节同样存在都需要共享缓存支撑。十、健康检查端点负载均衡的前提负载均衡器与编排平台依赖健康检查决定流量分发。技能文档给出的标准实现// app/api/health/route.ts export async function GET() { try { // Optional: check database connection // await db.$queryRawSELECT 1; return Response.json({ status: healthy }, { status: 200 }); } catch (error) { return Response.json({ status: unhealthy }, { status: 503 }); } }正常返回200 { status: healthy }依赖异常时返回503 { status: unhealthy }是否在健康检查内探测数据库等下游依赖需要权衡探得太深会导致「依赖抖动 → 实例被摘除」探得太浅又无法反映真实可用性建议至少探测关键持久层本仓库当前没有内置该路由自托管前按src/app/api/health/route.ts路径补充即可它同时被第三节 Compose 的healthcheck引用。十一、部署前检查清单技能文档给出了 9 项清单结合本仓库逐条落实如下本地先构建npm run build在 CI 前于本地暴露编译错误本仓库脚本见 package.json 的build字段本地试跑 standalone 产物node .next/standalone/server.js先于 Docker 验证产物完整性开启output: standaloneDocker 场景必做本仓库通过BUILD_STANDALONEtrueDockerfile触发勿遗漏该环境变量配置缓存处理器多实例 ISR 必做第五节单实例可跳过设置HOSTNAME0.0.0.0容器内监听所有网卡本仓库两个 Dockerfile 均已预设拷贝public/与.next/static/standalone 不包含它们详见第二节目录结构本仓库在 runner 阶段通过两条COPY完成Dockerfile添加健康检查端点见第十节并接入 Composehealthcheck部署后实测 ISR 重新验证用第五节的多端口 curl 对比法确认缓存共享监控内存Node.js 默认堆上限约 2GB在图片优化或大页面下可能不足可在容器或 PM2 中显式调优如NODE_OPTIONS--max-old-space-size4096同时关注sharp等原生依赖的内存占用。结语自托管 Next.js 的本质是把 Vercel 隐含的「产物裁剪、静态资源归位、缓存共享、运行期配置」四项能力显式化。以本仓库为参照BUILD_STANDALONE开关 Dockerfile 解决产物与镜像问题自定义 cache handler 解决多实例一致性remotePatterns与NEXT_PUBLIC_*边界管理解决配置问题。将本文清单逐项落地即可在自有基础设施上获得与托管平台等效的稳定性而每次升级 Next.js 时重跑「多实例缓存对比测试」是这套方案长期可靠的关键保障。赞分享前端UI组件【免费下载链接】next-shadcn-dashboard-starterFree, open source, AI-friendly admin dashboard template built with Next.js 16, shadcn/ui, Tailwind CSS, and TypeScript. Production-ready tables, forms, auth, and billing. MIT licensed.项目地址https://gitcode.com/gh_mirrors/ne/next-shadcn-dashboard-starter点击查看免费下载相关推荐Next.js 自托管部署实战指南Standalone 输出、Docker/PM2 与多实例 ISR 缓存preguntas-entrevista-react 技能文档深度解读Next.js 自托管部署实战指南Standalone 输出、Docker/PM2 与多实例 ISR 缓存preguntas entrevista reac前端教程create-t3-app 的 Docker 容器化部署实战多阶段构建、Standalone 输出与 Compose 编排create t3 app 的 Docker 容器化部署实战多阶段构建、Standalone 输出与 Compose 编排 本文基于 create t3 ap开发工具CLI代码生成Redwood Docker 部署完全指南从 yarn rw setup docker 到生产级多阶段构建Redwood Docker 部署完全指南从 yarn rw setup docker 到生产级多阶段构建 导读 本文围绕 Redwood 官方提供的 Doc后端前端Web框架开发工具上一篇别再用AltTab救场了Boss-Key老板键一套让敏感窗口一键蒸发的终极方案下一篇老游戏在Windows 10/11疯狂闪退这款免费兼容性补丁如何彻底修复游戏崩溃创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表