배포 환경 차이 해결: 프록시·캐시·환경변수·헤더 점검

··설명 약 13분·작성: 해비·블로그 소개

로컬과 운영의 동작이 다를 때 환경변수·프록시·캐시를 어떤 순서로 확인하나요?

같은 코드가 로컬과 운영에서 다르면 실제 배포 버전, 환경변수, 요청 주소, 응답 헤더와 캐시를 순서대로 비교하세요. 오래된 화면이라고 곧바로 캐시를 지우기보다 도메인이 새 배포를 가리키는지 먼저 확인합니다.

적용 조건·범위: 프로젝트의 데이터 특성을 기준으로 설정해야 하며 모든 API에 no-store를 일괄 적용하는 것도 좋은 기본값은 아닙니다.

본문 근거: vercel.com/docs/environment-variables · developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control · developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS

시작 상태

TaskFlow는 다음 상태라고 가정합니다.

Production 배포 성공
주요 route 존재
도메인·HTTPS 정상

하지만
로컬과 Production의 API 결과가 다름
또는 오래된 화면이 보임

목차

  1. 문제를 네 계층으로 나누기
  2. 환경변수 비교
  3. origin과 reverse proxy
  4. Cache-Control 확인
  5. 캐시된 구버전 확인
  6. CORS와 서버 오류 구분
  7. 수정 전
  8. 수정 후
  9. 실습 프로젝트 전체 코드
  10. 운영 진단 체크리스트
  11. 학습 완료 체크리스트
  12. TaskFlow 프로젝트 적용
  13. 실습 경로·결과·복구
  14. 다음 학습
  15. 공식 문서
  16. 단독 실습과 결과 확인

문제를 네 계층으로 나누기

환경 차이는 다음 순서로 확인하면 좋습니다.

1. Deployment
현재 URL이 어떤 commit인가

2. Environment
필요한 변수와 URL이 맞는가

3. Network
어디로 요청하고 어떤 상태가 돌아오는가

4. Cache
브라우저/CDN이 이전 응답을 재사용하는가

이 순서를 지키면 “일단 캐시 삭제”, “일단 재배포”처럼 증거가 사라지는 해결 방법부터 적용하는 일을 줄일 수 있습니다.

환경변수 비교

예를 들어 API 주소를 환경변수로 관리한다고 가정합니다.

Development
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000

Preview
NEXT_PUBLIC_API_BASE_URL=https://preview-api.example

Production
NEXT_PUBLIC_API_BASE_URL=https://api.example

코드가 같아도 변수 값이 다르면 요청 결과가 달라집니다.

클라이언트 코드에서는 다음처럼 사용할 수 있습니다.

const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL;

if (!apiBaseUrl) {
  throw new Error("Missing NEXT_PUBLIC_API_BASE_URL");
}

문제 해결을 위해 실제 Production Secret 값을 console.log로 출력하지 않습니다.

확인해야 할 것은 값 자체가 아니라 다음과 같은 상태입니다.

변수가 존재하는가
올바른 environment scope에 등록되었는가
현재 deployment가 변경된 값을 포함하는가
브라우저 공개 값과 서버 Secret이 구분되었는가

origin과 reverse proxy

origin은 보통 요청을 실제로 처리하는 원본 서버 또는 애플리케이션을 의미합니다.

CDN이나 reverse proxy가 앞에 있다면 사용자의 요청은 바로 origin으로 가지 않을 수 있습니다.

Browser
↓
CDN / Proxy
↓
Origin
↓
Application

이 구조에서는 같은 URL이라도 중간 계층의 캐시나 헤더 정책이 결과에 영향을 줄 수 있습니다.

예를 들어 브라우저 Network 탭에서 다음을 확인합니다.

Request URL
Request Method
Status Code
Origin
Response Headers
Cache-Control
Location

Cache-Control 확인

HTTP Cache-Control 헤더는 브라우저나 공유 캐시가 응답을 어떻게 저장하고 재사용할지 결정하는 데 사용됩니다.

예를 들어 다음 응답은 일정 시간 캐시될 수 있습니다.

