코드리뷰에서 나온 질문 정리
딜러 관리자 웹 작업을 하면서 리뷰 때 반복적으로 나온 질문들을 정리했다. 리뷰는 결국 “왜 이렇게 했어?”의 연속인데, 좋은 질문일수록 그 자리에서 답하기가 애매하다. 나중에 또 나올 질문이라 아예 답을 문서로 박아둔다. 실제로 딜러 관리자 웹에서 걸렸던 주제 위주로 적었다.
딜러 관리자 웹은 Next.js 기반이다. 아래 질문들은 이 저장소의 실제 작업에서 나온 것들이다.
Q1. 약관 페이지, 왜 노션 API로 읽어오나? 그냥 하드코딩하면 안 돼?
이용약관 같은 정적 문구는 처음엔 컴포넌트에 하드코딩돼 있었다. 문제는 비개발자가 문구를 못 고친다는 것. 약관 한 줄 바꾸려고 배포를 태우는 게 낭비였다.
그래서 노션을 CMS처럼 썼다. 노션 공식 API로 페이지를 읽어와 앱 자체 페이지로 렌더링한다. 문구는 노션에서 관리하니 비개발자가 직접 수정하고, 재배포가 필요 없다.
// src/api/fetchTerms.ts (요지)
import { Client } from '@notionhq/client';
import { NotionToMarkdown } from 'notion-to-md';
import { marked } from 'marked';
// ⚠️ NOTION_TOKEN 은 서버 전용 시크릿. getStaticProps 안에서만 import 한다.
const notion = new Client({ auth: process.env.NOTION_TOKEN });
const n2m = new NotionToMarkdown({ notionClient: notion });
marked.setOptions({ gfm: true, breaks: true }); // 노션 소프트 개행도 <br>로 살림
export async function fetchTermsHtml(pageId: string): Promise<string> {
const mdBlocks = await n2m.pageToMarkdown(pageId);
const markdown = n2m.toMarkdownString(mdBlocks).parent ?? '';
return marked.parse(markdown); // 노션 → 마크다운 → HTML
}
Q2. 그럼 매 요청마다 노션을 때리나? 느리지 않나?
아니다. 이게 리뷰에서 꼭 나오는 질문이다. 매 요청 노션을 호출하면 느리고, 노션 장애가 곧 우리 페이지 장애가 된다.
그래서 ISR(Incremental Static Regeneration) 을 썼다. getStaticProps에서 노션을 읽어 정적으로 만들어두고, revalidate로 주기적으로만 갱신한다.
export const getStaticProps = async () => {
const html = await fetchTermsHtml(TERMS_PAGE_ID);
return {
props: { html },
revalidate: 3600, // 1시간마다 백그라운드 재생성
};
};
- 사용자는 항상 캐시된 정적 페이지를 받는다 → 빠르다.
- 노션에서 약관을 고치면 최대 1시간 뒤 자동 반영 → 재배포 불필요.
- 노션이 잠깐 죽어도 마지막으로 생성된 정적 페이지가 떠 있다 → 장애 격리.
“실시간 반영이 필요한가?”를 먼저 물어야 한다. 약관은 1시간 지연이 전혀 문제가 안 되는 데이터라 ISR이 정확히 맞았다.
Q3. NOTION_TOKEN 이 클라이언트로 새어나가지 않나?
리뷰에서 반드시 확인하는 지점이다. 서버 전용 시크릿이 클라이언트 번들에 들어가면 사고다.
fetchTerms.ts는getStaticProps안에서만 import 한다. Next.js는getStaticProps/서버 코드에서만 쓰인 모듈을 클라이언트 번들에서 제외한다.- 환경변수 이름에
NEXT_PUBLIC_접두사를 붙이지 않는다. 이 접두사가 있으면 클라이언트로 노출된다. - 파일 상단에 “이 파일은 서버에서만 import 하라”는 주석을 남겨서, 다음 사람이 실수로 컴포넌트에서 import 하지 않게 한다.
💡 시크릿 관련 리뷰 질문의 핵심은 “이 토큰이 브라우저까지 가느냐”다. import 경로와 접두사 규칙 두 개만 지켜도 대부분 막힌다.
Q4. 배포 워크플로우에서 GITHUB_TOKEN 권한은 왜 명시했나?
GitHub Actions의 GITHUB_TOKEN은 아무것도 안 적으면 넓은 기본 권한을 갖는다. 리뷰에서 “이 워크플로우가 대체 무슨 권한을 쓰는데?”라는 질문이 나왔고, 답이 애매하면 그 자체가 문제다.
그래서 워크플로우 레벨에서 최소 권한 원칙으로 명시했다.
# 워크플로우가 어떤 권한을 쓰는지 명확히, 탈취돼도 최소 권한만 노출
permissions:
contents: read
packages: read
- 워크플로우가 필요한 권한만 선언 → 읽는 사람이 의도를 안다.
- 토큰이 탈취돼도 노출되는 권한이 read로 제한된다.
리뷰 관점에서 좋은 습관이다. “기본값이 뭔지 모르는 권한”을 코드에 남기지 않는 것.
Q5. 팝업/배너 노출 조건이 코드에 흩어지는데 어쩌지?
딜러 관리자 웹은 요금 변경 안내, 발신번호 변경 안내 같은 기간·조건부 팝업이 많다. 리뷰에서 “이 노출 조건 또 하드코딩이야?”가 반복됐다. 실제로 노출 날짜/시간/차량 브랜드 조건이 여러 커밋에 걸쳐 계속 바뀌었다.
여기서 배운 것:
- 노출 조건(시작일·종료일·대상)은 컴포넌트 로직에서 분리해 데이터로 다룬다. 조건이 자주 바뀌는 걸 전제해야 한다.
- “dev 상시 노출” 같은 환경별 분기가 필요하면 조건에 환경 플래그를 명시적으로 넣는다.
- 공용 팝업 레이아웃과 개별 콘텐츠를 분리한다(레이아웃 재사용, 콘텐츠만 교체). 실제로 payment 팝업 콘텐츠와 공용 팝업 레이아웃을 분리하는 리팩토링을 했다.
정리
딜러 관리자 웹 리뷰에서 반복된 질문을 다섯 개로 묶으면 이렇다.
- 정적 문구는 CMS(노션)로 빼서 비개발자가 관리하게.
- 매 요청 외부 API를 때리지 말고 ISR로 캐시 + 장애 격리.
- 서버 시크릿은 import 경로와
NEXT_PUBLIC_규칙으로 클라이언트 유출 차단. - CI 토큰은 최소 권한을 명시.
- 자주 바뀌는 노출 조건은 코드가 아니라 데이터로.
리뷰 질문을 정리해두면 다음 사람이 같은 질문에 다시 답하지 않아도 된다. 이게 리뷰 문서화의 진짜 이득이다.
관련 작업
- 이용약관 노션 API 연동(ISR 자동반영) —
getStaticProps+revalidate: 3600 - GITHUB_TOKEN 권한 명시적 설정(최소 권한 원칙)
- Payment popup content와 공용 popup layout 분리
- 차량 brandId로 요금변경 팝업 노출 조건 분기
- 이용약관 안내 팝업 노출 시간/날짜 조건 수정