BroadcastChannel을 정리하면서 생긴 궁금증 3가지

이명진·2026년 8월 17일

TIL

목록 보기
29/29

BroadcastChannel을 정리하면서 몇 가지 궁금한 점이 생겼다.

특히 실제 프로젝트에서 사용하려면 여러 탭에 어떻게 메시지를 전달하는지, 모든 컴포넌트에서 BroadcastChannel을 사용해야 하는지, 그리고 React Query만으로 해결할 수 있는 문제는 아닌지가 궁금했다.

이번 글에서는 이 부분을 정리해보려고 한다. 지피티의 도움으로 이를 해결할수 있어서 정리해본다. 


1. BroadcastChannel은 여러 탭에 어떻게 메시지를 전달할까?

먼저 브라우저에 다음과 같이 3개의 탭이 있다고 가정해보자.

Tab 1 → naver.com
Tab 2 → naver.com
Tab 3 → naver.com

그리고 세 탭 모두 다음과 같은 코드를 사용한다고 하자.

const channel = new BroadcastChannel('my-channel');

Tab 1에서 메시지를 전송한다.

channel.postMessage({
  type: 'LOGOUT',
});

그러면 Tab 2와 Tab 3에서 해당 메시지를 받을 수 있다.

                BroadcastChannel
                "my-channel"
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
        Tab 1       Tab 2       Tab 3
        naver       naver       naver
          │           │           │
          │           ▼           ▼
          │        message     message
          │
       postMessage()

여기서 처음에는 이런 의문이 생길 수 있다.

BroadcastChannel이 브라우저의 모든 탭을 어떻게 알고 있는 것일까?

개발자가 직접 탭을 찾아주는 것은 아니다.

BroadcastChannel은 같은 Origin에서 같은 이름의 채널을 사용하는 브라우징 컨텍스트를 기준으로 통신한다.

즉 다음과 같이 URL을 직접 비교할 필요가 없다.

if (window.location.href === 'https://naver.com') {
  // BroadcastChannel 전송
}

BroadcastChannel이 알아서 통신 범위를 결정한다.


Origin이란?

여기서 URL이 같다는 표현보다 정확한 표현은 Origin이 같다는 것이다.

Origin은 다음 세 가지로 구성된다.

scheme + host + port

예를 들어 다음은 같은 Origin이다.

https://example.com
https://example.com/products
https://example.com/products/123

경로가 달라도 Origin은 동일하다.

반면 다음은 다른 Origin이다.

https://example.com
https://api.example.com

또한 다음도 다른 Origin이다.

https://example.com
http://example.com

따라서 BroadcastChannel을 이해할 때는

"URL이 같은 탭끼리 통신한다."

보다는

"같은 Origin의 브라우징 컨텍스트 사이에서 통신한다."

라고 이해하는 것이 정확하다.


2. 그러면 특정 탭 하나에게만 메시지를 보낼 수 있을까?

BroadcastChannel은 기본적으로 특정 탭 하나를 지정해서 보내는 방식이 아니다.

예를 들어 다음과 같이 3개의 탭이 있다고 하자.

Tab 1
Tab 2
Tab 3

모두 같은 Origin이고 같은 채널을 사용한다.

Tab 1에서:

channel.postMessage({
  type: 'MESSAGE',
});

를 실행하면 같은 채널을 사용하는 다른 탭에서 메시지를 받을 수 있다.

Tab 1
  │
  │ postMessage()
  ▼
BroadcastChannel
  │
  ├──────> Tab 2
  │
  └──────> Tab 3

그렇다면 Tab 2에게만 보내고 싶다면 어떻게 해야 할까?

메시지에 대상 정보를 넣고 수신 측에서 필터링할 수 있다.

channel.postMessage({
  target: 'tab-2',
  type: 'MESSAGE',
});

그리고 각 탭에서는:

channel.addEventListener('message', (event) => {
  if (event.data.target !== myTabId) {
    return;
  }

  // 나에게 전달된 메시지만 처리
});

이런 식으로 구현할 수 있다.

중요한 것은 BroadcastChannel이 Tab 2를 직접 찾아서 전달하는 것은 아니라는 점이다.

메시지를 전달하고, 필요한 쪽에서 메시지를 처리하는 구조다.


3. 모든 컴포넌트에서 BroadcastChannel을 사용해야 할까?

여기서 실제 React 프로젝트를 생각해보자.

브라우저에 다음과 같은 세 개의 탭이 있다고 가정한다.

Tab 1
└── Main

Tab 2
└── Detail

Tab 3
└── Cart

Main과 Detail에는 BroadcastChannel 리스너가 있고 Cart에는 없다고 하자.

Main
 └── BroadcastChannel listener

Detail
 └── BroadcastChannel listener

Cart
 └── 없음