Cache-Control: public, max-age=300

반면 민감하거나 항상 최신 상태가 필요한 응답은 다른 정책이 필요합니다.

프로젝트의 데이터 특성을 기준으로 설정해야 하며 모든 API에 no-store를 일괄 적용하는 것도 좋은 기본값은 아닙니다.

문제 해결에서는 먼저 실제 응답 헤더를 봅니다.

$PRODUCTION_URL = Read-Host "본인의 HTTPS Production 주소를 입력하세요"
if ([string]::IsNullOrWhiteSpace($PRODUCTION_URL) -or $PRODUCTION_URL -notmatch "^https://") {
  throw "확인된 HTTPS Production 주소를 입력해야 합니다."
}
$PRODUCTION_URL = $PRODUCTION_URL.TrimEnd("/")
curl.exe -I "$PRODUCTION_URL/"

같은 PowerShell 세션에서 필요하면 API 경로도 확인합니다. HEAD를 지원하지 않아 405가 나온다면 API 고장으로 단정하지 말고 해당 API의 지원 메서드를 확인합니다.

if ([string]::IsNullOrWhiteSpace($PRODUCTION_URL)) {
  throw "먼저 본인의 Production 주소를 설정하세요."
}
curl.exe -I "$PRODUCTION_URL/api/tasks"

캐시된 구버전 확인

사용자가 “수정했는데 그대로다”라고 말할 때 실제로 새 deployment가 Production에 연결되었는지부터 확인합니다.

commit A
→ deployment A
→ 이전 화면

commit B
→ deployment B
→ 수정 화면

현재 도메인이 deployment B를 가리키는지 확인하지 않은 상태에서 브라우저 캐시만 지우면 원인을 놓칠 수 있습니다.

확인 순서는 다음처럼 잡습니다.

1. 현재 Production deployment 확인
2. 해당 deployment의 commit 확인
3. 원본 URL에서 응답 확인
4. custom domain에서 응답 확인
5. 응답 헤더 확인
6. 그다음 브라우저/CDN 캐시 판단

CORS와 서버 오류 구분

CORS는 브라우저가 다른 origin의 응답을 스크립트에서 사용할 수 있는지를 제어하는 보안 메커니즘과 관련됩니다.

하지만 “API 요청이 실패했다”는 모든 상황이 CORS는 아닙니다.

401
→ 인증 문제 가능

403
→ 권한 문제 가능

404
→ URL / route 문제 가능

500
→ 서버 런타임 문제 가능

브라우저 CORS 오류
→ Origin / Access-Control-* 헤더 확인

Network 탭에서 실제 HTTP 응답이 있었는지 확인하고 console 메시지와 함께 판단합니다.

수정 전

환경을 고려하지 않고 API 주소를 코드에 고정했다고 가정합니다.

export const API_BASE_URL = "http://localhost:3000";

로컬에서는 동작하지만 Production 브라우저에서도 localhost를 요청하게 됩니다.

수정 후

function required(name: string, value: string | undefined) {
  if (!value) {
    throw new Error(`Missing environment variable: ${name}`);
  }

  return value;
}

export const API_BASE_URL = required(
  "NEXT_PUBLIC_API_BASE_URL",
  process.env.NEXT_PUBLIC_API_BASE_URL,
);

환경별 값은 배포 플랫폼에서 관리합니다. 이전 API_URL 예제에서 API_BASE_URL로 이름을 바꾸었다면 로컬 .env 및 Preview·Production 변수 이름도 함께 맞춘 뒤 다시 빌드합니다. NEXT_PUBLIC_API_BASE_URL에는 끝의 /가 없는 origin을 입력합니다. 공개 환경변수 값은 빌드 시 반영되므로 변경 후 새 빌드가 필요합니다.

운영 진단 체크리스트

문제가 발생했을 때 다음 정보를 먼저 모읍니다.

URL
발생 시각
환경: Preview / Production
deployment ID 또는 URL
commit SHA
HTTP status
browser console
Network request/response
response headers
build/runtime log
DNS 결과가 필요한지 여부

이 정보를 모은 뒤 원인을 하나씩 제거합니다.

