🚀 Next.js 10장: `next.config.ts` — 넥스트 엔진의 계기판 조립하기
📋 개요
next.config.ts의 핵심 옵션과 환경 변수, rewrites, headers 설정을 실무 관점에서 정리합니다.
📋 목차
- 📌 이 문서를 읽기 전에
- 🤔 왜 알아야 하는가
- 🏗️ 비유로 먼저 이해하기
- 🧩
rewrites: 우아한 프록시 은폐술 🟢 - 🛡️
images: 엄격한 외부 이미지 허가증 🟡 - 🌱
redirects&headers: 트래픽 제어탑 🔴 - 💥 에러 해결 카탈로그
- 🏁 이번에 배운 내용 총정리
- 📝 마무리 퀴즈
- 🔗 더 알아보기
📌 이 문서를 읽기 전에
⏱️ 예상 읽기 시간: 15분(전체) / 핵심 파트만: 8분
🗺️ 이 문서의 흐름
환경 설정 파일의 위상 → 프록시 우회(CORS 타파) → 외부 호스트 선언문 → 트래픽 라우팅
🎯 이 문서를 다 읽으면 할 수 있는 것
- 프론트엔드 환경 구축 시 발생하는 지긋지긋한 개발망 CORS 에러를 백엔드 도움 없이
rewrites선언 하나로 혼자 격파할 수 있다. -
next/image컴포넌트가 S3나 외부 CDN 사진을 그릴 때 발생하는 "Hostname is not configured" 빌드 에러를 완벽하게 방어할 수 있다.
🗺️ 이 문서의 배경 세계관: '영수네 커뮤니티'
- 영철(새로 온 주니어): "리드 님... 백엔드 서버 호출했더니 시뻘건 CORS 에러가 떠요! 백엔드 팀원한테 보안 좀 내려달라고 애원했는데 소용이 없네요. 😭"
- 영호(FE 리드): "영철 님... 남의 집 포트로 직접 쳐들어가니까 브라우저 보안 경찰(CORS) 이 잡는 거죠! Next.js 내장 프록시 엔진(
rewrites) 을 켜면, 우리 서버가 같은 식구인 척하면서 몰래 뒷구멍으로 통신을 뚫어줄 수 있다고요!"
🤔 왜 알아야 하는가
Next.js는 단순한 리액트 렌더링 라이브러리가 아니라 "스스로 웹 서버(Node.js)를 돌리는 풀스택 프레임워크" 야.
app/ 폴더 안쪽에서 네가 짜는 코드들이 '승객'이라면, 앱 루트에 존재하는 next.config.ts 파일은 그 승객들을 나르는 '비행기 조종석의 메인 계기판' 이라고 볼 수 있지.
이 계기판을 돌릴 줄 모르고 승객석에서 코드만 짜면, 외부 이미지 차단, 보안 헤더 결락, 라우팅 꼬임, 구형 브라우저 컴파일 실패 같은 인프라 레벨의 거대한 벽에 부딪히게 돼.
가이드 10장에서 이 무서운 계기판의 핵심 버튼 3개만 완벽하게 외워둔다면 프론트엔드 리드 급의 환결 설정 능력을 갖추게 될 거야.
🏗️ 비유로 먼저 이해하기
🧒 5살에게 설명한다면? (영수네 국밥집의 식재료 수입 첩보전)
❌ 기존 통신 (영철이의 무모한 직구)
내가 이웃집 식당 창고에 가서 "고기 좀 줘!" 하고 정문으로 들어갔어.
이웃집 경비 아저씨(CORS) 가 "넌 우리 집 사람(Origin) 이 아니네? 돌아가!" 하고 쫓아냈지.✅ Rewrites (영호의 그림자 변장술)
우리 집 웹 서버(next.config.ts) 한테 "고기 좀 구해다 줘!" 라고 심부름을 시켜.
그러면 우리 서버가 닌자 복장을 하고 이웃집 뒷문으로 살짝 들어가서 고기를 받아와.
그리고 돌아올 땐 자기 옷으로 갈아입고 와서 나한테 "자, 우리 집에서 꺼내온 고기야" 하고 건네줘.
브라우저는 "오호, 우리 집 거니까 안전하네!" 하고 맛있게 국밥을 끓여버려.
🧩 rewrites: 우아한 프록시 은폐술 🟢
프론트엔드 서버가 대신 요청을 받아서, 백엔드 서버로 남몰래 토스해주는 마법이야.
상황: 브라우저(use client)에서 외부 B2B API 호출
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
// 비동기 함수로 rewrites 선언
async rewrites() {
return [
{
// 1. 브라우저야, 너는 앞으로 통신할 때 목적지를 우리 집 "/api/v1/..." 으로 해라.
source: '/api/v1/:path*',
// 2. 그러면 Next.js 서버(나)가 낚아채서 진짜 스프링 백엔드로 몰래 토스해줄게!
destination: 'http://192.168.0.10:8080/api/v1/:path*',
},
]
},
}
export default nextConfig[효과]
영철이는 클라이언트 코드 안에서 fetch("http://192.168.0.10/...") 라는 흉물스러운 하드코딩 서버 주소를 쓸 필요가 없어졌어.
그냥 프론트 안에서 당당하게 fetch("/api/v1/users") 라고 자기 자신을 찌르면 돼. 그러면 중간에 Next.js 계기판이 가로채서 백엔드로 물건을 타오기 때문에, 브라우저의 악랄한 CORS (Cross-Origin) 에러가 태생적으로 발생할 수 없는 구조가 완벽하게 세워져.
🛡️ images: 엄격한 외부 이미지 허가증 🟡
Next.js의 강력한 보물 중 하나인 <Image src={...}> (next/image) 모듈.
이 컴포넌트는 원본 이미지를 자동으로 Webp/AVIF로 압축해주고 리사이징 해주는 무서운 연산 능력이 있어.
하지만, 악의적인 해커가 이상한 바이러스 사이트 그림판(Image)을 던지면서 우회 크롤링 및 서버 연산을 남발해 지갑을 거덜 낼까 봐, Next.js는 기본적으로 "내가 승인한 외부 도메인이 아니면 절대 이미지 압축 변환 안 해줄 거임!" 하고 에러를 뿜어.
// next.config.ts
const nextConfig: NextConfig = {
images: {
// 💡 과거 (Next 13 이하의 domains: ['...'] 배열 방식은 폐기 예정이니 사용 금지!)
// 💡 현재 (Next 14+ 에서는 remotePatterns 라는 훨씬 깐깐하고 정밀한 정규식 허가증을 쓴다)
remotePatterns: [
{
protocol: 'https',
hostname: 's3.ap-northeast-2.amazonaws.com', // 우리 회사 S3 버킷 허락!
port: '',
pathname: '/youngsu-bucket/**', // 버킷 내 특정 폴더 사진만 허락!
},
{
protocol: 'https',
hostname: 'avatars.githubusercontent.com', // 깃허브 오픈 프로필 허락!
},
],
},
}
export default nextConfig단골 실수:
next.config.ts를 한 글자라도 수정했다면? 반드시 터미널에서 구동 중인npm run dev서버를 완전히 껐다가(Ctrl+C) 다시 켜야만 계기판 설정이 Next.js 빌드 엔진에 반영돼! 화면 새로고침(F5) 백날 눌러도 소용없어.
🌱 redirects & headers: 트래픽 제어탑 🔴
앱이 5년 10년 늙어가다 보면 옛날 구형 페이지 URL( /old-about)을 즐겨찾기 해놨던 사용자들이 접속해서 404를 맞고 떠나는 경우가 생겨.
1) Redirects (이삿짐 안내원)
영구적으로 이사 간 주소를 브라우저 단에서 뒤통수치며 이동시켜.
// next.config.ts
const nextConfig: NextConfig = {
async redirects() {
return [
{
source: '/old-about',
destination: '/about-us',
permanent: true, // 💡 true면 브라우저(캐시)와 구글 검색엔진 봇에게 "나 영영 이사 갔어 (HTTP 301)" 라고 통보한다. (SEO 등급 유지의 비밀)
},
{
source: '/event/2024/:id', // 와일드카드 매핑도 가능
destination: '/event/2025/:id',
permanent: false, // 💡 false면 임시 수리(점검창) 목적의 HTTP 307 임시 리다이렉트를 발동한다.
},
]
},
}2) Headers (보안 트랜스포터)
iFrame(다른 남의 사이트가 우리 사이트를 액자 훔쳐보기로 띄우는 행위) 방어, XSS 방어 등을 설정할 때 쓰여.
(보안 부서에서 가장 깐깐하게 점검하는 파일이다!)
// next.config.ts
const nextConfig: NextConfig = {
async headers() {
return [
{
// 전 구역(모든 API + Page)에 적용할 보안 갑옷
source: '/(.*)',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY', // "우리 영수네 웹사이트를 남의 집 iframe 태그 안에 절대 못 가두게 막아라!"
},
],
},
]
},
}💥 에러 해결 카탈로그
에러 메시지가 뜨면 Ctrl+F로 검색해봐.
❌ Invalid src prop (...) on next/image, hostname "kakaocdn.net" is not configured under images in your next.config.js
원인: next/image를 썼는데, 허락받지 않은 외부 출처(카카오톡 프사 CND 등) URL 문자열이 날아왔음.
해결책: next.config.ts 안에 위 챕터에서 배운 remotePatterns 룰을 적용하고, 반드시 개발 서버 터미널 프로세스를 껐다(npm run dev 재시작) 켜야 함.
❌ Rewrite source (...) cannot have both (...) and a path match!
원인: rewrites 작성 시 경로 정규식(Path-matching) 문법을 틀림.
해결책: /:path* (모든 하위 경로 통째 매핑) 와 /:id (딱 하나의 동적 뎁스 매핑) 의 공식 문법 기호를 숙지하고 오타를 점검할 것.
🏁 이번에 배운 내용 총정리
| 기능 이름 | 역할 및 패러다임 | 핵심 키워드 |
|---|---|---|
rewrites | 브라우저 몰래 프론트 서버가 대신 백엔드 통신 가로채기 | CORS 에러 물리치기, 보안 URL 은폐 |
images | 무거운 자체 최적화 서버 엔진의 외부 침입 차단망 설정 | remotePatterns, 허가된 S3 호스팅 그림판 방어 |
redirects | 기존 즐겨찾기 유저와 구글 크롤러 봇에게 이사실 통보 | permanent (영구 캐시 301 vs 임시 307), SEO 방어 |
headers | 서버가 브라우저에 내려보내는 HTTP 수준의 철옹성 방패 | X-Frame-Options, 보안 인증, 커스텀 태그 주입 |
📝 마무리 퀴즈
배웠으면 한 번 비틀어서 확인해봐야 해.
Q1. 프론트엔드 팀과 백엔드 팀 모두가 각자의 로컬(localhost)에서 띄우고 회의 중이다. 프론트엔드 신입 영철이가 브라우저에서 fetch('http://localhost:8080/api/users') 를 호출하다가 끔찍한 CORS 에러를 뿜었고 삐졌다. 영호 리드는 next.config.ts 에 rewrites( source: "/api/:path*", destination: "http://localhost:8080/api/:path*" ) 를 세팅해두고, 구원투수가 되었다. 성공적인 처리 후, 영철이가 짠 컴포넌트 내부의 로직은 이전 fetch(...) 주소에서 구체적으로 어떤 문자열 형태로 삭감/리팩토링 변경되었고 그 이유는 무엇인가?
✅ 정답: 기존 전체 경로 fetch('http://localhost:8080/api/users') 에서, 상대 통신 주소인 fetch('/api/users') 로 리팩토링된다.
💡 상세 해설: 클라이언트의 브라우저 엔진은 http://자신도메인포트/api/users 같은 오리진(동일 3000포트) 요청으로 해석하므로, CORS 규칙의 '동일 출처 정책(Same-Origin)'을 완벽하게 만족시켜 에러를 패스한다. 이후 이 로컬 요청을 낚아챈 Next.js Node.js 서버는 보안 검증 따위가 없는 서버 런타임이므로 진짜 백엔드 8080 포트로 유령처럼 통과해버린다.
Q2. 영호 리드가 특정 이벤트 페이지(/event/summer)가 완전히 종료되어서, 사용자가 거기에 접속하면 즉시 메인 홈(/)으로 튕겨져 내보내려 한다. 그런데 다음 달 여름에 같은 URL을 다시 쓸 수도 있어서, 브라우저가 이걸 "영구 폐쇄"로 캐시해버리면 곤란하다고 한다. 이때 next.config.ts 의 redirects() 옵션에서 건드려야 하는 Boolean 셋업 키는 무엇이며, 값은 true/false 중 무엇이어야 하는가?
✅ 정답: permanent: false 를 설정한다.
💡 상세 해설: 이 값을 설정하면, 브라우저(및 검색 봇)에게 HTTP 상태 코드 307(또는 308 특수상황) 임시 리다이렉트 응답을 내려보낸다. 크롬 브라우저는 "오호, 이번엔 임시로 메인 홈으로 쫓아내는군. 내 브라우저 자체 메모리엔 캐시해두지 말고 다음번 접속 때에도 꼬박꼬박 물어봐야지!" 라고 작동하여 추후 시즌 이벤트 부활 시 문제가 발생하지 않는다. 반대로 true는 301 영구 이동으로 크롬 깊숙이 화석처럼 각인된다.
🐣 영철이의 퇴근 일기
오늘은 넥스트 엔진의 핵심 계기판인 next.config.ts 를 만져봤어. 처음엔 복잡해 보여서 겁부터 났는데, 영호 리드 님이 비유처럼 '그림자 변장술(Rewrites)' 이나 '허가증(Images)' 으로 생각하니까 훨씬 가깝게 느껴지더라!
💡 오늘의 교훈: "CORS 에러가 나면 당황하지 말고 rewrites 닌자를 파견하자. 설정을 바꿨다면 npm run dev 서버를 껐다 켜는 것도 잊지 말 것!"
그동안 나를 괴롭히던 CORS 에러를 백엔드 팀원 없이 내 손으로 직접 격파했을 때의 그 짜릿함이란... 정말 잊지 못할 것 같아. 오늘은 기분 좋게 퇴근해서 밀린 게임 한 판 하고 푹 쉬어야지. 내일은 더 정교한 설정으로 무장한 '엔지니어' 영철이가 되겠어! 🐣