개발 노트/Backend,CMS,API

[Backend/CMS] CMS를 Next.js로 전환할 때 API 경계를 유지하는 기준

pposooj 2026. 5. 21. 12:29

기존 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 전환 체크리스트