Docker部署Next.js前端应用的DynamicServerError笔记

Docker部署Next.js前端应用的DynamicServerError笔记

0. 背景

最近在尝试使用saleor这个开源项目,后台部署完成后,也想通过docker部署前端应用saleor-storefront,这是一个基于Next.js框架的前端应用。在使用Docker部署该Next.js前端应用时,遇到了DynamicServerError的问题。且翻阅了不少资料,该问题困扰了我半天了,今天得以解决。

1. 问题描述

这个应用生成后在云端采用pm2部署并管理,是没有任何问题的,但是,一旦使用docker部署,就会报DynamicServerError错误,具体表现为:

bash 复制代码
[root@VM-16-4-opencloudos saleor-storefront]# docker logs saleor-storefront --tail 30
   ▲ Next.js 16.0.10
   - Local:         http://771383eea4a0:3000
   - Network:       http://771383eea4a0:3000

 ✓ Starting...
 ✓ Ready in 419ms
[Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.] {
  digest: 'DYNAMIC_SERVER_USAGE'
}
 ⨯ [Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.] {
  digest: 'DYNAMIC_SERVER_USAGE'
}

其在外部通过SSL及域名访问时,表现为307反复重定向错误,实际上内部的链接已经出现了500内部错误了:

bash 复制代码
[root@VM-16-4-opencloudos saleor-storefront]# curl http://localhost:3000/default-channel
Internal Server Error

2. 问题分析与解决

通过查阅Next.js官方文档[2]及相关资料,发现DynamicServerError通常是由于在静态生成的页面中使用了动态数据或功能引起的。Next.js在构建时会尝试将页面静态化,如果页面中包含需要在运行时才能确定的数据(如数据库查询、API调用等),就会导致这个错误。

然而,我核对了很久,发现并没有该文档示例中的setTimer问题,于是我检索到了文档[1],发现动态路由的页面也可能导致这个错误的发生,我认真阅读了该开源项目的代码,发现确实有动态路由的页面,于是我决定将这些动态路由页面改为服务器端渲染(SSR),而不是静态生成(SSG)。

具体的页面是/src/app/[channel]/layout.tsx,我在该文件顶部添加了强制使用动态生成的代码:

typescript 复制代码
// Docker version adds this export to disable static optimization
// to avoid the error as below:
//  ⨯ [Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.] {
//   digest: 'DYNAMIC_SERVER_USAGE'
// }
export const dynamic = "force-dynamic";

import { type ReactNode } from "react";
import { executeGraphQL } from "@/lib/graphql";
import { ChannelsListDocument } from "@/gql/graphql";
import { DefaultChannelSlug } from "@/app/config";

...

删除容器和镜像,重新生成镜像和容器:

bash 复制代码
[root@VM-16-4-opencloudos saleor-storefront]# docker compose up -d
WARN[0000] /home/saleor/saleor-storefront/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion 
Compose can now delegate builds to bake for better performance.
 To do so, set COMPOSE_BAKE=true.
[+] Building 72.3s (26/26) FINISHED                                                                  docker:default
 => [saleor-storefront internal] load build definition from Dockerfile                                         0.0s
 => => transferring dockerfile: 2.29kB                                                                         0.0s
 => [saleor-storefront internal] load metadata for docker.io/library/node:20-alpine                            3.0s
 => [saleor-storefront internal] load .dockerignore                                                            0.0s
 => => transferring context: 113B                                                                              0.0s
 => [saleor-storefront internal] load build context                                                            0.0s
 => => transferring context: 26.73kB                                                                           0.0s
 => [saleor-storefront base 1/1] FROM docker.io/library/node:20-alpine@sha256:09e2b3d9726018aecf269bd35325f46  0.0s
 => CACHED [saleor-storefront builder 1/5] WORKDIR /app                                                        0.0s
 => CACHED [saleor-storefront deps 1/6] RUN apk add --no-cache libc6-compat                                    0.0s
 => CACHED [saleor-storefront deps 2/6] RUN apk add --no-cache curl                                            0.0s
 => CACHED [saleor-storefront deps 3/6] WORKDIR /app                                                           0.0s
 => CACHED [saleor-storefront deps 4/6] RUN corepack enable                                                    0.0s
 => CACHED [saleor-storefront deps 5/6] COPY package.json pnpm-lock.yaml ./                                    0.0s
 => CACHED [saleor-storefront deps 6/6] RUN pnpm i --frozen-lockfile --prefer-offline                          0.0s
 => CACHED [saleor-storefront builder 2/5] COPY --from=deps /app/node_modules ./node_modules                   0.0s
 => [saleor-storefront builder 3/5] COPY . .                                                                   0.1s
 => [saleor-storefront builder 4/5] RUN corepack enable                                                        0.3s
 => [saleor-storefront builder 5/5] RUN pnpm build                                                            64.8s
 => CACHED [saleor-storefront runner 2/9] RUN apk add --no-cache curl                                          0.0s 
 => CACHED [saleor-storefront runner 3/9] RUN addgroup --system --gid 1001 nodejs                              0.0s 
 => CACHED [saleor-storefront runner 4/9] RUN adduser --system --uid 1001 nextjs                               0.0s 
 => CACHED [saleor-storefront runner 5/9] COPY --from=builder /app/public ./public                             0.0s 
 => CACHED [saleor-storefront runner 6/9] RUN mkdir .next                                                      0.0s 
 => CACHED [saleor-storefront runner 7/9] RUN chown nextjs:nodejs .next                                        0.0s 
 => [saleor-storefront runner 8/9] COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./          0.3s
 => [saleor-storefront runner 9/9] COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static  0.0s
 => [saleor-storefront] exporting to image                                                                     1.5s
 => => exporting layers                                                                                        1.5s
 => => writing image sha256:a3fd1e623c6efafb4fc999209444467e048ebb8b4b3808f27f766a2b5d82cdfb                   0.0s
 => => naming to docker.io/library/saleor-storefront-saleor-storefront                                         0.0s
 => [saleor-storefront] resolving provenance for metadata file                                                 0.0s