이 주제만 따로 연습하기

실제 운영 설정을 바꾸지 않고 다음 입력을 비교합니다. 같은 commit의 로컬 브라우저는 https://api.example.test/api/tasks, Production 브라우저는 http://localhost:3000/api/tasks를 요청합니다. Production의 변수 존재 여부는 아직 확인하지 않았습니다.

관찰 다음 확인
요청 URL의 origin이 다름 하드코딩 URL과 NEXT_PUBLIC_API_BASE_URL 사용 위치
Preview만 변수 등록 Production 범위에도 필요한 이름이 존재하는지
설정 저장 후 옛 요청 지속 공개 변수는 빌드 반영 대상이므로 새 배포 여부

기대 진단은 무조건 캐시 삭제가 아니라 실제 요청 대상과 빌드 환경 연결입니다. 자가 확인: API가 500을 반환하고 CORS 헤더도 없으면 CORS 설정만 고쳐 성공했다고 할 수 있을까요? 실제 서버 실패도 별도로 해결해야 합니다. 아래 최소 앱에는 getTasks를 호출하는 UI/API 서버가 없으므로 함수 파일만 추가한 상태를 통합 성공으로 기록하지 않습니다.

학습 완료 체크리스트

  • 환경변수를 environment별로 비교할 수 있습니다.
  • origin과 proxy 역할을 구분할 수 있습니다.
  • Cache-Control 응답을 확인할 수 있습니다.
  • 현재 Production deployment와 commit을 확인할 수 있습니다.
  • CORS와 일반 HTTP 오류를 구분할 수 있습니다.
  • 캐시를 삭제하기 전에 필요한 증거를 수집할 수 있습니다.

선택 실습: 할 일 앱에 적용하기

TaskFlow는 작업을 관리하는 예제 앱입니다. 이 선택 실습에서는 앱을 배포하고 서버·설정·운영 결과를 확인합니다.

이번 주제: 배포 환경 차이 해결: 프록시·캐시·환경변수·헤더 점검.

선택: 연재 프로젝트와 전체 자료

연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.

TaskFlow 프로젝트 적용

이 글부터 시작하는 전체 프로젝트

시작 ZIP — deploy-step-08-routing-debug-full-20260928을 새 폴더에 풀고, 이번 단계 완료 ZIP은 다른 폴더에 풀어 비교합니다. ZIP 안 프로젝트 폴더의 package.json 위치가 실행 루트입니다.

출발점은 Next.js 누적 TaskFlow와 같은 38단계 소스입니다. 프로젝트에는 홈·로그인·개인 작업 화면, 작업 추가·완료·삭제, Route Handler와 캐시 예제가 들어 있습니다. 설치 버전을 고정한 package-lock.json도 포함합니다.

이번 변경 이유: 환경별 API origin 진단 예제와 요청·응답 비교 항목을 추가합니다.

  • .env.example
  • docs/deployment-plan.md
  • src/lib/api.ts
  • src/lib/env.ts
  1. 시작 ZIP을 풀고 npm ci로 의존성을 설치합니다. Node.js 24와 npm을 사용합니다.
  2. 위 변경 파일을 아래 전체 내용 또는 완료 ZIP의 같은 경로와 대조해 적용합니다. 다른 앱 파일은 유지합니다.
  3. npm run typecheck, npm run build를 실행합니다. 오류가 있으면 첫 오류의 파일·명령을 기록하고 해결한 뒤 진행합니다.
  4. npm run setup-auth로 일회용 로컬 학습 계정을 설정한 후 npm run dev를 실행합니다. 생성된 .env.local과 입력 값은 공유하지 않습니다.

예상 결과: 홈과 로그인 화면은 공개됩니다. 로그인 전 /tasks는 로그인 화면으로 이동하고 /api/tasks는 401을 반환합니다. 로그인 후 작업 목록에서 추가·완료·삭제를 확인합니다. /cache-lab의 별도 캐시 실습은 npm run cache-api가 필요합니다.

