개발 서버 실행 및 기본 설정
개발 서버의 코드 감시와 오류 표시를 확인하고 포트·이미지·엄격 모드·환경 변수의 기본 설정을 조정합니다.
Next.js 프로젝트의 구조를 이해했다면, 이제 개발의 중심인 개발 서버(Development Server)를 더 자세히 살펴보고 기본 설정으로 개발 환경을 다듬는 방법을 알아봅니다. 개발 서버는 작성한 코드를 곧바로 브라우저에 반영해, 결과를 빠르게 확인하며 개발할 수 있게 돕는 중요한 도구입니다.
개발 서버 다시 실행하기
2장 2절에서 개발 서버를 한 번 실행해 보았지만, 그 과정을 다시 떠올리며 몇 가지 주의할 점을 짚어 봅니다. 프로젝트 디렉터리(my-next-app이나 직접 정한 이름)로 이동한 뒤 다음 명령을 실행합니다. 아래의 cd는 프로젝트의 상위 폴더에 있을 때 실행하며, 앞 글을 따라 이미 my-next-app 안에 있다면 npm run dev부터 실행합니다. 앞 글의 서버가 아직 실행 중이라면 그 서버에서 일반 소스의 변경을 확인하고, 설정을 바꿔 다시 시작할 때는 그 터미널에서 먼저 Ctrl + C로 끝냅니다.
cd my-next-app
npm run devnpm run dev는 package.json의 dev 스크립트를 실행하므로, 이 명령은 package.json이 있는 프로젝트 폴더 안에서 실행해야 합니다. 상위 폴더에서 cd를 빼고 실행하면 npm이 package.json을 찾지 못해 오류로 끝납니다. Yarn을 쓴다면 다음 명령입니다.
cd my-next-app
yarn dev명령이 성공하면 접속 URL이 나옵니다. 아래는 Next.js 16의 출력 형식을 보여 주는 예이며, 실제 문구는 버전과 실행 환경에 따라 다릅니다.
▲ Next.js 16.x (Turbopack)
- Local: http://localhost:3000
- Network: http://<내 컴퓨터의 IP>:3000
✓ Starting...
✓ Ready in <걸린 시간>이 출력은 Next.js 개발 서버가 http://localhost:3000에서 실행 중이라는 뜻이며, 웹 브라우저로 그 주소에 들어가면 Next.js의 기본 환영 페이지를 볼 수 있습니다.
개발 서버에는 몇 가지 특징이 있습니다. 서버가 실행 중일 때 코드를 고쳐 저장하면 바뀐 모듈만 교체되거나 화면이 다시 그려져 곧바로 반영되며(Next.js는 이를 Fast Refresh로 제공합니다), 개발 효율을 크게 높여 줍니다. 개발하는 도중 문법 오류나 런타임 오류가 나면 브라우저 화면에 오류 메시지 오버레이가 나타나 문제를 쉽게 찾을 수 있습니다. 반면 개발 서버는 빠른 피드백을 위해 성능 최적화를 최소로 하므로, 실제 프로덕션 환경의 성능과는 차이가 날 수 있습니다. 개발 서버를 끝낼 때는 운영체제와 관계없이 실행 중인 터미널에서 Ctrl + C를 누릅니다.
기본 포트 바꾸기
3000번 포트를 다른 애플리케이션이 쓰고 있거나 다른 포트에서 개발하고 싶을 때가 있습니다. 이럴 때는 npm run dev를 실행할 때 PORT 환경 변수를 지정해 포트를 바꿀 수 있습니다.
# Windows (명령 프롬프트)
set PORT=3001 && npm run dev
# Windows (PowerShell)
$env:PORT=3001; npm run dev
# macOS/Linux
PORT=3001 npm run dev위 명령을 실행하면 http://localhost:3001에서 개발 서버가 시작됩니다. 운영체제와 셸에 관계없이 같은 명령을 쓰고 싶다면, next dev의 -p 옵션을 넘기는 npm run dev -- -p 3001도 쓸 수 있습니다.
PORT는 서버를 시작하는 셸의 환경에 지정합니다. .env.local에 PORT=3001만 적는 방법은 포트 설정으로 쓰지 않는데, HTTP 서버가 시작되는 것이 .env를 읽는 것보다 먼저 일어나기 때문입니다. 실행한 뒤에는 지정한 번호뿐 아니라 터미널에 실제로 나온 주소를 확인합니다(Next.js 문서의 기본 포트 바꾸기).
next.config.ts 파일과 기본 설정
next.config.ts는 Next.js 애플리케이션의 전반적인 동작을 정하는 중요한 파일로, 프로젝트의 루트 디렉터리에 있습니다. create-next-app으로 프로젝트를 만들었다면 기본으로 다음 내용이 들어 있습니다.
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {};
export default nextConfig;이 파일은 타입 검사를 받는 설정 객체를 기본으로 내보내며, nextConfig 객체 안에 여러 설정 옵션을 더해 Next.js의 동작을 바꿀 수 있습니다. 아래의 next.config.ts 예시들은 옵션을 하나씩 따로 설명하는 것이므로, 한 파일에 세 예시를 차례로 붙여 default export를 세 번 만드는 것이 아닙니다. 여러 옵션을 함께 쓴다면 하나의 nextConfig 객체 안에 images와 reactStrictMode 같은 속성을 모으고 한 번만 내보냅니다.
images 설정: 이미지 최적화
Next.js는 이미지를 최적화하는 <Image> 컴포넌트를 제공합니다. 로컬 이미지는 별도의 설정 없이 쓸 수 있고, 외부 도메인의 이미지를 불러올 때는 next.config.ts의 remotePatterns로 허용할 범위를 적어야 합니다.
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'assets.example.com',
pathname: '/my-bucket/**',
},
],
},
};
export default nextConfig;위 remotePatterns는 HTTPS와 정확한 호스트, /my-bucket/ 아래의 경로를 함께 검사합니다. 예를 들어 https://assets.example.com/my-bucket/cat.png는 이 조건에 맞지만, http://assets.example.com/my-bucket/cat.png는 프로토콜이, https://assets.example.com/other/cat.png는 경로가 다릅니다. 이 예시에는 port와 search 조건이 없으므로 포트와 쿼리 문자열까지 제한했다고 설명하지 않으며, 필요한 제한은 별도의 속성으로 적습니다(Next.js 문서의 remotePatterns).
reactStrictMode 설정: React 엄격 모드
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
reactStrictMode: true, // true 또는 false
};
export default nextConfig;App Router는 기본으로 Strict Mode를 쓰므로 보통 이 옵션을 직접 적을 필요가 없습니다. 개발하는 도중 일부 로직이 두 번 실행되는 것처럼 보인다면, Strict Mode를 끄기보다 Effect의 정리 함수와 부수 효과가 되풀이되어도 안전한지를 먼저 확인합니다.
더 깊이: Strict Mode가 개발 중에 한 번 더 실행하는 것
React 문서의 Strict Mode는 이 모드가 켜는 동작을 네 가지로 정리합니다. 순수하지 않은 렌더링을 찾으려고 컴포넌트를 한 번 더 렌더링하고, 빠진 정리 함수를 찾으려고 Effect를 한 번 더 실행하며, ref 콜백도 한 번 더 실행하고, 사용 중단된 API를 쓰는지 검사합니다. 그래서 렌더링 중에 찍은 로그가 두 번 보이거나, Effect에서 보낸 요청이 개발 서버에서 두 번 나가는 것은 이 모드의 정상 동작입니다.
이 검사들은 모두 개발 모드에서만 실행되며 프로덕션 빌드에는 영향을 주지 않습니다. 따라서 next build와 next start로 실행한 화면에서는 이 검사 때문에 로그가 두 번 찍히지 않습니다. 다만 개발 중에 두 번 실행되어 문제가 생겼다면, 그것은 프로덕션에서 재현하기 어려울 뿐 코드에 이미 있는 버그일 수 있습니다. Next.js는 13.5.1부터 App Router에서 Strict Mode를 기본으로 켜므로 reactStrictMode: true는 Pages Router에만 필요하고, 끄려면 false를 명시해야 합니다(Next.js 문서의 reactStrictMode).
.env.local: 환경 변수 관리
API 키 같은 민감한 정보나 백엔드 API 주소처럼 환경마다 달라지는 값은 환경 변수로 관리합니다. 프로젝트 루트의 .env.local에 서버 전용 값과 브라우저에 공개할 값을 나누어 적으며, 이 교재의 구조에서도 src/.env.local이 아니라 package.json 옆에 둡니다. 예시의 DB 주소는 값을 읽는 방법을 설명할 뿐 실제 데이터베이스를 만들거나 연결하지 않습니다.
# 서버 컴포넌트와 Route Handler에서만 읽습니다.
DATABASE_URL=postgresql://user:password@localhost:5432/app
# 브라우저 번들에 공개해도 되는 값에만 접두사를 붙입니다.
NEXT_PUBLIC_API_ORIGIN=https://api.example.comNEXT · CH2-4
공개 변수는 빌드된 브라우저 코드에 남습니다
이름·접근 방식·읽는 시점을 함께 확인합니다. 일반 서버 환경 변수와 공개 변수, next.config의 env는 노출 경로가 다릅니다.
서버 전용 · DATABASE_URL
서버 코드에서 process.env.DATABASE_URL을 읽습니다. 브라우저에 자동 제공하지 않습니다.
별도 노출 경로: 응답이나 props에 직접 넣으면 공개됩니다. 서버에서도 빌드 중 읽는지 요청 중 읽는지에 따라 값이 출력에 남는 시점이 다릅니다.
공개 · NEXT_PUBLIC_API_ORIGIN
process.env.NEXT_PUBLIC_API_ORIGIN처럼 변수명으로 직접 접근하면 빌드 시 공개 값으로 치환됩니다.
빌드 후: 배포 환경값만 바꾸어도 기존 브라우저 번들의 값은 바뀌지 않습니다.
설정 객체 · next.config의 env
접두사와 무관하게 번들에 포함할 값을 설정하는 경로입니다. 비밀값을 넣지 않습니다.
접근 방식: process.env[name] 같은 동적 조회는 공개 변수의 정적 치환 대상이 아닙니다.
공개 여부는 NEXT_PUBLIC_ 이름만 보고 끝내지 않고 실제 응답·props·번들 경로를 함께 확인합니다.
위 그림은 공개 변수가 빌드된 브라우저 코드에 남는다는 점과, 환경값이 읽히고 전달되는 경로에 따라 공개 여부가 달라진다는 점을 보여 줍니다. 그림의 요약처럼 이름과 접근 방식, 읽는 시점을 함께 확인해야 하며, 일반 서버 환경 변수와 공개 변수, next.config의 env는 노출되는 경로가 다릅니다. 환경값에서 서버 코드로 가는 길과 빌드의 공개 치환으로 가는 길은 서로 독립된 분기이며, 실행 순서가 아닙니다.
DATABASE_URL은 서버 코드에서 process.env.DATABASE_URL로 읽으며 브라우저 번들에 저절로 들어가지 않습니다. 다만 서버가 이 값을 응답이나 props에 직접 넣으면 공개되고, 서버에서도 빌드할 때 읽는지 요청할 때 읽는지에 따라 값이 출력에 남는 시점이 다릅니다. process.env.NEXT_PUBLIC_API_ORIGIN처럼 공개 변수를 변수명으로 직접 읽으면 빌드할 때 공개 값으로 치환되므로, 빌드한 뒤 배포 환경의 값만 바꾸어도 기존 브라우저 번들의 값은 바뀌지 않습니다. next.config의 env는 접두사와 관계없이 번들에 넣을 값을 설정하는 경로이므로 비밀값을 넣지 않으며, process.env[name] 같은 동적 조회는 공개 변수의 정적 치환 대상이 아닙니다. 그림의 마지막 설명처럼 공개 여부는 NEXT_PUBLIC_이라는 이름만 보고 판단하지 말고, 실제 응답과 props, 번들의 경로를 함께 확인합니다.
예를 들어 NEXT_PUBLIC_API_ORIGIN을 https://stage.example.com으로 두고 빌드한 브라우저 코드를 그대로 운영에 옮겼다고 해 봅시다. 운영 서버의 환경값만 https://prod.example.com으로 바꾸어도, 이미 만든 브라우저 코드 안의 공개 값은 여전히 stage 주소입니다. 변수명으로 직접 읽는 공개 값은 빌드할 때 치환되므로, 운영 값을 쓰려면 그 값으로 다시 빌드하거나 따로 설계한 런타임 공개 설정 방식을 써야 합니다.
DATABASE_URL은 접두사가 없으므로 브라우저 코드에 자동으로 치환되지 않습니다. 하지만 서버가 그 문자열을 응답이나 클라이언트 컴포넌트의 props에 넣으면 직접 공개한 것이므로, 이름과 실제 전달 경로를 함께 확인합니다(Next.js 문서의 브라우저용 환경 변수). 환경 변수를 읽어 들이는 우선순위와 런타임에 읽는 시점은 뒤의 장에서 더 자세히 다룹니다.
개발 서버를 시작하기 전 마지막 확인
모든 설정을 마쳤다면 프로젝트 디렉터리에서 npm run dev를 다시 실행합니다. 실행하기 전에는 현재 폴더와 포트의 충돌, 설정 파일을 바꿨는지를 함께 확인합니다. 개발 서버가 실행 중일 때 next.config.ts를 고치면 터미널에 Found a change in next.config.ts. Restarting the server to apply the changes...가 나오며 서버가 저절로 다시 시작되고, .env.local을 고치면 Reload env: .env.local이 나오며 환경 변수를 다시 읽습니다. 이 줄이 나오지 않거나 바뀐 값이 반영되지 않을 때만 개발 서버를 직접 다시 시작합니다.
정리
- 개발 서버는 코드 변경을 곧바로 반영하고 오류 오버레이를 보여 주지만, 성능 최적화를 최소로 하므로 프로덕션의 성능과 다릅니다.
- 포트는 셸 환경의
PORT나npm run dev -- -p 3001로 바꾸며,.env.local의PORT는 포트 설정으로 쓰지 않습니다. next.config.ts의 설정은 하나의nextConfig객체에 모아 한 번만 내보내고, 외부 이미지는remotePatterns로 허용 범위를 정합니다.- App Router는 기본으로 Strict Mode를 쓰며, 두 번 실행되는 것처럼 보이는 로직은 정리 함수와 부수 효과부터 확인합니다.
NEXT_PUBLIC_공개 변수는 빌드할 때 브라우저 번들에 고정되고, 접두사가 없는 값도 응답이나 props에 넣으면 공개됩니다.
연습 문제
1. 노출 경로와 허용 조건
서버에서 DB 연결 문자열을 읽어 제품 목록을 조회한 뒤 { title: 제품명, debug: 연결문자열 }을 브라우저로 돌려줬습니다. 연결 문자열에 NEXT_PUBLIC_ 접두사가 없으니 비공개라고 말할 수 있을까요? 위의 이미지 설정에서 https://assets.example.com/other/a.png도 허용되는지 따로 판단해 보세요.
해설 보기
debug로 응답에 넣는 순간 그 문자열이 브라우저로 전달되므로 비공개라고 말할 수 없습니다. 서버 내부의 연결 정보는 돌려주지 않고, 필요한 제품 데이터만 돌려줍니다.
이미지 URL은 호스트와 HTTPS 조건이 맞아도 /my-bucket/** 경로 조건이 맞지 않아 이 패턴으로는 허용되지 않습니다. 변수의 이름이나 이미지의 호스트처럼 조건 하나만 보고 결론을 내리지 않습니다.
2. 운영 환경으로 옮긴 공개 값
스테이징 주소로 NEXT_PUBLIC_API_ORIGIN을 설정해 빌드한 결과를 운영 서버에 그대로 올리고, 운영 서버의 환경 변수만 운영 주소로 바꿨습니다. 브라우저의 코드는 어느 주소로 요청할까요?
해설 보기
여전히 스테이징 주소로 요청합니다. 변수명으로 직접 읽는 공개 변수는 빌드할 때 그 값이 브라우저 번들에 치환되어 고정되므로, 배포한 뒤 서버의 환경 변수를 바꿔도 이미 만든 번들은 바뀌지 않습니다.
운영 주소를 쓰려면 운영 값으로 다시 빌드하거나, 런타임에 공개 설정을 읽어 오는 방식을 따로 설계해야 합니다.
3. 포트 설정이 먹히지 않을 때
.env.local에 PORT=4000을 적고 npm run dev를 실행했는데 서버가 3000번에서 시작되었습니다. 왜 그럴까요? 4000번에서 시작하려면 어떻게 해야 할까요?
해설 보기
HTTP 서버가 시작되는 것이 .env 파일을 읽는 것보다 먼저 일어나므로, .env.local의 PORT는 포트 설정에 쓰이지 않습니다. 4000번에서 시작하려면 macOS·Linux에서 PORT=4000 npm run dev처럼 서버를 시작하는 셸의 환경에 지정하거나, npm run dev -- -p 4000으로 -p 옵션을 넘깁니다.
실행한 뒤에는 터미널에 실제로 나온 주소를 확인합니다.
이것으로 2장을 마칩니다. 2장에서는 Node.js와 npm을 설치하고 버전을 확인한 뒤, create-next-app으로 Next.js 16 프로젝트를 만들고, 프로젝트의 폴더와 파일이 맡는 일과 URL이 만들어지는 규칙, 개발 서버와 next.config.ts, 환경 변수의 기본 설정을 살펴봤습니다. 다음 장의 App Router 구조부터는 App Router의 라우팅을 본격적으로 다룹니다.