NextJS 13.5.3 Docker构建失败:JSON输入意外结束错误求助
问题
本地执行yarn build完全正常,但Docker构建时失败,报错核心信息:
45.12 Collecting page data ... 46.10 SyntaxError: Unexpected end of JSON input
使用NextJS版本:13.5.3
完整Docker构建日志:
[+] Building 120.9s (12/12) FINISHED docker:desktop-linux => [internal] load .dockerignore 0.0s => => transferring context: 111B 0.0s => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 2.21kB 0.0s => [internal] load metadata for docker.io/library/node:alpine 3.3s => [auth] library/node:pull token for registry-1.docker.io 0.0s => [1/7] FROM docker.io/library/node:alpine@sha256:37750e51d61bef92165b2e29a77da4277ba0777258446b7a9c 0.0s => [internal] load build context 0.1s => => transferring context: 215.17kB 0.1s => CACHED [2/7] WORKDIR /app 0.0s => [3/7] RUN apk add yarn 3.1s => [4/7] COPY package.json yarn.lock ./ 0.0s => [5/7] RUN yarn install 67.4s => [6/7] COPY . . 0.1s => ERROR [7/7] RUN yarn build 46.9s ------ > [7/7] RUN yarn build: 0.725 yarn run v1.22.19 0.762 $ next build 1.357 Attention: Next.js now collects completely anonymous telemetry regarding usage. 1.357 This information is used to shape Next.js' roadmap and prioritize features. 1.357 You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: 1.357 https://nextjs.org/telemetry 1.357 1.445 Creating an optimized production build ... 34.52 ✓ Compiled successfully 34.53 Linting and checking validity of types ... 36.95 ⨯ ESLint: Failed to load config "prettier" to extend from. Referenced from: /app/.eslintrc.json 45.12 Collecting page data ... 46.10 SyntaxError: Unexpected end of JSON input 46.10 at JSON.parse (<anonymous>) 46.10 at 1634 (/app/.next/server/chunks/1669.js:1:162) 46.10 at t (/app/.next/server/webpack-runtime.js:1:143) 46.10 at 4582 (/app/.next/server/app/api/articles/[articleId]/questions/[descriptorId]/route.js:1:1251) 46.10 at t (/app/.next/server/webpack-runtime.js:1:143) 46.10 at t (/app/.next/server/app/api/articles/[articleId]/questions/[descriptorId]/route.js:1:3287) 46.10 at /app/.next/server/app/api/articles/[articleId]/questions/[descriptorId]/route.js:1:3350 46.10 at t.X (/app/.next/server/webpack-runtime.js:1:1275) 46.10 at /app/.next/server/app/api/articles/[articleId]/questions/[descriptorId]/route.js:1:3300 46.10 at Object.<anonymous> (/app/.next/server/app/api/articles/[articleId]/questions/[descriptorId]/route.js:1:3377) 46.11 46.11 > Build error occurred 46.12 Error: Failed to collect page data for /api/articles/[articleId]/questions/[descriptorId] 46.12 at /app/node_modules/next/dist/build/utils.js:1178:15 46.12 at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { 46.12 type: 'Error' 46.12 } 46.26 error Command failed with exit code 1. 46.26 info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. ------ Dockerfile:98 -------------------- 96 | 97 | # Build the application 98 | >>> RUN yarn build 99 | 100 | # Expose the port -------------------- ERROR: failed to solve: process "/bin/sh -c yarn build" did not complete successfully: exit code: 1
对应的API路由文件api/articles/questions/[descriptorId]/route.ts代码:
import db from "@/configs/firestore-config"; import * as z from "zod" import { authOptions } from "@/lib/nextauth"; import { getServerSession } from "next-auth"; import { NextRequest } from "next/server"; const routeContextSchema = z.object({ params: z.object({ articleId: z.string(), descriptorId: z.string(), }), }) const articleRecordSchema = z.object({ answer: z.string(), timeRecorded: z.number(), }) export async function POST( req: Request, context: z.infer<typeof routeContextSchema> ) { try { const { params } = routeContextSchema.parse(context) const articleId = params.articleId; const descriptorId = params.descriptorId; const session = await getServerSession(authOptions); if (!session) { return new Response(JSON.stringify({ message: 'Unauthorized', }), { status: 403 }) } const json = await req.json(); const body = articleRecordSchema.parse(json); const answer = body.answer; const timeRecorded = body.timeRecorded; console.log('answer', answer); console.log('timeRecorded', timeRecorded); const userId = session.user.id; // get article const articleRef = db.collection('articles').doc(articleId); const articleSnapshot = await articleRef.get(); const article = articleSnapshot.data(); console.log('descriptorId', descriptorId); // get question const question = article?.questions.multiple_choice_questions.find((question: { descriptor_id: string; }) => question.descriptor_id === descriptorId); // get correct answer const correctAnswer = question.answers.answer_a; // check answer const isCorrect = answer === correctAnswer; console.log('answer', answer); console.log('correctAnswer', correctAnswer); // creaet new user-article-record // if user-article-record exists, update it const userArticleRecordRef = db.collection('user-article-records').doc(`${userId}-${articleId}`); const userArticleRecordSnapshot = await userArticleRecordRef.get(); const updateRecordData = { articleId: articleId, userId: userId, questions: [ { descriptorId: descriptorId, isCorrect: isCorrect, } ], timeRecorded: timeRecorded, status: 'uncompleted', // uncompleted, completed createdAt: new Date(), updatedAt: new Date(), } if (userArticleRecordSnapshot.exists) { // update user-article-record const userArticleRecord = userArticleRecordSnapshot.data(); const questions = userArticleRecord?.questions; const question = questions.find((question: { descriptorId: string; }) => question.descriptorId === descriptorId); if (question) { question.isCorrect = isCorrect; } else { questions.push({ descriptorId: descriptorId, isCorrect: isCorrect, }) } await userArticleRecordRef.update({ questions: questions, timeRecorded: timeRecorded, updatedAt: new Date(), }); } else { // create new user-article-record await userArticleRecordRef.set(updateRecordData); } return new Response(JSON.stringify({ data: { isCorrect: isCorrect, correctAnswer: correctAnswer, } }), { status: 200 }) } catch (error) { console.log('error', error); return new Response(JSON.stringify({ message: 'Internal server error', error: `errors: ${error}` }), { status: 500 }) } }
解决方案
1. 修复ESLint配置警告
日志中先出现ESLint加载prettier配置失败的警告,虽然不是直接报错原因,但会影响构建环境稳定性:
- 检查
.eslintrc.json的extends数组,确认prettier配置项正确 - 执行
yarn add eslint-config-prettier --dev安装依赖,再重新构建
2. 解决构建阶段API路由的JSON解析错误
核心问题是NextJS构建时会静态分析API路由代码,Docker环境缺少本地的环境变量或数据库连接权限,导致数据异常触发JSON解析失败:
2.1 跳过构建阶段的数据库操作
在API路由的POST函数开头添加判断,避免构建时执行数据库逻辑:
// 构建阶段直接返回,不执行数据库操作 if (process.env.NEXT_PHASE === 'build') { return new Response(JSON.stringify({ message: 'Build phase skip' }), { status: 200 }); }
2.2 同步Docker环境变量
本地构建依赖.env.local里的Firestore配置,但Docker构建时未传递这些变量:
- 在Dockerfile中添加环境变量,或使用
docker build --env-file .env传递配置文件 - 确保Firestore的项目ID、密钥等配置在Docker构建时可用
2.3 补充空值检查避免异常
代码中存在多处未处理的空值访问(如article?.questions、question.answers),构建时数据库返回空数据会触发异常:
// 获取文章后检查是否存在 if (!article) { return new Response(JSON.stringify({ message: 'Article not found' }), { status: 404 }); } // 获取问题后检查是否存在 const question = article.questions?.multiple_choice_questions?.find(q => q.descriptor_id === descriptorId); if (!question) { return new Response(JSON.stringify({ message: 'Question not found' }), { status: 404 }); } // 检查问题答案是否存在 if (!question.answers) { return new Response(JSON.stringify({ message: 'Question answers missing' }), { status: 404 }); } const correctAnswer = question.answers.answer_a;
2.4 调整Docker构建依赖逻辑
确保Docker构建时安装完整依赖(包括开发依赖):
修改Dockerfile中的安装命令:
RUN yarn install --production=false
3. 本地模拟生产构建验证
执行NODE_ENV=production yarn build,复现Docker构建环境,快速排查问题,无需每次构建镜像
内容的提问来源于stack exchange,提问作者PssK
相关产品推荐
相关产品推荐

