로컬에선 멀쩡, 배포하니 터졌다

LinkDropper·2025년 4월 28일
post-thumbnail

😎 시작은 단순했다

링크 드라퍼 서비스에서는
사용자가 접근할 때 "현재 서버가 점검 중인지"를 확인하는 기능이 필요했습니다.

Next.js Middleware를 이용해,
모든 요청이 들어올 때 점검 API를 호출하고,
점검 중이라면 /maintenance 페이지로 리디렉션하는 흐름을 만들었습니다.

구조도 간단했습니다:

// middleware.ts
import { customFetch } from '@/lib/fetch';

export async function middleware(req: NextRequest) {
  const response = await customFetch('/api/maintenance');
  const data = await response.json();

  if (data.isMaintenance) {
    return Response.redirect('/maintenance');
  }
}

로컬에서는 정말 깔끔하게 잘 돌아갔습니다.
문제는… Vercel에 배포한 순간 시작됐습니다.


🚨 “Internal Server Error”

Middleware가 fetch를 못 한다고?

Vercel에 배포하고 나니
점검 처리가 아예 안 되는 것은 물론,
Middleware 자체가 에러를 뱉으며 동작을 멈췄습니다.

에러 로그를 살펴보니:
• fetch 호출 실패
• 특정 헤더 관련 오류
• Authorization 토큰이 undefined

“아니 로컬에선 잘 됐는데?”


🤔 원인을 추적하다: Edge Runtime의 정체

Next.js에서 Middleware는 Vercel에 배포되면,
Node.js 서버가 아니라
Edge Function 위에서 실행됩니다.

Edge Runtime이라고 부르는 이 환경은,
Node.js가 아닙니다.

🔥 Edge Runtime vs Node.js

항목 Node.js Edge Runtime
런타임 완전한 Node.js 환경 V8 Isolate 기반
지원 API fs, http, https, net 등 자유롭게 사용 대부분 미지원
fetch 동작 서버 context 유지 새 요청 context로 동작
쿠키/헤더 관리 자동 포함 가능 수동 설정 필요


💥 터진 진짜 이유: 커스텀 fetch + 쿠키

우리 customFetch는
getCookie()로 session_token을 가져와
Authorization 헤더를 만들고 있었습니다.

// customFetch 내부 (문제)
const token = getCookie('session_token');
headers['Authorization'] = `Bearer ${token}`;

Edge Middleware에서는
• getCookie() 자체가 제대로 동작하지 않고
• 새로 만든 fetch 요청에는 쿠키가 자동으로 전달되지 않습니다.

결국:
• Authorization 헤더가 비어 있음
• 인증 실패
• API 요청 실패
• Middleware 자체 에러

이라는 대참사가 일어난 것이죠.


🛠️ 어떻게 고쳤을까?

✨ 해결법 1: Middleware 안에서는 ‘순수 fetch’만 쓰자

Middleware에서는
customFetch를 쓰지 않고,
순수 fetch로 API를 호출했습니다.

쿠키도 직접 수동으로 넘겼습니다.

export async function middleware(req: NextRequest) {
  const baseUrl = process.env.BASE_URL || 'https://link-dropper.com';

  const res = await fetch(`${baseUrl}/api/maintenance`, {
    headers: {
      cookie: req.headers.get('cookie') || '',
    },
  });

  const data = await res.json();

  if (data.isMaintenance) {
    return Response.redirect('/maintenance');
  }
}
  • fetch는 기본 제공이니까 사용 가능
  • req.headers.get('cookie')로 기존 쿠키를 복사해서 넘김

🛠️ 다른 고민했던 해결 방법들

✅ Edge Config 사용하기 (Vercel 기능)

Vercel에서는 Edge Config라는 기능을 제공해
초고속 Key-Value 저장소를 Edge Function에서 바로 읽을 수 있습니다.
• 점검 모드 여부를 Edge Config에 저장
• API 호출 없이 바로 읽기

단점: 관리하는 시스템이 하나 더 늘어남

✅ 환경변수 활용하기

process.env.MAINTENANCE_MODE 같은 환경변수로 점검 상태를 관리하는 방법도 있습니다.

단점: 런타임 중 실시간 변경이 불가능함
(배포해야 반영)

✅ API fetch 시 Bearer 인증 제거

점검 API 자체를 비인증 엔드포인트로 만들어서
fetch에 Authorization 헤더를 요구하지 않게 만드는 것도 하나의 방법입니다.

단점: 보안적인 고민 필요


✅ 최종 선택: 가장 깔끔한 방법

•	Middleware 안에서는 순수 fetch만 사용
•	요청 헤더로 쿠키를 수동 전달
•	점검 API는 인증 없이 접근 가능하도록 수정

현재는 이 구조로 Vercel에서도 문제없이 잘 작동 중입니다.


🧪 링크 드라퍼, 지금 베타 테스트 중입니다

링크 드라퍼는 단순한 링크 저장 툴이 아닙니다.
링크를 정리하고, 다시 꺼내볼 수 있게 만드는 서비스를 목표로 하고 있습니다.
• 🔗 링크 삭제, 폴더 생성/수정 작업에 낙관적 UI 적용
• 🌐 OpenGraph 정보 자동 수집
• 👥 폴더를 친구에게 공유하는 기능까지

👉 🔗 링크 드라퍼 베타 체험하러 가기

지금 체험해보시고,
빠르고 부드러운 사용자 흐름을 경험해보세요!


✍️ 마치며

로컬에서 잘 돌아간다고 방심하면,
Vercel Edge Runtime이 발목을 잡을 수 있습니다.

Next.js를 Vercel에 올린다면,
Middleware는 Node.js가 아니다.
라는 점을 꼭 기억해야 합니다.

이 글이 같은 문제로 헤매는 개발자들에게 작은 등불이 되길 바랍니다 🙌

읽어주셔서 감사합니다!
비슷한 실수나 경험 있으셨다면 댓글로 공유해주세요 😊

profile
“기록하는 습관을 도구로 만들다 — 두 개발자의 링크 드라퍼 구축기”

0개의 댓글