Main에서 로그아웃 이벤트를 전달한다.

channel.postMessage({
  type: 'LOGOUT',
});

그러면 Detail은 이벤트를 받을 수 있다.

하지만 Cart에는 BroadcastChannel 리스너가 없기 때문에 Cart에서 해당 이벤트를 직접 처리할 수 없다.

                BroadcastChannel
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
        Main       Detail      Cart
          ✓           ✓          X

그렇다면 모든 컴포넌트에 BroadcastChannel 리스너를 넣어야 할까?

그럴 필요는 없다.

오히려 모든 컴포넌트에서 BroadcastChannel을 생성하는 것은 좋은 구조가 아니다.


4. BroadcastChannel은 상위 레벨에서 관리할 수 있다

React에서는 애플리케이션의 상위 레벨에서 한 번 구독하는 구조를 사용할 수 있다.

예를 들어:

function App() {
  useBroadcastChannel();

  return <Router />;
}

그리고 Hook에서 BroadcastChannel을 관리한다.

function useBroadcastChannel() {
  useEffect(() => {
    const channel = new BroadcastChannel('app');

    const handleMessage = (event: MessageEvent) => {
      if (event.data.type === 'LOGOUT') {
        // 로그아웃 처리
      }
    };

    channel.addEventListener('message', handleMessage);

    return () => {
      channel.close();
    };
  }, []);
}

그러면 각 페이지는 BroadcastChannel을 직접 알 필요가 없다.

                 BroadcastChannel
                        │
                        ▼
                  App / Provider
                        │
                  이벤트 처리
                        │
          ┌─────────────┼─────────────┐
          ▼             ▼             ▼
        Main          Detail         Cart

이렇게 하면 Main, Detail, Cart 모두 BroadcastChannel 코드를 직접 가지고 있을 필요가 없다.


5. 전역 이벤트를 전역 상태로 연결할 수 있다

예를 들어 로그아웃 이벤트라면 App 레벨에서 이벤트를 받은 후 Zustand나 TanStack Query를 변경할 수 있다.

BroadcastChannel
        │
        │ LOGOUT
        ▼
App / Provider
        │
        ├───────────────┐
        ▼               ▼
     Zustand        TanStack Query
        │               │
        └───────┬───────┘
                ▼
             React UI

예를 들어 TanStack Query를 사용한다면:

const handleMessage = (event: MessageEvent) => {
  if (event.data.type === 'LOGOUT') {
    queryClient.clear();
  }
};

이렇게 하면 페이지 컴포넌트에서는 BroadcastChannel의 존재를 몰라도 된다.

이런 구조가 관심사의 분리 측면에서도 더 적절하다.


6. 그렇다면 React Query만 사용하면 안 될까?

이 부분이 가장 궁금했던 부분이다.

예를 들어 Tab 1에서 로그아웃한다고 하자.

Tab 1
  │
  │ logout
  ▼
Server
  │
  │ session 삭제
  ▼
로그아웃 완료

그러면 Tab 2에서 React Query로 사용자 정보를 다시 조회하면 로그아웃된 상태를 확인할 수 있다.

Tab 2
  │
  │ GET /me
  ▼
Server
  │
  ▼
401 Unauthorized
  │
  ▼
로그아웃 처리

따라서 BroadcastChannel 없이도 로그아웃을 구현할 수 있다.

그렇다면 왜 BroadcastChannel이 필요한 것일까?


7. 문제는 "언제 다시 조회하느냐"

TanStack Query가 서버 상태를 관리한다고 해서 서버의 데이터가 변경되는 순간 모든 탭이 자동으로 서버에 요청하는 것은 아니다.

예를 들어 Tab 2가 다음과 같은 Query를 가지고 있다고 하자.

const { data } = useQuery({
  queryKey: ['me'],
  queryFn: getMe,
});

현재 캐시에는 로그인한 사용자의 정보가 있다.

Tab 2

['me']
   ↓
{
  id: 1,
  name: 'user'
}

이 상태에서 Tab 1이 로그아웃했다고 하자.

Tab 1
   │
   │ logout
   ▼
Server
   │
   └── session 삭제

하지만 Tab 2의 React Query 캐시가 자동으로 변경되는 것은 아니다.

Tab 2

['me']
   ↓
{
  id: 1,
  name: 'user'
}

      ↓

Server에서는 이미 로그아웃됨

즉,

서버의 상태가 변경된 것과 클라이언트가 그 사실을 알게 되는 것은 별개의 문제다.


8. BroadcastChannel을 사용하면 변경 사실을 즉시 전달할 수 있다

이때 BroadcastChannel을 사용할 수 있다.

Tab 1에서 로그아웃한다.

Tab 1
  │
  │ logout API
  ▼
Server
  │
  │ 성공
  ▼
BroadcastChannel
  │
  ├───────────────┐
  ▼               ▼