/legacy/tasks도 기존 인증을 유지합니다. 로그인하지 않은 상태의 로그인 이동은 404 오류가 아닙니다. next.config.ts의 rewrite와 proxy.ts의 보호 경로를 함께 확인하고, 로그인 후 두 주소에서 작업 화면을 대조합니다.

src/lib/env.ts와 src/lib/api.ts(9단계부터)는 환경 차이를 관찰하는 별도 진단 예제입니다. 기본 TaskFlow는 같은 origin의 인증된 /api/tasks를 사용하므로 이 예제로 기존 데이터 조회나 인증을 교체하지 않습니다. 공개 API origin 변수도 해당 진단 예제를 직접 사용할 때만 설정합니다.

저장·운영 범위: 작업은 단일 Node 프로세스 메모리에 저장되고 인증은 학습용 계정 예제입니다. 서버 재시작·서버리스 인스턴스 사이의 데이터 유지는 보장하지 않습니다. 로컬 실행 확인과 실제 운영 서비스 구축은 구분하며, 원격 배포·DNS·SSH·CI·롤백 결과는 직접 실행한 항목만 문서에 기록합니다.

전체 파일·코드 보기

이번 단계의 변경 파일 전체

.env.example

AUTH_SECRET=
DEMO_EMAIL=
DEMO_PASSWORD_SALT=
DEMO_PASSWORD_HASH=
SITE_URL=
NEXT_PUBLIC_API_MOCKING=
NEXT_PUBLIC_API_BASE_URL=

docs/deployment-plan.md

# TaskFlow Deployment Plan


## Build
- package manager: npm
- install: npm ci
- build: npm run build


## Primary deployment
- platform: Vercel
- environment: Production


## Alternative deployment
- Firebase Hosting: static output일 때 검토
- Firebase App Hosting: server runtime이 필요할 때 검토


## Verification
- Production URL 응답
- 주요 화면 확인
- 환경변수 확인
- 주요 route 확인
- 현재 commit 확인
- rollback 가능 여부 확인

## Runtime decision


- static-only: no
- server runtime required: yes
- reasons:
  - Route Handler
  - server-side environment variables


## Primary
Vercel Production


## Firebase alternative
App Hosting 우선 검토


## Static variant
정적 결과물로 분리할 경우 Firebase Hosting 검토

## Production deployment


- deployment URL: 운영 문서에서 관리
- commit SHA: 배포마다 기록
- deployment status: 확인
- build log: 확인
- smoke test: 확인

## Domain


- canonical domain: 운영 문서에서 관리
- www policy: canonical로 redirect
- HTTPS: 확인
- DNS status: 확인
- auth callback / allowed origin: 확인

## Runtime difference checklist


1. Production deployment와 commit 확인
2. 환경변수 이름·scope 확인
3. Request URL과 status 확인
4. Response headers 확인
5. origin / proxy 경로 확인
6. cache 여부 확인
7. 수정 후 새 deployment에서 재검증

src/lib/api.ts

import { env } from "./env";


export async function getTasks() {
  const response = await fetch(`${env.apiBaseUrl}/api/tasks`);


  if (!response.ok) {
    throw new Error(`Failed to fetch tasks: ${response.status}`);
  }


  return response.json();
}

src/lib/env.ts

function required(name: string, value: string | undefined) {
  if (!value) {
    throw new Error(`Missing environment variable: ${name}`);
  }


  return value;
}


export const env = {
  apiBaseUrl: required(
    "NEXT_PUBLIC_API_BASE_URL",
    process.env.NEXT_PUBLIC_API_BASE_URL,
  ),
};

나머지 앱 소스와 잠금 파일은 전체 완료 ZIP에 있습니다. 기존 코드 조각은 설명용이며 위 전체 변경 파일과 ZIP이 이 누적 실습의 기준입니다. 다음 글에는 이번 완료 ZIP을 시작본으로 가져갑니다.

이 회차는 아래 전체 TaskFlow 시작 ZIP을 기준으로 진행합니다. 이전 글의 코드를 모으지 않아도 되며, 완료 ZIP에는 이번 변경과 기존 앱 기능이 함께 들어 있습니다.

