우리 팀은 왜 Toast 사용 규칙을 다시 정의했을까

유소정·2026년 8월 10일

들어가며

프론트엔드를 개발하다 보면 "성공했습니다", "실패했습니다" 같은 짧은 Toast 메시지 하나쯤은 대수롭지 않게 느껴지기 쉽습니다. 하지만 여러 개발자가 같은 코드베이스를 오래 다루다 보면, 이 작은 UI 요소 하나에도 원칙이 없으면 금방 혼란이 쌓입니다.

저희 팀도 그랬습니다. Colo 1.0을 운영하면서 Toast는 화면마다, 개발자마다 제각각의 방식으로 호출되고 있었고, 이는 크고 작은 문제로 이어졌습니다. 이 글은 그 문제들을 어떻게 발견했고, 어떤 기준으로 해결했는지 정리한 트러블슈팅 기록입니다.


문제 상황: 무엇이 불편했나

1. 같은 Toast가 중복으로 쌓인다

가장 눈에 띄는 문제는 중복 노출이었습니다. API가 짧은 시간 안에 여러 번 실패하거나, 사용자가 같은 액션을 반복해서 누르면 동일한 문구의 Toast가 화면에 계속 쌓였습니다. 이전 Toast가 사라지기도 전에 같은 메시지가 겹쳐서 뜨는 경우, 사용자 입장에서는 오류가 여러 번 발생한 것처럼 보이거나 화면이 지저분해 보이는 문제가 있었습니다.

원인을 들여다보니, Toast를 호출하는 쪽마다 별도의 key 관리 없이 그때그때 message.error() / message.success()를 직접 호출하고 있었습니다. 즉, "같은 의미의 Toast는 하나만 보여준다"는 규칙 자체가 없었던 것입니다.

2. 실패 처리 방식이 통로마다 달랐다

API 실패를 처리하는 방식도 일관되지 않았습니다. 어떤 훅은 Axios 인터셉터를 통해 자동으로 에러 Toast가 뜨는 반면, 어떤 훅은 인터셉터가 붙지 않은 통로(바닐라 Axios, 표준과 다른 응답 구조 등)를 사용하고 있어서 개발자가 매번 직접 에러 처리를 해야 했습니다.

문제는 이 구분 기준이 문서화되어 있지 않았다는 점입니다. 그러다 보니 신규 기능을 만들 때마다 "이 API는 자동으로 Toast가 뜨나요, 직접 처리해야 하나요?"를 매번 코드를 뒤져서 확인해야 했고, 실수로 에러 처리를 빠뜨려 사용자가 아무 피드백도 받지 못하는 경우도 있었습니다.

3. 성공/경고 Toast의 사용 기준이 없었다

실패 Toast는 그나마 "실패했으니 알려야 한다"는 직관이라도 있었지만, 성공 Toast와 경고 Toast는 기준 자체가 모호했습니다.

  • 단순 조회가 성공했을 때도 Toast를 띄워야 하는가?
  • 페이지 이동 후에도 성공 메시지가 필요한가?
  • 폼 검증 실패는 에러로 처리해야 하는가, 경고로 처리해야 하는가?

이런 질문에 대한 답이 팀 내에 공유되어 있지 않다 보니, 개발자 개인의 판단에 따라 Toast가 남발되거나 반대로 필요한 순간에 빠지는 일이 반복됐습니다.

4. 문구 톤이 제각각이었다

마지막으로, 문구 자체의 톤도 통일되어 있지 않았습니다. "저장됐어요!", "저장되었습니다.", "저장됐습니다 🎉" 처럼 같은 상황에도 다른 어미와 뉘앙스가 섞여 있었고, 개중에는 "잘못 입력했습니다"처럼 은근히 사용자에게 책임을 돌리는 듯한 문구도 있었습니다. 서비스 전체로 보면 이런 디테일이 브랜드 톤앤매너를 흐리는 요인이 되고 있었습니다.


