🚀 Next.js 10장: `next.config.ts` — 넥스트 엔진의 계기판 조립하기

📋 개요

next.config.ts의 핵심 옵션과 환경 변수, rewrites, 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.tsrewrites( 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.tsredirects() 옵션에서 건드려야 하는 Boolean 셋업 키는 무엇이며, 값은 true/false 중 무엇이어야 하는가?

정답: permanent: false 를 설정한다.

💡 상세 해설: 이 값을 설정하면, 브라우저(및 검색 봇)에게 HTTP 상태 코드 307(또는 308 특수상황) 임시 리다이렉트 응답을 내려보낸다. 크롬 브라우저는 "오호, 이번엔 임시로 메인 홈으로 쫓아내는군. 내 브라우저 자체 메모리엔 캐시해두지 말고 다음번 접속 때에도 꼬박꼬박 물어봐야지!" 라고 작동하여 추후 시즌 이벤트 부활 시 문제가 발생하지 않는다. 반대로 true는 301 영구 이동으로 크롬 깊숙이 화석처럼 각인된다.


🐣 영철이의 퇴근 일기

오늘은 넥스트 엔진의 핵심 계기판인 next.config.ts 를 만져봤어. 처음엔 복잡해 보여서 겁부터 났는데, 영호 리드 님이 비유처럼 '그림자 변장술(Rewrites)' 이나 '허가증(Images)' 으로 생각하니까 훨씬 가깝게 느껴지더라!

💡 오늘의 교훈: "CORS 에러가 나면 당황하지 말고 rewrites 닌자를 파견하자. 설정을 바꿨다면 npm run dev 서버를 껐다 켜는 것도 잊지 말 것!"

그동안 나를 괴롭히던 CORS 에러를 백엔드 팀원 없이 내 손으로 직접 격파했을 때의 그 짜릿함이란... 정말 잊지 못할 것 같아. 오늘은 기분 좋게 퇴근해서 밀린 게임 한 판 하고 푹 쉬어야지. 내일은 더 정교한 설정으로 무장한 '엔지니어' 영철이가 되겠어! 🐣


🔗 더 알아보기