deployment-plan.md 추가 항목

## Runtime difference checklist

1. Production deployment와 commit 확인
2. 환경변수 이름·scope 확인
3. Request URL과 status 확인
4. Response headers 확인
5. origin / proxy 경로 확인
6. cache 여부 확인
7. 수정 후 새 deployment에서 재검증

실습 프로젝트 전체 코드

아래는 8편의 rewrite 문제를 재현하는 최소 Next.js 앱과 누적 운영 자료 전체입니다. 기존 TaskFlow 전체 기능이나 실제 배포 결과가 아닙니다. 의존성 잠금 파일 package-lock.json은 ZIP에 포함하며 본문에서는 이 자동 생성 파일만 생략합니다.

.env.example

# 7편 NEXT_PUBLIC_API_URL을 이 회차부터 NEXT_PUBLIC_API_BASE_URL로 통일합니다.
# 끝에 /가 없는 API origin을 설정하세요. 공개 API 주소만 넣고 secret은 넣지 마세요.
NEXT_PUBLIC_API_BASE_URL=
API_SECRET=

.github/workflows/ci.yml

name: CI


on:
  push:
    branches:
      - main
  pull_request:


jobs:
  build:
    runs-on: ubuntu-latest


    steps:
      - name: Checkout
        uses: actions/checkout@v7


      - name: Setup Node.js
        uses: actions/setup-node@v7
        with:
          node-version: "24.x"
          cache: "npm"


      - name: Install dependencies
        run: npm ci


      - name: Build
        run: npm run build

.gitignore

node_modules/
.next/
.env.local
*.tsbuildinfo

.nvmrc

24

app/layout.tsx

import type { ReactNode } from "react";
export default function RootLayout({ children }: { children: ReactNode }) {
  return <html lang="ko"><body>{children}</body></html>;
}

app/page.tsx

import Link from "next/link";
export default function Home() {
  return <main><h1>TaskFlow 배포 진단 실습</h1><Link href="/tasks">작업 목록</Link></main>;
}

app/tasks/page.tsx

export default function TasksPage() {
  return (
    <main>
      <h1>Tasks</h1>
      <p>TaskFlow 작업 목록입니다.</p>
    </main>
  );
}

docs/deployment-plan.md

# TaskFlow Deployment Plan


## Build
- package manager: npm
- install: npm ci
- build: npm run build


## Primary deployment
- platform: Vercel
- environment: Production


## Alternative deployment
- Firebase Hosting: static output일 때 검토
- Firebase App Hosting: server runtime이 필요할 때 검토


## Verification
- Production URL 응답
- 주요 화면 확인
- 환경변수 확인
- 주요 route 확인
- 현재 commit 확인
- rollback 가능 여부 확인

## Runtime decision


- static-only: no
- server runtime required: yes
- reasons:
  - Route Handler
  - server-side environment variables


## Primary
Vercel Production


## Firebase alternative
App Hosting 우선 검토


## Static variant
정적 결과물로 분리할 경우 Firebase Hosting 검토

## Production deployment


- deployment URL: 운영 문서에서 관리
- commit SHA: 배포마다 기록
- deployment status: 확인
- build log: 확인
- smoke test: 확인

## Domain


- canonical domain: 운영 문서에서 관리
- www policy: canonical로 redirect
- HTTPS: 확인
- DNS status: 확인
- auth callback / allowed origin: 확인

## Runtime difference checklist


1. Production deployment와 commit 확인
2. 환경변수 이름·scope 확인
3. Request URL과 status 확인
4. Response headers 확인
5. origin / proxy 경로 확인
6. cache 여부 확인
7. 수정 후 새 deployment에서 재검증

docs/server-access-runbook.md

# Server Access Runbook


## Connection
- host: 저장하지 않음
- user: 환경별 문서에서 별도 관리
- authentication: SSH key 우선
- private key: 저장소에 포함 금지


## Before upload
1. 대상 서버 확인
2. 현재 경로 확인
3. 배포 대상 경로 확인
4. 기존 파일 상태 확인