[+] Running 3/3
 ✔ saleor-storefront                         Built                                                             0.0s 
 ✔ Network saleor-storefront_saleor_network  Created                                                           0.1s 
 ✔ Container saleor-storefront               Started                                                           0.2s

重新访问,问题解决:

bash 复制代码
[root@VM-16-4-opencloudos saleor-storefront]# curl http://localhost:3000/default-channel
<!DOCTYPE html><html lang="en" class="min-h-dvh"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="image" imageSrcSet="/_next/image?url=https%3A%2F%2Fapi...

3. 其它设定

3.1 Dockerfile

除上面发现问题和修改代码外,为了便于使用curl进行调试(包括容器内和容器外),我还在Dockerfile中添加了curl的安装命令,同时,考虑到该前端应用在public目录下有静态资源文件,我还添加了对该目录的拷贝命令,完整的Dockerfile如下:

dockerfile 复制代码
FROM node:20-alpine AS base

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
RUN apk add --no-cache curl
WORKDIR /app

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

COPY package.json pnpm-lock.yaml ./
RUN pnpm i --frozen-lockfile --prefer-offline

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1

ENV NEXT_OUTPUT=standalone
ARG NEXT_PUBLIC_SALEOR_API_URL
ENV NEXT_PUBLIC_SALEOR_API_URL=${NEXT_PUBLIC_SALEOR_API_URL}
ARG NEXT_PUBLIC_STOREFRONT_URL
ENV NEXT_PUBLIC_STOREFRONT_URL=${NEXT_PUBLIC_STOREFRONT_URL}
ARG NEXT_PUBLIC_DEFAULT_CHANNEL
ENV NEXT_PUBLIC_DEFAULT_CHANNEL=${NEXT_PUBLIC_DEFAULT_CHANNEL}

# Get PNPM version from package.json
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

RUN pnpm build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1

ARG NEXT_PUBLIC_SALEOR_API_URL
ENV NEXT_PUBLIC_SALEOR_API_URL=${NEXT_PUBLIC_SALEOR_API_URL}
ARG NEXT_PUBLIC_STOREFRONT_URL
ENV NEXT_PUBLIC_STOREFRONT_URL=${NEXT_PUBLIC_STOREFRONT_URL}

RUN apk add --no-cache curl
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs


CMD ["node", "server.js"]

.env文件等则按实际情况就行修改配置,在此就不做赘述了。

4. 总结

通过这次解决Next.js应用在Docker部署时遇到的DynamicServerError(DYNAMIC_SERVER_USAGE)问题,我深刻体会到理解框架的渲染机制和数据获取方式对于解决实际问题的重要性。Next.js提供了多种渲染方式(静态生成、服务器端渲染等),选择合适的方式对于应用的性能和用户体验都有显著影响。在未来的项目中,我将更加注重代码的结构设计和渲染策略的选择,以避免类似的问题再次发生。

该问题也让我困扰了半天,特记录下来备忘。

5. 参考资料

相关推荐
打小就很皮...6 小时前
《在 React/Vue 项目中引入 Supademo 实现交互式新手指引》
前端·supademo·新手指引
C澒6 小时前
系统初始化成功率下降排查实践
前端·安全·运维开发
摘星编程6 小时前
React Native + OpenHarmony:自定义useFormik表单处理
javascript·react native·react.js
Suchadar7 小时前
Docker常用命令
运维·docker·容器
C澒7 小时前
面单打印服务的监控检查事项
前端·后端·安全·运维开发·交通物流
pas1367 小时前
39-mini-vue 实现解析 text 功能
前端·javascript·vue.js
你才是臭弟弟7 小时前
MinIo开发环境配置方案(Docker版本)
运维·docker·容器
qq_532453537 小时前
使用 GaussianSplats3D 在 Vue 3 中构建交互式 3D 高斯点云查看器
前端·vue.js·3d
Swift社区7 小时前
Flutter 路由系统,对比 RN / Web / iOS 有什么本质不同?
前端·flutter·ios
七夜zippoe7 小时前
Docker容器化Python应用最佳实践:从镜像优化到安全防护
python·docker·云原生·eureka·容器化