왜 이 문서를 쓰게 됐나

이런 문제들은 하나하나 보면 사소해 보이지만, 누적되면 두 가지 큰 비용을 만듭니다.

  1. 의사결정 비용: "이 상황에 Toast를 띄워야 하나, 어떻게 띄워야 하나"를 매번 새로 고민해야 했습니다.
  2. 일관성 비용: 같은 서비스 안에서 화면마다 다른 규칙이 적용되면서 사용자 경험이 들쭉날쭉해졌습니다.

그래서 저희는 기존 방식을 그대로 답습하기보다, Colo 1.0에서 실제로 어떻게 Toast가 쓰이고 있었는지를 먼저 코드 레벨에서 파악하고, 그 문제점을 명시적으로 정리한 뒤, 앞으로 새로운 프로젝트(Flamingo)에서 지킬 공통 규칙을 세우기로 했습니다.

즉 이 문서의 목적은 새로운 걸 발명하는 것이 아니라, 암묵적으로 존재하던 판단 기준을 명시적인 규칙으로 끌어올리는 것이었습니다.


어떻게 풀었나

1) Toast를 용도에 따라 3가지로 분류했다

먼저 Toast가 쓰이는 상황을 실패(error) / 성공(success) / 경고(warning) 세 가지로 나누고, 각각 언제 쓰는지, 자동으로 처리할지 수동으로 처리할지를 표로 명확히 정의했습니다. 특히 "자동으로 처리하지 않는 이유"까지 함께 남겨서, 나중에 "왜 성공 Toast는 인터셉터에서 자동으로 안 띄우지?" 같은 질문이 다시 나오지 않도록 했습니다.

2) 인터셉터 기준으로 자동/수동을 나눴다

에러 처리는 원칙적으로 Axios 인터셉터(flamingoErrorFn)가 4xx/5xx 응답과 네트워크 단절을 감지해 자동으로 처리하도록 표준화했습니다. 다만 인터셉터가 적용되지 않는 예외 통로(바닐라 axios, 비표준 응답 구조)가 실제로 존재하기 때문에, 이 경우에는 호출부에서 직접 message.error()를 호출하도록 예외 규칙도 함께 정리했습니다. "왜 자동/수동이 공존하는가"를 별도 섹션으로 남긴 것도 이 지점에서 나온 질문에 답하기 위해서입니다.

3) 중복 방지를 위한 공통 key 생성 규칙을 만들었다

buildMessageKey(url, content)라는 유틸을 만들어, URL과 문구를 조합한 고유 key로 Toast를 호출하도록 통일했습니다. 같은 key로 Toast가 다시 호출되면 기존 Toast를 새 Toast로 교체하는 Ant Design의 동작을 활용해, 동일한 Toast가 중복으로 쌓이는 문제를 구조적으로 해결했습니다.

4) 문구 가이드를 만들었다

마지막으로 문구 톤을 통일하기 위해 권장 표현과 피해야 할 표현을 나란히 정리했습니다. 결과를 안내할 때는 "-되었습니다/-습니다"로, 사용자에게 조치를 요청할 때는 "-해주세요"로 끝맺도록 규칙을 세웠고, 이모지나 감탄사, 책임을 전가하는 뉘앙스의 표현은 지양하도록 명시했습니다.


마치며

이 작업을 통해 얻은 건 단순히 "Toast를 이렇게 쓰자"는 규칙 목록이 아니었습니다. 오히려 "왜 지금까지 이런 문제가 반복됐는가"를 코드 레벨에서 짚어보고, 그 원인을 없애는 구조(공통 인터셉터, 공통 key 생성 함수, 명시적 분류 기준)를 함께 설계한 것이 더 큰 소득이었습니다.

작은 UI 요소라도 팀 전체가 같은 기준으로 판단할 수 있게 만드는 것이 트러블슈팅의 핵심이었습니다!

profile
기술을 위한 기술이 되지 않도록!

0개의 댓글