## After upload
1. 파일 존재 확인
2. owner / permission 확인
3. 서비스가 실제 경로를 읽는지 확인
4. HTTP 결과 확인


## Never commit
- private key
- password
- access token
- production secret

next-env.d.ts

/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/types/root-params.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

next.config.ts

import type { NextConfig } from "next";


const nextConfig: NextConfig = {
  async rewrites() {
    return [
      {
        source: "/legacy/tasks",
        destination: "/tasks",
      },
    ];
  },
};


export default nextConfig;

package.json

{
  "name": "taskflow-deployment-lab",
  "private": true,
  "engines": {
    "node": "24.x"
  },
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "next": "16.3.5",
    "react": "19.3.0",
    "react-dom": "19.3.0"
  },
  "devDependencies": {
    "typescript": "5.9.3",
    "@types/node": "24.10.1",
    "@types/react": "19.2.7",
    "@types/react-dom": "19.2.3"
  }
}

README.md

# 배포·서버 09 — deploy-step-09-runtime-diff

이 ZIP은 8편의 /legacy/tasks → /tasks rewrite를 재현하는 최소 Next.js 앱과 이전 회차 운영 문서를 포함합니다. 기존 TaskFlow 전체 기능, 로그인, 실제 API 서버나 Production 배포 상태를 제공하지 않습니다.

Node.js 24.x, npm을 사용합니다. `npm ci` → `npm run typecheck` → `npm run build`로 확인하고 `npm run dev` 후 `/tasks`, `/legacy/tasks`를 비교합니다. 전체 의존성 잠금 파일은 package-lock.json에 있습니다.

.env.example은 값이 빈 설정 안내입니다. 독자의 앱에서 사용하는 변수만 .env.local 또는 플랫폼의 해당 환경에 설정하세요. URL·계정·비밀키를 ZIP에 넣지 않습니다.

CI workflow는 이미 package.json/package-lock.json과 build 스크립트가 있는 기존 npm 프로젝트 루트에 적용합니다. actions/checkout@v7, actions/setup-node@v7, Node 24.x를 사용합니다. GitHub 원격 실행은 검증하지 않았습니다.

src/lib/env.ts는 해당 모듈을 import할 때 환경 변수를 검사합니다. 파일을 추가하기만 해서는 사용하지 않는 앱 전체의 환경 설정이 자동 검증되지 않습니다.

9편부터 변수명을 NEXT_PUBLIC_API_BASE_URL로 통일합니다. 7편 NEXT_PUBLIC_API_URL 값의 이름을 바꾸고 Preview/Production scope에도 반영한 뒤 다시 build해야 합니다. 끝에 /가 없는 API origin을 입력하세요. src/lib/api.ts의 getTasks는 독자의 실제 /api/tasks API를 호출하는 함수이며 이 최소 앱에는 API 서버와 호출 UI를 추가하지 않았습니다.

이 자료에는 실제 배포·DNS 변경·SSH 접속·메일/알림·롤백 결과가 없습니다. 운영 작업은 독자가 본인 환경과 권한을 확인한 후 수행해야 합니다. 문서의 확인 항목은 수행할 체크리스트이며 이미 통과했다는 기록이 아닙니다.

src/lib/api.ts

import { env } from "./env";


export async function getTasks() {
  const response = await fetch(`${env.apiBaseUrl}/api/tasks`);


  if (!response.ok) {
    throw new Error(`Failed to fetch tasks: ${response.status}`);
  }


  return response.json();
}

src/lib/env.ts

function required(name: string, value: string | undefined) {
  if (!value) {
    throw new Error(`Missing environment variable: ${name}`);
  }


  return value;
}


export const env = {
  apiBaseUrl: required(
    "NEXT_PUBLIC_API_BASE_URL",
    process.env.NEXT_PUBLIC_API_BASE_URL,
  ),
};

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2017",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": [
        "./*"
      ]
    }
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts",
    ".next/dev/types/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

이전 체크포인트

deploy-step-08-routing-debug

현재 상태

TaskFlow의 route는 정상이고 build도 성공하지만 환경에 따른 차이를 진단할 수 있는 기준이 부족합니다.

