Next.js는 기본적으로 next start 명령어를 통해 자체적인 서버를 구동할 수 있도록 포함하고 있어요. 만약 이미 사용 중인 기존 백엔드(Node.js, Spring, Django 등) 서버가 있다면, Next.js와 함께 연동해서 사용하는 것도 당연히 가능하답니다 (이걸 커스텀 서버라고 부르진 않아요).
Next.js의 커스텀 서버는 여러분이 직접 코드를 작성해서(programmatically) 특수한 패턴의 서버를 시작할 수 있게 해주는 기능이에요. 솔직히 말씀드리면, 대부분의 상황에서는 이 방식이 전혀 필요하지 않으실 거예요. 하지만 프레임워크의 기본 틀에서 벗어나(eject) 서버를 완전히 직접 제어해야 할 상황이 생긴다면 언제든 사용할 수 있도록 열려있는 기능입니다.
💡 강사의 부연 설명:
프론트엔드 개발자로 포트폴리오를 만드실 때, 예를 들어 'Velog 뷰어'나 'AI 문서 번역기' 같은 프로젝트를 구성하신다면 단순히 데이터를 불러오고 화면을 그리는 용도일 테니 기본 Next.js 내장 서버만으로도 차고 넘칩니다. 커스텀 서버는 보통 'Next.js와 동일한 포트에서 무거운 웹소켓(WebSocket) 서버를 돌려야 할 때'나, '매우 복잡하고 동적인 프록시(Proxy) 로직이 필요할 때' 등 아주 특수한 경우에만 사용한답니다.
알아두면 좋은 점 (Good to know):
- 커스텀 서버를 사용하기로 결정하기 전에, 이 기능은 Next.js의 내장 라우터만으로는 도저히 앱의 요구사항을 충족할 수 없을 때만 사용해야 한다는 걸 꼭 명심하세요. 커스텀 서버를 사용하면 자동 정적 최적화 (Automatic Static Optimization) 와 같은 Next.js의 정말 중요한 성능 최적화 기능들이 제거되어 버리거든요.
standalone출력 모드를 사용할 때는 커스텀 서버 파일들을 추적하지 않아요. 이 모드는 앱을 실행하는 데 필요한 최소한의 파일만 모아서 별도의 가벼운server.js파일을 만들어 내기 때문이죠. 따라서 커스텀 서버와 standalone 모드는 함께 사용할 수 없답니다.
🎯 강사의 면접 팁!
면접관이 "Next.js에서 커스텀 서버를 써보셨나요? 일반적인 방식과 어떤 차이가 있죠?"라고 묻는다면, 바로 저 '자동 정적 최적화(ASO) 상실'을 핵심으로 짚어주세요.
"특별한 서버 로직이 필요한 게 아니라면, Next.js가 페이지를 정적으로 빌드해서 렌더링 성능을 극대화해 주는 이점을 포기하면서까지 커스텀 서버를 쓸 이유는 없다고 판단했습니다."라고 논리적으로 대답하시면, 프론트엔드 최적화와 프레임워크에 대한 이해도가 깊다는 좋은 인상을 줄 수 있습니다.
자, 그럼 커스텀 서버가 어떻게 생겼는지 다음 예제(following example)를 한번 살펴볼까요?
import { createServer } from 'http'
import next from 'next'
const port = parseInt(process.env.PORT || '3000', 10)
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
handle(req, res)
}).listen(port)
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? 'development' : process.env.NODE_ENV
}`
)
})
import { createServer } from 'http'
import next from 'next'
const port = parseInt(process.env.PORT || '3000', 10)
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
handle(req, res)
}).listen(port)
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? 'development' : process.env.NODE_ENV
}`
)
})
server.js파일은 Next.js의 컴파일러나 번들링 과정을 거치지 않아요. 그렇기 때문에 이 파일에서 사용하는 문법이나 요구하는 소스 코드들이 여러분이 현재 사용하고 있는 Node.js 버전과 호환되는지 반드시 확인하셔야 해요. 예제 보기(View an example).
💡 강사의 부연 설명:
Next.js 앱 안에서 우리가 React 코드를 작성할 땐 Babel이나 SWC가 최신 문법을 알아서 변환해 주지만, 저server.js파일은 순수하게 Node.js 환경에서 맨몸으로 실행된다는 뜻이에요. 만약 Node.js 버전이 낮다면 최신 ES 모듈 문법(import,export)을 바로 쓰지 못하고 오류가 날 수 있으니 주의해야 한답니다!
이 커스텀 서버를 실행하려면, package.json 안의 scripts 부분을 다음과 같이 업데이트해 주시면 됩니다.
{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
}
이 방법 대신에, 코드가 변경될 때마다 서버를 자동으로 재시작해주는 nodemon을 설정할 수도 있어요 (예제 링크). 이 커스텀 서버는 서버와 Next.js 애플리케이션을 서로 연결하기 위해 다음과 같은 import 문을 사용해요.
import next from 'next'
const app = next({})
위 코드에서 불러온 next 함수는 다음과 같은 옵션들이 들어있는 객체를 매개변수로 받습니다.
| 옵션 (Option) | 타입 (Type) | 설명 (Description) |
|---|---|---|
conf | Object | next.config.js 파일에서 사용하는 것과 동일한 설정 객체예요. 기본값은 {} (빈 객체) 입니다. |
dev | Boolean | (선택 사항) Next.js를 개발(dev) 모드로 실행할지 여부를 결정해요. 기본값은 false 예요. |
dir | String | (선택 사항) Next.js 프로젝트가 위치한 폴더 경로예요. 기본값은 '.' (현재 디렉토리) 예요. |
quiet | Boolean | (선택 사항) 서버 정보가 포함된 에러 메시지를 화면에 숨길지 결정해요. 기본값은 false 예요. |
hostname | String | (선택 사항) 서버가 실행되고 있는 호스트 이름(hostname)이에요. |
port | Number | (선택 사항) 서버가 실행되고 있는 포트(port) 번호예요. |
httpServer | node:http#Server | (선택 사항) Next.js가 실행되는 기반 HTTP 서버 객체예요. |
turbopack | Boolean | (선택 사항) Turbopack(터보팩)을 활성화할지 여부예요. (기본적으로 활성화되어 있어요) |
webpack | Boolean | (선택 사항) Webpack(웹팩)을 활성화할지 여부예요. |
이렇게 해서 반환된 app 객체를 사용하면, 여러분이 필요한 상황에 맞게 Next.js가 HTTP 요청들을 처리하도록 자유롭게 제어할 수 있게 된답니다.
모든 공식 문서의 의미론적 개요(semantic overview)를 보시려면, https://nextjs.org/docs/sitemap.md 문서를 확인해 보세요.
사용 가능한 모든 문서의 색인(index)을 확인하시려면, https://nextjs.org/docs/llms.txt 문서를 참고해 주세요.