기술 포스트

백엔드 협업 시 가장 많이 발생하는 충돌 포인트


Article

퍼블리셔와 백엔드 개발자가 같은 프로젝트에서 작업할 때, 예상보다 자주 같은 유형의 충돌이 반복됩니다. 대부분은 "누가 못해서"가 아니라 서로 보는 관점이 달라서 생기는 문제입니다.

실제로 자주 보는 케이스 세 가지를 코드와 함께 정리해보겠습니다.


1. 데이터 구조를 모르고 마크업하는 문제

시안에 유저 프로필이 이렇게 보인다고 치겠습니다:

김민수 프론트엔드 개발자

퍼블리셔는 보이는 대로 짭니다:

<div class="profile">
  <p class="profile__name">김민수</p>
  <p class="profile__role">프론트엔드 개발자</p>
</div>

근데 실제 API 응답은 이렇게 옵니다:

{
  "user": {
    "displayName": "김민수",
    "position": {
      "team": "개발팀",
      "title": "프론트엔드 개발자",
      "level": "시니어"
    }
  }
}

개발자가 이 데이터를 컴포넌트에 연결하려면 마크업 구조를 바꿔야 합니다. role이라는 단일 텍스트가 아니라 position 객체 안에 여러 필드가 있으니까요.

TSX에서 이걸 처음부터 고려하면 이렇게 됩니다:

interface ProfileProps {
  displayName: string;
  position: {
    team: string;
    title: string;
    level: string;
  };
}

export default function Profile({ displayName, position }: ProfileProps) {
  return (
    <div className="flex flex-col gap-1">
      <p className="text-base font-semibold text-stone-900">{displayName}</p>
      <p className="text-sm text-stone-500">
        {position.team} · {position.title}
      </p>
    </div>
  );
}

포인트는 데이터 구조를 interface로 먼저 정의하는 습관입니다. 이것만 해도 개발자와의 충돌이 확 줄어듭니다.


2. 상태 처리의 누락

이게 가장 흔합니다. 퍼블리셔는 시안에 보이는 "정상 상태"만 만듭니다. 근데 실제 화면에는 최소 네 가지 상태가 있습니다:

// 이 네 가지 상태를 모두 처리해야 합니다

// 1. 로딩 중
<div className="animate-pulse h-40 bg-stone-100 rounded" />

// 2. 에러 발생
<div className="p-4 text-sm text-red-600 bg-red-50 rounded">
  데이터를 불러오지 못했습니다.
</div>

// 3. 데이터 없음
<div className="p-8 text-center text-stone-400">
  등록된 게시글이 없습니다.
</div>

// 4. 정상 (퍼블리셔가 보통 이것만 만듭니다)
<ul className="grid gap-4">
  {posts.map((post) => (
    <li key={post.id}>
      <PostCard {...post} />
    </li>
  ))}
</ul>

디자인 시안에 로딩이나 에러 화면이 포함되지 않는 경우가 많습니다. 그래서 퍼블리셔도 인식하지 못하고 넘어갑니다. 나중에 개발자가 이 상태들의 UI를 따로 만들어야 하는데, 디자인 가이드도 없으니 임의로 만들게 됩니다. 결과물이 일관되지 않습니다.

이걸 퍼블리셔 단계에서 챙기면 이렇게 깔끔해집니다:

interface PostListProps {
  posts: Post[];
  isLoading: boolean;
  error: string | null;
}

export default function PostList({ posts, isLoading, error }: PostListProps) {
  if (isLoading) {
    return (
      <div className="grid gap-4">
        {[1, 2, 3].map((i) => (
          <div key={i} className="animate-pulse h-32 bg-stone-100 rounded" />
        ))}
      </div>
    );
  }

  if (error) {
    return (
      <div className="p-4 text-sm text-red-600 bg-red-50 rounded">
        {error}
      </div>
    );
  }

  if (posts.length === 0) {
    return (
      <div className="py-12 text-center text-stone-400">
        등록된 게시글이 없습니다.
      </div>
    );
  }

  return (
    <ul className="grid gap-4">
      {posts.map((post) => (
        <li key={post.id}>
          <PostCard {...post} />
        </li>
      ))}
    </ul>
  );
}

개발자는 isLoading, error, posts만 넘기면 됩니다. UI는 이미 다 만들어져 있으니까요.


3. 리스트 렌더링에서 생기는 구조 불일치

시안에 카드가 3개 보인다고 해서 HTML로 3개를 하드코딩하면, 개발자가 동적 렌더링으로 전부 다시 짜야 합니다.

<!-- 이렇게 하면 개발자가 힘들다 -->
<div class="card-list">
  <div class="card">프로젝트 A</div>
  <div class="card">프로젝트 B</div>
  <div class="card">프로젝트 C</div>
</div>

처음부터 map()으로 짜는 습관을 들이면 됩니다:

interface Project {
  id: string;
  title: string;
  description: string;
  thumbnail: string;
}

interface ProjectListProps {
  projects: Project[];
}

export default function ProjectList({ projects }: ProjectListProps) {
  return (
    <div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
      {projects.map((project) => (
        <article key={project.id} className="overflow-hidden rounded border border-stone-200">
          <img
            src={project.thumbnail}
            alt={project.title}
            className="h-48 w-full object-cover"
          />
          <div className="p-4">
            <h3 className="font-semibold text-stone-900">{project.title}</h3>
            <p className="mt-1 text-sm text-stone-500">{project.description}</p>
          </div>
        </article>
      ))}
    </div>
  );
}

데이터가 3개든 30개든 같은 컴포넌트로 처리됩니다.


정리

이 충돌들은 퍼블리셔가 "개발을 잘해서" 해결되는 문제가 아닙니다. 습관 세 가지만 잡으면 대부분 사전에 방지됩니다:

  1. 데이터 구조 먼저 확인 — 시안 보기 전에 API 응답 형태를 체크
  2. 4가지 상태 체크 — 로딩, 에러, 빈 상태, 정상을 항상 세트로 생각
  3. 리스트는 항상 map() — 하드코딩은 절대 하지 않기

이 세 가지를 시작 전 체크리스트로 두면, 개발자와의 불필요한 충돌이 눈에 띄게 줄어듭니다. 저도 이걸 체크리스트화해서 교육에 넣은 이후로 피드백이 확실히 좋아졌습니다.