output: 'standalone'은 next build가 프로덕션 실행에 필요한 파일만 골라 .next/standalone 폴더에 모아 주는 빌드 출력 모드다. 이 폴더는 node_modules를 다시 설치하지 않고 node server.js 하나로 실행된다.
기본 모드와 무엇이 다른가
기본 모드의 .next는 혼자서는 돌지 않는다. next start를 하려면 옆에 package.json과 dependencies 전체가 설치된 node_modules가 있어야 한다. 그래서 배포 서버에서 소스를 받고 패키지를 설치한 뒤 빌드 결과를 덮어쓰는 식으로, 실행 직전에 "조립"하게 된다.
standalone은 빌드 때 이 조립을 끝낸다.
- 파일 트레이싱(Output File Tracing): 빌드 중
@vercel/nft가import·require·fs사용을 정적으로 분석해 각 페이지가 불러올 수 있는 파일을 모은다. standalone은 그 목록대로node_modules의 필요한 파일만 복사한다 - 최소 서버:
nextCLI 대신 쓰는server.js가 함께 나온다. 포트와 호스트는PORT·HOSTNAME환경 변수로 정한다
.next/standalone/
server.js next CLI 없이 실행하는 서버 진입점
package.json
node_modules/ 트레이싱으로 추린 패키지만
.next/ 서버 렌더에 필요한 빌드 결과직접 복사해야 하는 두 폴더
public/과 .next/static(JS 청크·CSS)은 기본으로 들어가지 않는다. 문서는 이 정적 파일을 CDN이 서빙하는 구성을 전제로 한다. 서버가 직접 서빙하게 하려면 빌드 뒤 복사한다. 그러면 server.js가 알아서 서빙한다.
cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/환경마다 구성이 다르면 함정이 된다. 정적 자산을 CDN(assetPrefix)으로 보내는 운영 환경은 복사를 빠뜨려도 멀쩡하다. 그런데 CDN 없이 노드가 직접 서빙하는 검증 환경만 화면이 깨진다. 운영에서 통과했다고 안심하면 안 된다.
트레이싱 조정
outputFileTracingRoot: 모노레포에서는 기본 트레이싱 루트가 앱 폴더라 바깥 공용 패키지가 빠진다. 루트를 레포 최상단으로 올린다outputFileTracingIncludes·outputFileTracingExcludes: 트레이싱이 놓치는 파일을 넣거나, 쓰지 않는데 딸려 오는 파일을 뺀다. 키는 라우트 글롭('/*','/api/*'), 값은 프로젝트 루트 기준 파일 글롭이다. Edge 런타임 라우트와 완전 정적 페이지에는 적용되지 않는다
module.exports = {
output: 'standalone',
images: { unoptimized: true }, // 이미지 최적화를 안 쓴다면
outputFileTracingExcludes: {
'/*': ['node_modules/sharp/**/*', 'node_modules/@img/**/*'],
},
}위 예는 문서의 공식 예가 아니다(문서는 반대로 outputFileTracingIncludes로 sharp를 넣는 예를 든다). 이처럼 쓰지 않는 네이티브 모듈을 빼면 결과물이 작아질 뿐 아니라, 빌드 머신과 실행 머신의 CPU 아키텍처를 맞춰야 하는 제약도 풀린다(CPU 아키텍처와 네이티브 모듈).
컨테이너와 잘 맞는 이유
폴더 하나가 곧 배포 단위라서 Dockerfile: 레이어 캐시와 멀티 스테이지의 실행 스테이지가 COPY 몇 줄과 CMD ["node", "server.js"]로 끝난다. 이미지 안에서 다시 설치하지 않으니 이미지가 작고, devDependencies나 소스 코드가 실행 이미지에 들어가지 않는다. 컨테이너가 없는 기존 VM 배포에서도 "설치 단계 삭제, 실행 명령 교체"만으로 먼저 검증해 볼 수 있다.
COPY .next/standalone ./
COPY .next/static ./.next/static
COPY public ./public
CMD ["node", "server.js"]