Tab 2           Tab 3

그리고 다른 탭에서는 로그아웃 이벤트를 받는다.

channel.addEventListener('message', (event) => {
  if (event.data.type === 'LOGOUT') {
    queryClient.clear();
  }
});

이렇게 하면 다른 탭이 다음 API 요청이나 React Query의 refetch를 기다리지 않고 로그아웃 이벤트를 즉시 처리할 수 있다.


9. BroadcastChannel + TanStack Query 조합

실제로는 BroadcastChannel이 데이터를 직접 관리하도록 만들기보다는 변경 사실만 전달하고 TanStack Query가 데이터를 다시 가져오도록 하는 방식이 깔끔하다.

예를 들어 상품 상세 페이지에서 상품을 수정했다고 하자.

Detail
  │
  │ 상품 수정
  ▼
Server
  │
  │ 성공
  ▼
BroadcastChannel
  │
  │ PRODUCT_UPDATED
  ▼
Home
  │
  ▼
invalidateQueries()
  │
  ▼
Server
  │
  ▼
최신 데이터

Home에서는:

channel.addEventListener('message', (event) => {
  if (event.data.type === 'PRODUCT_UPDATED') {
    queryClient.invalidateQueries({
      queryKey: ['product', event.data.productId],
    });
  }
});

이렇게 처리할 수 있다.

여기서 역할이 명확하게 나뉜다.

BroadcastChannel
        │
        │ "상품이 변경됐다."
        ▼
TanStack Query
        │
        │ "그럼 다시 조회한다."
        ▼
Server
        │
        ▼
최신 데이터

BroadcastChannel이 데이터를 가지고 있는 것이 아니라 이벤트를 전달하는 역할만 담당한다.


10. 그렇다면 언제 BroadcastChannel을 사용해야 할까?

모든 상황에서 BroadcastChannel을 사용할 필요는 없다.

React Query만으로 충분한 경우

다음과 같은 상황이라면 굳이 BroadcastChannel을 사용하지 않아도 된다.

서버 데이터 변경
      ↓
다음 API 요청
      ↓
최신 데이터 확인

데이터가 즉시 동기화될 필요가 없다면 이것만으로 충분하다.


BroadcastChannel이 유용한 경우

반대로 여러 탭에 변경 사실을 즉시 알려야 하는 경우에는 유용하다.

Tab 1
  │
  │ 로그아웃
  ▼
BroadcastChannel
  │
  ├──────> Tab 2
  └──────> Tab 3

대표적인 예시는 다음과 같다.

  • 한 탭에서 로그아웃하면 다른 탭도 즉시 로그아웃
  • 한 탭에서 상품을 수정하면 다른 탭에서 즉시 refetch
  • 한 탭에서 결제 완료 후 다른 탭의 장바구니 갱신
  • 한 탭에서 특정 작업을 완료하면 다른 탭에 이벤트 전달

11. 최종적으로 어떻게 구분하면 될까?

BroadcastChannel을 정리하면서 처음에는 Zustand, React Query, WebSocket과 비슷한 역할을 하는 기술이라고 생각할 수 있다.

하지만 각각의 역할은 다르다.

Zustand
  ↓
애플리케이션 상태를 관리한다.


TanStack Query
  ↓
서버 상태를 가져오고 캐싱한다.


BroadcastChannel
  ↓
다른 브라우징 컨텍스트에 이벤트를 전달한다.


WebSocket
  ↓
서버와 실시간으로 통신한다.

따라서 BroadcastChannel을 단독으로 사용하기보다는 기존 상태 관리 도구와 함께 사용하는 경우가 많다.

예를 들어 다음과 같은 구조가 가능하다.

                    Server
                       │
                       │
              ┌────────┴────────┐
              │                 │
        TanStack Query       WebSocket
              │                 │
              │                 │
              ▼                 ▼
            Tab 1            실시간 데이터
              │
              │
       BroadcastChannel
         ↙           ↘
      Tab 2          Tab 3
        │              │
 TanStack Query  TanStack Query

결국 핵심은 상태를 어디에서 관리할 것인가와 변경 사실을 어떻게 전달할 것인가는 별개의 문제라는 것이다.

서버
 ↓
실제 데이터의 Source of Truth

TanStack Query
 ↓
서버 상태 관리 / 캐싱

BroadcastChannel
 ↓
탭 간 변경 이벤트 전달

Zustand
 ↓
클라이언트 전역 상태 관리

따라서 BroadcastChannel을 이해할 때는

"탭 간 상태를 관리하는 기술"

이라고 생각하기보다는

"탭 간에 상태 변경 이벤트를 전달하는 기술"

이라고 생각하는 것이 가장 정확하다.

profile
프론트엔드 개발자 초보에서 고수까지!

0개의 댓글