기존 Backend/CMS 프로젝트를 Next.js로 전환하려고 할 때 가장 헷갈리는 부분은 화면과 API 사이의 경계를 어디에서 나눌지 정하는 일입니다. 관리자 CMS 화면을 Next.js로 바꾸거나, 앱 API 앞단에 BFF를 두는 구조를 검토하다 보면 기존 Laravel/PHP API, 관리자 route, 앱 API 응답 계약이 한꺼번에 흔들릴 수 있습니다.
이때 중요한 기준은 기존 앱 API 계약을 쉽게 깨지 않는 것입니다. 모바일 앱은 이미 배포된 버전이 있을 수 있고, 모든 사용자가 동시에 업데이트될 수 있는 것도 아닙니다. 그래서 CMS 화면을 Next.js로 바꾸더라도 앱 API의 DTO, 인증, 캐시, versioning 경계는 분리해서 유지해야 합니다.
처음에는 Next.js로 화면을 옮기면서 API도 같이 정리하면 깔끔할 것처럼 느껴질 수 있습니다. 하지만 실제 경험상 화면 전환과 앱 API 계약 변경은 다른 문제였습니다. 기존 앱 사용자가 남아 있다면 응답 필드 하나를 바꾸는 일도 조심해야 하는 것 같습니다.
이 글에서는 Backend/CMS를 Next.js로 전환할 때 API 경계를 유지하는 기준을 정리해보겠습니다. 전환 구조를 검토할 때 놓치기 쉬운 endpoint, DTO, 인증, 캐시, versioning 기준을 중심으로 살펴보겠습니다.
이 글에서 다룰 문제
Next.js 전환 과정에서 API 경계가 흐려지면 다음 문제가 생길 수 있습니다.
- CMS 화면 개편과 함께 앱 API 응답 형식이 바뀐다.
- 기존 모바일 앱 버전에서 breaking change가 발생한다.
- Next.js Route Handler가 내부 DB model을 그대로 반환한다.
- 관리자 인증과 앱 API 인증이 섞인다.
- SSR/ISR 캐시와 앱 API 캐시 정책이 혼동된다.
- BFF에서 내부 서버 URL, 관리자 URL, 토큰을 노출한다.
- API versioning 없이 기존 endpoint를 바로 바꿔 버린다.
전환 작업은 새 기술을 적용하는 일이기도 하지만, 기존 사용자를 깨뜨리지 않는 일이기도 합니다. 그래서 API 경계를 먼저 정해두는 것이 좋습니다.
Next.js 전환에서 말하는 API 경계란 무엇인가
API 경계는 앱이나 외부 클라이언트가 의존하는 계약입니다. endpoint, 요청 파라미터, 응답 필드, 오류 형식, 인증 방식, 캐시 정책이 모두 경계에 포함됩니다.
| 구분 | API 경계에 포함되는가 | 예시 |
|---|---|---|
| endpoint path | 포함 | /api/v1/notices |
| request params | 포함 | page, limit, keyword |
| response DTO | 포함 | id, title, publishedAt |
| error format | 포함 | code, message |
| auth policy | 포함 | public, bearer token, session |
| internal DB column | 제외 | admin_memo, deleted_at |
| CMS 화면 컴포넌트 | 제외 | 관리자 테이블 UI |
| 내부 서버 URL | 제외 | 비공개 upstream URL |
Next.js로 전환하더라도 이 경계는 유지되어야 합니다. 화면을 바꾸는 일과 앱 API 계약을 바꾸는 일은 분리해서 관리하는 편이 안전합니다.
전환 방식 비교
Next.js를 도입하는 방식은 여러 가지입니다. 어떤 방식이든 기존 앱 API 계약을 깨지 않는지 먼저 확인해야 합니다.
| 방식 | 장점 | 주의점 |
|---|---|---|
| CMS 화면만 Next.js로 전환 | 관리자 UI 개선이 쉬움 | 기존 API와 인증 연동 필요 |
| Next.js BFF 추가 | 프론트에 맞는 데이터 조립 가능 | 앱 API 계약과 BFF 응답을 구분해야 함 |
| 일부 API를 Route Handler로 이전 | Node 기반 API 관리 가능 | 기존 PHP/Laravel API와 versioning 필요 |
| 전체 백엔드 전환 | 구조 통일 가능 | 리스크가 크고 단계적 검증 필요 |
앱 API는 배포된 클라이언트와 연결되어 있으므로 화면 전환보다 더 조심스럽게 다뤄야 합니다.
앱 API 계약을 먼저 고정하기
전환 전에 현재 앱 API 계약을 문서화해야 합니다. 문서가 있어야 Next.js 전환 후에도 같은 계약을 유지하는지 확인할 수 있습니다.
{
"endpoint": "/api/v1/notices",
"method": "GET",
"params": {
"page": "number",
"limit": "number"
},
"response": {
"data": [
{
"id": "number",
"title": "string",
"summary": "string",
"publishedAt": "string"
}
],
"meta": {
"page": "number",
"limit": "number",
"hasNext": "boolean"
}
}
}
계약 문서 없이 화면 기준으로만 전환하면 앱에서 예상하지 못한 오류가 생길 수 있습니다. 특히 모바일 앱은 사용자가 바로 업데이트하지 않을 수 있기 때문에, 기존 응답 구조를 기준으로 회귀 테스트를 준비하는 편이 좋습니다.
DTO와 adapter로 내부 구현을 숨기기
Next.js BFF나 Route Handler를 사용하더라도 내부 응답을 그대로 앱에 넘기지 않는 편이 안전합니다. Laravel API에서 오든, CMS DB에서 오든, 최종 앱 응답은 DTO 형태로 고정해야 합니다.
type NoticeDto = {
id: number;
title: string;
summary: string;
publishedAt: string | null;
};
type UpstreamNotice = {
id: number;
title?: string;
summary?: string | null;
published_at?: string | null;
admin_memo?: string | null;
};
function toNoticeDto(item: UpstreamNotice): NoticeDto {
return {
id: item.id,
title: item.title ?? '',
summary: item.summary ?? '',
publishedAt: item.published_at ?? null,
};
}
여기서 admin_memo 같은 내부 필드는 DTO에 포함하지 않습니다. 내부 upstream 구조가 바뀌더라도 앱 응답은 DTO 기준으로 유지됩니다.
처음에는 upstream 응답을 그대로 내려주는 방식이 빠르게 느껴질 수 있습니다. 하지만 내부 필드가 한 번 앱 응답에 포함되면 나중에 제거하기가 조심스러워집니다. 중간 adapter에서 공개 필드만 고정하는 편이 더 안전합니다.
Next.js Route Handler 예시
Node.js 24 기준으로 이해하기 쉬운 형태로 정리했습니다.
// app/api/v1/notices/route.ts
type ApiResponse<T> = {
data: T;
meta?: Record<string, unknown>;
};
export async function GET(request: Request): Promise<Response> {
const url = new URL(request.url);
const page = Math.max(1, Number(url.searchParams.get('page') ?? 1));
const limit = Math.min(50, Math.max(1, Number(url.searchParams.get('limit') ?? 20)));
const upstreamItems: UpstreamNotice[] = await fetchNoticesFromInternalApi({page, limit});
const body: ApiResponse<NoticeDto[]> = {
data: upstreamItems.map(toNoticeDto),
meta: {
page,
limit,
hasNext: upstreamItems.length > limit,
},
};
return Response.json(body, {
headers: {
'Cache-Control': 'public, max-age=60',
},
});
}
async function fetchNoticesFromInternalApi(params: {page: number; limit: number}) {
// 실제 내부 서버 URL, 토큰, 관리자 경로는 코드 예제에 공개하지 않습니다.
return [] as UpstreamNotice[];
}
중요한 점은 Next.js가 응답을 만든다고 해서 내부 데이터를 그대로 노출하지 않는 것입니다. Route Handler 안에서도 DTO 변환과 응답 계약을 유지해야 합니다.
이 부분은 전환 과정에서 놓치기 쉽습니다. 기존 Backend에서 DTO나 serializer를 쓰던 이유는 Next.js로 옮긴다고 해도 사라지지 않습니다.
인증 경계 분리하기
관리자 CMS 인증과 앱 API 인증은 다르게 봐야 합니다. Next.js에서 관리자 화면과 API를 함께 다룰 때도 관리자 session 정보를 앱 API에 섞으면 안 됩니다.
| 구분 | 관리자 CMS | 앱 API/BFF |
|---|---|---|
| 인증 방식 | 관리자 세션, 관리자 계정 | public endpoint, token, app auth |
| 실패 응답 | 로그인 화면 또는 관리자 오류 | JSON 오류 응답 |
| 권한 범위 | 생성, 수정, 삭제, 검수 | 조회, 사용자별 접근 |
| 토큰 저장 | 서버 세션 중심 | 클라이언트 노출 최소화 |
앱 API는 앱이 이해할 수 있는 JSON 오류 형식을 유지해야 합니다. 관리자 인증 실패처럼 HTML 로그인 페이지가 내려가면 모바일 앱에서는 오류 처리가 어려워질 수 있습니다.
SSR/ISR 캐시와 앱 API 캐시 구분하기
Next.js에서는 SSR, ISR, fetch cache 같은 개념이 등장합니다. 그런데 이것을 앱 API 캐시와 혼동하면 안 됩니다.
| 캐시 | 대상 | 주의점 |
|---|---|---|
| SSR/ISR 캐시 | 웹 페이지 렌더링 | 페이지 표시 최신성 기준 |
| fetch cache | 서버 컴포넌트 데이터 fetch | 요청별 인증과 캐시 정책 확인 |
| 앱 API cache-control | 모바일 앱 API 응답 | 앱 클라이언트와 CDN 캐시 기준 |
| 내부 API 캐시 | upstream 조회 부하 감소 | 사용자별 데이터 공유 금지 |
앱 API는 모바일 클라이언트가 직접 의존하는 계약입니다. 웹 페이지 ISR 설정을 바꾼다고 앱 API 캐시 정책까지 자동으로 맞춰지는 것은 아닙니다. 캐시 정책은 화면 렌더링용인지, 앱 응답용인지 나눠서 보는 것이 좋습니다.
versioning과 breaking change 기준
전환 과정에서 응답 필드를 바꿔야 한다면 versioning을 고려해야 합니다. 기존 endpoint에서 필드를 삭제하거나 타입을 바꾸는 것은 breaking change가 될 수 있습니다.
/api/v1/notices 기존 앱 유지용
/api/v2/notices 새 응답 계약 적용
새 필드를 추가하는 것이 상대적으로 안전할 수 있지만 필드 삭제, 타입 변경, 오류 형식 변경은 기존 앱에서 문제를 만들 가능성이 큽니다.
| 변경 | 위험도 | 권장 방식 |
|---|---|---|
| 새 optional 필드 추가 | 낮음 | v1 유지 가능 |
| 필드 삭제 | 높음 | v2 검토 |
| 타입 변경 | 높음 | v2 검토 |
| 오류 형식 변경 | 높음 | v2 또는 호환 처리 |
| 내부 구현 변경 | 낮음 | DTO 계약 유지 시 가능 |
모바일 앱은 구버전 사용자가 남아 있을 수 있으므로 호환성을 먼저 생각해야 합니다.
전환 전 체크리스트
Next.js 전환 전에 아래 기준을 먼저 확인해보면 좋습니다. 가장 중요한 것은 기존 앱 버전에서 API 응답을 계속 정상적으로 파싱할 수 있는지입니다.
- 기존 앱 API endpoint와 응답 계약을 문서화한다.
- DTO/serializer로 내부 구현과 앱 응답을 분리한다.
- 관리자 CMS route와 앱 API route를 분리한다.
- Next.js BFF에서 내부 서버 URL과 토큰을 노출하지 않는다.
- SSR/ISR 캐시와 앱 API 캐시 정책을 구분한다.
- breaking change가 필요한 경우 versioning 계획을 세운다.
- 기존 앱 버전 회귀 테스트를 준비한다.
새 웹 화면은 정상이어도 기존 앱에서 API 응답을 파싱하지 못하면 장애가 됩니다. 그래서 화면 전환 테스트와 앱 API 회귀 테스트를 분리해서 가져가는 편이 좋습니다.
자주 하는 실수
1. CMS 화면 개편과 앱 API 변경을 한 번에 처리하는 경우가 있습니다. 둘을 같이 바꾸면 문제가 생겼을 때 원인을 찾기 어렵습니다.
2. Next.js Route Handler에서 내부 DB model이나 upstream 응답을 그대로 반환하는 경우도 있습니다. 기존 Backend에서 DTO를 쓰던 이유가 사라지지 않습니다.
3. 캐시 정책을 한 종류로 보는 경우가 있습니다. SSR/ISR 캐시와 앱 API Cache-Control은 목적이 다릅니다.
4. v1 endpoint를 바로 바꾸는 경우도 있습니다. 모바일 앱은 구버전 사용자가 남아 있을 수 있으므로 호환성을 먼저 생각해야 합니다.
결론
Backend/CMS를 Next.js로 전환할 때 핵심은 새 기술 도입 자체보다 API 경계를 유지하는 것입니다. 관리자 화면을 바꾸더라도 모바일 앱이 의존하는 endpoint, DTO, 오류 형식, 인증, 캐시 정책은 쉽게 깨지면 안 됩니다.
좋은 전환 방식은 기존 앱 API 계약을 먼저 문서화하고, Next.js BFF나 Route Handler에서도 DTO 변환을 유지하며, breaking change가 필요한 경우 versioning을 적용하는 것입니다. 또한 SSR/ISR 캐시와 앱 API 캐시를 분리해서 봐야 합니다.
처음에는 Next.js로 옮기는 작업이 화면 전환처럼 느껴질 수 있습니다. 하지만 앱 API가 연결되어 있다면 전환의 핵심은 호환성입니다. 기존 앱이 계속 정상 동작하는지 확인하면서 단계적으로 경계를 옮기는 방식이 안전합니다.
참고 자료
- Next.js Route Handlers, Server Components, Caching 공식 문서
- Laravel API Resource와 Middleware 관련 문서
- REST API versioning과 breaking change 관련 문서
- 프로젝트 내부 API 계약, DTO, BFF 전환 체크리스트
'개발 노트 > Backend,CMS,API' 카테고리의 다른 글
| [Backend/API] 관리자 인증과 앱 토큰 인증을 분리하는 기준 (0) | 2026.05.21 |
|---|---|
| [Backend/API] Backend 앱 API에서 오류 응답 형식을 고정하는 기준 (0) | 2026.05.21 |
| [Backend/API] 캐시와 rate limit을 설계하는 기준 (0) | 2026.05.21 |
| [Backend/API] 목록 조회 pagination, search, filter를 설계하는 기준 (0) | 2026.05.20 |
| [Backend/API] DTO와 serializer로 응답 형식을 고정하는 방법 (0) | 2026.05.20 |