콘텐츠로 이동

프론트엔드와 백엔드 서버 구조

11/9(월) → 11/10(화)·2번 물음

한 줄 요약: 브라우저에서 도는 코드는 누구나 열어 볼 수 있다. 그래서 비밀값을 들고 대신 요청해 주는 서버가 필요하다. Next.js는 그 자체가 Node.js 서버로 돌기 때문에 화면과 그 서버 역할을 함께 맡을 수 있다.

📍 만난 상황

  • 2026-11-09 — 2번 물음 → 그날 일지
  • 14:10 대시보드를 외부 앱처럼 따로 만들기로 했다. 백엔드는 이미 dhub2-manager가 있는데 왜 서버를 또 만드는지 물었다.
  • 15:54 백엔드 계획을 짜다가 "그냥 Next.js로 서버 없이 하는 건 불가능한가?"라고 물었다.
  • 결론: 매니저 앞에 붙는 얇은 중계 서버가 필요하고, 그 역할을 Next.js 하나가 화면과 함께 맡기로 했다.
  • 2026-11-10 — 1번 물음 → 그날 일지
  • 13:23 아키텍처 설명을 준비하다가 "Next.js의 서버 코드가 BFF 역할을 한다"는 답을 들었다. 이어서 "Next.js로 프론트와 백을 둘 다 할 수 있는 이유는? 나중에 복잡해져도 유지할 수 있나?"를 물었다.
  • 지난번과 달라진 점: "서버가 왜 필요한가"에서 한 단계 나아가, 한 프로그램이 어떻게 둘 다 하는지와 어디까지 버티는지를 물었다.

🧩 쉽게 말하면

식당을 떠올리면 된다. 손님 테이블(브라우저)에 금고 열쇠(비밀값)를 둘 수는 없다. 손님은 홀 직원(서버)에게 주문만 한다. 직원이 열쇠를 들고 주방(매니저)에 가서 음식을 받아 온다.

Next.js는 메뉴판을 나눠 주는 일과 주문을 받아 오는 일을 한 사람이 하는 가게다. 직원이 둘일 필요가 없어 작은 가게에 편하다. 다만 "새벽마다 장 보기"나 "케이크 3시간 굽기" 같은 일은 이 직원 혼자 맡기 어렵다.

📖 제대로 알기

프론트엔드와 백엔드

  • 프론트엔드: 이용자 브라우저에서 도는 코드다. 화면을 그리고 클릭을 받는다. 누구나 개발자 도구로 내용을 볼 수 있다.
  • 백엔드: 서버 컴퓨터에서 도는 코드다. 이용자는 결과만 받는다.
  • 브라우저에 두면 안 되는 것: 토큰(접근 권한 문자열), client secret(로그인 서버에 앱임을 증명하는 비밀값), DB 비밀번호.

CORS

CORS(Cross-Origin Resource Sharing: 다른 주소 사이의 요청 허용 규칙)는 브라우저가 지키는 보안 규칙이다. 페이지 주소와 다른 주소로 요청하면, 받는 쪽이 허락해야 결과를 볼 수 있다. dhub2-manager는 등록된 주소만 받으므로 브라우저에서 바로 부를 수 없다.

서버끼리 통신에는 CORS가 걸리지 않는다. 그래서 중계 서버가 대신 부른다. 화면 하나를 위한 이 중계 서버를 BFF라고 부른다. → BFF 패턴

React만 쓸 때 vs Next.js

  • React만(예: Vite로 만든 앱): 결과물은 HTML·JS 정적 파일뿐이다. 서버가 없으니 비밀을 숨길 곳도, DB에 붙을 곳도 없다. 백엔드를 따로 만들어야 한다.
  • Next.js(기본 실행 방식): 빌드 결과에 Node.js 서버 프로그램이 들어 있다. 그 서버가 화면 파일도 보내고 API도 처리한다.
  • 예외: 정적 내보내기(output: 'export')를 쓰면 Next.js도 파일만 만든다. 이때는 서버가 없다.

한 코드베이스가 빌드 때 두 갈래로 나뉜다

갈래 어디서 도나 예
클라이언트 코드 ("use client") 브라우저 조건 패널, 지도, 표, 즐겨찾기 버튼
서버 코드 Node.js 서버 route.ts API, page.tsx의 DB 읽기, src/server/**
  • Route Handler(app/api/**/route.ts): GET·POST 함수를 내보내면 그대로 HTTP API가 된다. Express의 app.get(...)과 같은 역할이다.
  • Server Component(서버에서 미리 그리는 화면 조각, page.tsx의 기본): 화면을 그리기 전에 서버에서 PG를 직접 읽는다. 별도 API를 거치지 않는다.
  • import "server-only": 이 줄이 있는 파일이 브라우저 쪽에 섞이면 빌드가 실패한다. 토큰·SQL 코드가 새지 않게 막는 안전장치다.
  • 서버 쪽은 일반 Node.js라 pg 드라이버, node:crypto, 환경 변수를 그대로 쓴다.

⚙️ 어떻게 동작하나

"침수 · 2022-08 · 강남구"로 건수를 볼 때를 따라가 본다.

  1. 브라우저가 Next.js의 /api/...로 조건만 보낸다. 같은 주소라 CORS도 없다.
  2. Route Handler가 세션 쿠키로 누구인지 확인한다.
  3. 서버가 PG에서 데이터셋 정보를 읽고 SQL을 만든다.
  4. 서버가 자기만 가진 토큰을 붙여 매니저에 보낸다.
  5. 매니저가 권한을 검사하고 ClickHouse에서 결과를 돌려준다.
  6. 서버가 결과를 정리해 브라우저에 준다.