변경 파일

src/
└─ lib/
   ├─ env.ts
   └─ api.ts

docs/
└─ deployment-plan.md

실행 확인

npm run build

Production에서는 DevTools Network와 실제 응답 헤더를 확인합니다.

이 본문 작성 단계에서는 특정 운영 URL의 실제 response header를 검사했다고 기록하지 않습니다. 실제 운영 반영 시 프로젝트 URL을 기준으로 별도 검증해야 합니다.

완료 체크포인트

deploy-step-09-runtime-diff

실습 경로·결과·복구

독자 질문

로컬·Preview·Production의 화면 차이가 캐시인지 설정인지 어떻게 판별할까요?

배포 전제

각 환경의 URL과 배포 commit, 실패 요청의 Network 기록이 필요합니다. 변수 값과 인증 헤더는 복사하지 않고 이름·설정 범위·존재 여부만 비교합니다.

실행 단계

  1. 동일 경로를 각 환경에서 열어 응답 status, request URL, 응답의 Cache-Control·Age 같은 캐시 관련 헤더, 화면에 표시된 배포 버전을 기록합니다.
  2. Production에서 사용하는 공개 API origin과 Production 환경변수 이름을 Preview와 대조합니다. Network 요청이 CORS에 막히는지, 서버가 실제 4xx/5xx를 반환하는지 구분합니다.
  3. 캐시 삭제 전 원본 응답과 배포 SHA를 보존하고, 잘못된 변수 범위·프록시 경로·캐시 정책 중 확인된 한 가지 원인만 수정해 새 배포와 재요청을 비교합니다.

성공·실패 확인

수정 전후 동일 URL의 status·요청 대상·헤더·화면 버전이 의도한 값으로 바뀌어야 성공입니다. 강력 새로고침에서만 새 화면이면 브라우저·CDN 캐시 계층을 따로 확인합니다. CORS 콘솔 메시지만으로 프록시 문제라고 단정하지 않습니다.

복구와 검증 상태

문제가 확대되면 변경 전 환경 설정과 이전 정상 배포의 응답을 대조해 설정을 되돌리고 재배포합니다. 브라우저·CDN 캐시 갱신 후에도 주요 기능을 확인합니다. 실제 Production 응답과 캐시 변경은 미확인입니다.

다음 학습

다음 글에서는 오류를 발견한 뒤 빠르게 복구하기 위한 build/runtime 로그, 최소 모니터링, 알림, rollback, incident 기록을 정리합니다.

다음 글: 프론트엔드 운영 모니터링: 로그·알림·롤백·장애 대응

다음 회차 확인 과제: 10회차에서는 수집한 배포 SHA와 로그·응답 기록을 이용해 알림 조건과 안전한 복구 절차를 문서화합니다.

공식 문서

공식 문서 확인 기준: 2026-09-21

체크포인트 다운로드

deploy-step-09-runtime-diff 완료 ZIP 다운로드

시작 체크포인트: 8회차 완료 ZIP

글 소개·학습 준비·전체 목차
두 서버 환경을 비교하는 배포 환경 차이 점검 표지

배포·서버 · 9 / 11 · 수정 실습 · 환경 차이 진단

로컬에서는 정상인데 Preview나 Production에서만 문제가 발생하면 코드를 무작정 다시 작성하기보다 “환경이 무엇이 다른가?”를 먼저 확인해야 합니다.

같은 commit이라도 환경변수, 요청 대상 URL, 프록시, 응답 헤더, CDN 캐시, 배포 버전이 다르면 결과가 달라질 수 있습니다.

이번 글에서 배우는 내용

  • Development / Preview / Production 환경 차이
  • 환경변수 범위와 값 차이
  • origin과 reverse proxy 개념
  • Cache-Control과 CDN 캐시
  • CORS 오류와 서버 오류 구분
  • 현재 배포 버전 확인
  • 증거를 수집한 뒤 수정하는 운영 진단 순서

이 글이 도움이 되었나요?

조회 중

분류별 과정 이동 (선택)

읽음 기록

읽음 — / 전체 11개 · 필수 7개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기