flowchart LR
  B["브라우저<br/>use client 코드"] -->|"① 조건만"| N["Node 서버<br/>Route Handler"]
  N -->|"③ 메타 읽기"| P[("PostgreSQL")]
  N -->|"④ SQL + 토큰"| M["dhub2-manager"]
  M --> C[("ClickHouse")]

B와 N은 같은 Next.js 프로젝트, 같은 컨테이너에서 나온다. 그래도 B는 브라우저에서, N은 서버에서 돈다. 토큰은 N 쪽에만 있다.

🧪 예시

  • 예시 1 · 일상에 빗대어: 법인카드(토큰)를 고객 100명에게 나눠 주면 1명만 잃어버려도 끝이다. 직원 1명이 카드를 들고 100명의 주문을 받아 결제하면, 위험한 물건이 있는 곳이 100곳에서 1곳으로 준다.

  • 예시 2 · 실제로는: Route Handler 하나의 뼈대다.

// app/api/count/route.ts
import "server-only";                                  // ①
export async function POST(req: Request) {
  const { region, from, to } = await req.json();      // ②
  const sql = buildCountSql(region, from, to);        // ③
  const res = await fetch(`${process.env.MANAGER_URL}/analytics/query`, {
    method: "POST",
    headers: { Authorization: `Bearer ${await getToken()}` }, // ④
    body: JSON.stringify({ sql }),
  });
  return Response.json(await res.json());
}
① route.ts는 원래 서버에서만 돈다. 이 줄은 화면 코드가 이 파일을 실수로 불러오면 빌드를 멈추는 이중 안전장치다. 토큰을 다루는 src/server/** 파일에 특히 붙인다. ② 브라우저가 보낸 것은 조건뿐이다. ③ SQL은 서버가 틀로 만든다. 값은 검사를 거쳐 넣는다. ④ 토큰은 서버 환경 변수·세션에서 꺼낸다. 브라우저 코드에는 없다.

⚖️ 비교 · 흔한 오해

구조 프로그램 수 특징
React 화면 + FastAPI 백엔드 2개 역할이 뚜렷이 나뉨, 배포·인증 연결이 2배
Next.js(화면 + 서버 코드) 1개 언어·저장소·컨테이너 하나

하는 일은 같고, 몇 개로 나눠 띄우느냐만 다르다.

  • 헷갈렸던 점: "Next.js = 프론트엔드 도구" — React 화면을 만드는 데 쓰니 그렇게 보인다. 하지만 기본 실행 방식은 Node 서버다. Express로 만든 백엔드가 할 수 있는 일을 서버 코드에서 그대로 할 수 있다.
  • 헷갈렸던 점: "복잡해지면 Next.js를 버려야 한다" — 어떤 복잡함인지에 따라 다르다. 화면이 복잡해지는 건 문제없다. 결국 React이기 때문이다. 문제는 요청-응답 구조에 맞지 않는 백엔드 일이다(아래 표).
  • 오해: "CORS를 풀면 해결된다" — 풀어도 토큰을 브라우저에 둬야 하는 문제는 그대로다.

🛠️ 실무에서는

Next.js 서버는 "요청이 오면 응답하고 끝" 구조다. 여기에 안 맞는 일은 떼어 낸다.

이런 일이 생기면 Next.js 안에서 떼어 내는 곳
정해진 시간마다 도는 작업 요청이 없으면 안 돎 k8s CronJob (기존 스크립트 재사용)
오래 걸리는 작업 (큰 CSV) 요청이 길게 붙잡혀 끊김 작업 큐 + 워커
무거운 계산 이벤트 루프를 막아 모두 멈춤 worker_threads나 별도 서비스

떼어 내도 같은 저장소·같은 Docker 이미지에서 시작 명령만 바꿔 띄우는 경우가 많다. 화면과 BFF까지 버릴 필요는 없다.

  • 체크리스트
  • 비밀값이 NEXT_PUBLIC_(브라우저 공개 변수)에 들어가지 않았나.
  • 비밀값을 쓰는 파일에 server-only가 붙어 있나.
  • 관리자 API는 서버에서 역할을 확인하나(버튼 숨기기만으로는 부족).
  • 문제가 생기면 먼저 볼 것: 브라우저 Network 탭에서 요청이 어디로 갔는지, CORS 오류가 떴는지, 상태 코드(401·403·502)가 무엇인지 본다.

❓ 자주 묻는 질문

  • Q. 어떤 코드가 브라우저로 가는지 어떻게 아나? A. "use client"가 붙은 파일과 그 파일이 불러오는 것만 브라우저로 간다. 나머지는 서버에서 돈다.
  • Q. 서버끼리는 CORS가 없다면, 아무 서버나 매니저를 부를 수 있나? A. 요청은 갈 수 있다. 하지만 매니저가 토큰을 검사해서, 없으면 거절한다.
  • Q. 화면 코드에 비밀값을 넣고 난독화하면 안 되나? A. 안 된다. 브라우저가 실행하려면 결국 값을 알아야 하므로 꺼낼 수 있다.

🔭 더 깊이 보기

📚 참고 자료

  • 교차 출처 리소스 공유(CORS) (MDN 한국어) — 브라우저가 다른 주소 요청을 막고 허락하는 방식 · 링크
  • 서버 측 웹사이트 프로그래밍 첫걸음 (MDN 한국어) — 서버 쪽 코드가 무엇을 하는지 입문 · 링크
  • Next.js Docs (영문) — Server and Client Components, Route Handlers 항목 · 링크

🗓️ 이력

  • 2026-11-09: 매니저가 있는데 서버가 왜 필요한지, Next.js로 서버 없이 되는지 물음 → 일지
  • 2026-11-10: Next.js가 프론트·백을 함께 하는 이유와, 복잡해질 때 떼어 낼 부분을 물음 → 일지

🕸️ 연결된 주제