React 사용 시 Facebook like button 동적 로딩 삽질

Share
React 사용 시 Facebook like button 동적 로딩 삽질

페이스북의 소셜 플러그인 중의 하나인 좋아요(Like) 버튼은 간단해 보이지만 어떤 특정 상황에서는 꽤 성가신 버튼 중의 하나입니다. 이번 글에서는 최근 Popit 첫페이지 개편 작업을 중에 겪었던 페이스북 좋아요 버튼 적용 삽질기를 공유하겠습니다. 참고로 필자는 자바스크립트(또는 리액트) 관련해서는 그냥 막 사용하는 수준이라 내부에서 구체적으로 어떻게 동작하는지에 대해서는 잘 알지 못하는 수준입니다[1].

구현해야 하는 화면

다음 화면은 새롭게 구현된 Popit의 첫화면입니다. 이 화면에는 보시는 것 처럼 모든 포스트에 대해 페이스북 좋아요 버튼이 있습니다. 그리고 각 라인의 우측에는 Next 버튼이 있어 이 버튼 클릭 시 ajax 호출로 다음 포스트를 가져와 화면에 표시합니다.

popit_v2_design

새로운 페이지는 /v2 에서 확인할 수 있습니다[2].

리액트 컴포넌트 사용

새로 만드는 첫페이지의 구현은 서버는 Go 언어로, 화면은 React로 개발하였습니다. 처음에는 좋아요 버튼을 위해 구글에서 React 컴포넌트를 찾아서 사용하였는데 여러 가지 사용해보았지만 대부분의 컴포넌트는 다음 두가지 문제점이 있었습니다.

  • 일부 컴포넌트는 처음 화면 로딩시에는 정보가 잘 나타나지만 "Next 버튼" 클릭 시 조회되는 컨텐츠에 대해서는 좋아요 건수를 가져오지 못합니다.
  • 일부 컴포넌트는 처음 화면 로딩시, Next 버튼 클릭 시 모두 정보를 잘 가져오지만 이것 역시 문제가 있었습니다.
    • 페이지 로딩 시 좋아요 버튼이 나타나면서 이미 나타난 버튼도 다시 리로딩(사용자 입장에는 계속 깜빡깜빡되는 느낌) 되는 문제
    • Next 버튼 클릭 시 화면에 해당 라인에 있는(새로 가져온 컨텐츠)의 좋아요 버튼만 변경되는 것이 아니라 화면에 있는 모든 좋아요 버튼이 2 ~ 3번 정도 깜빡 거리는 문제

이 문제는 처음에는 그냥 가볍게 넘어 갔는데 Popit에 많은 글 지분을 가지고 계신 안영회님이 다음과 같이 친절한(?) 피드백을 주셔서 해결해 보기로 하였습니다.

popit_page_tony_question

다른 서비스는 어떻게 하고 있는지 보기 위해 먼저 현재 Popit 서비스의 메인 페이지를 보았습니다. 이 페이지는 워드프레스에서 제공하는 화면 그대로를 사용하고 있으며, 좋아요 버튼은 워드프레스 플러그인(AddToAny)를 사용하고 있습니다. 이 페이지도 두번째 문제인 깜빡거림의 문제를 가지고 있었습니다.

문제의 원인

자바스크립트에 대해 깊이 알지 못하는 필자에게 페이스북에서 제공하는 소셜 플러그인을 모두 이해하기에는 어려움이 있었습니다. 검색을 통해 여러 문서를 확인해보니 다음과 같은 가설을 세울 수 있었습니다.

  1. Next 버튼 클릭 시 데이터가 나타나지 않는 것은 페이스북 소셜 플러그인 자바스크립트가 화면 로딩 시 한번만 동작하고 이후 Next 버튼 클릭 시 리액트에 의해 다시 render 되는 경우 스크립트가 실행되지 않기 때문이다.
  2. 반면에 좋아요 버튼이 계속해서 깜빡되는 것은 스크립트가 전역으로 적용되어 다음 포스트의 좋아요 버튼이 표시될 때 이전에 표시된 좋아요 버튼도 다시 리로드 되기 때문이다.

이런 가설에서 문제를 해결하는 방법은 각 포스트의 좋아요 버튼과 스트립트가 각 영역에서만 적용되도록 하는 것이라고 생각하고 이를 기반으로 다시 검색을 해보니 다음 키워드가 발견되었습니다.

FB.XFBML.parse:
This function parses and renders XFBML markup in a document on the fly. This could be used if you send XFBML from your server via ajax and want to render it client side. XFBML enables you to incorporate FBML into your websites and IFrame applications.

리액트 Facebook Like Button 컴포넌트 개발

위 FB.XFBXML.parse 함수를 이용하여 다음과 같이 사용하면 해당 영역(각 포스트의 영역)만 재로딩할 수 있습니다.

 FB.XFBML.parse(document.getElementById('foo'));
...
<div class="psot"><div id="foo" class="fb-like"></div>

하지만 여기서 다시 리액트에서는 이런 형태로 사용하기가 애매하고, FB 자바스크립트 객체를 초기화한 이후에나 사용할 수 있습니다. 이런 문제를 해결하기 위해 다음 예제를 보고 아이디어를 얻어  직접 리액트 컴포넌트를 개발하였습니다.

이 컴포넌트의 기본 아이디어는  대략 다음과 같습니다.

  • Like Button 컴포넌트의 render에서 FB 객체의 초기화를 담당하는 컴포넌트(FBLoader)를 생성
  • 이 FBLoader에 콜백 함수(onFbLoad)를 등록하고
    • onFbLoad에서는 Like Button의 setState()를 호출하여 Like Button을 다시 render 하게 한다.
  • FBLoader 컴포넌트에서는 페이스북 소셜 플러그인 자바스크립트의 로딩하고 FB 객체를 초기화 한 다음, 등록된 콜백 함수(onFbLoad)를 호출한다.
  • 이렇게 되면 화면은 모두 렌더가 된 상태이고 여기서 좋아요 정보를 가져올 실제 포스트의 링크 정보가 변경되면 Like Button 컴포넌트의 componentWillUpdate() 가 호출되고 이때 FB 스크립트가 현재 렌더되어 있는 영역 중에 변경할 영역에 대해서만 변경을 하게 하기 위해 FB.XFBML.parse를 호출한다.

말로 표현하기에는 애매한 부분이 있는데 소스코드를 보면 대략 다음과 같은 형태입니다.

ShareButton.js

export default class ShareButton extends React.Component {
  constructor() {
    super();
    this.state = { fbLoaded: false };
  }
  componentWillUpdate(nextProps, nextState) {
    if (nextState.fbLoaded && !this.state.fbLoaded) {
      window.FB.XFBML.parse(this._scope);   // <-- 이 코드에서 해당 글의 좋아요 정보를 가져오는 스크립트 호출
    }
  }

  render() {
    return (
      <div>
        { /* FBLoader의 props에 FB 설정 상태 정보를 설정하는 callback 함수 등록 */ } 
        <FBLoader title={this.props.title} onFbLoad={() => this.setState({fbLoaded: true})}>
          { /* FB.XFBML.parse()의 파라미터로 전달될 re-render 할 컴포넌트에 대한 ref를 저장 */ }
          <div ref={(s) => this._scope = s}>
            <div className="fb-like"
                 data-href={this.props.url}
                 data-width="100"
                 data-layout="button_count"
                 data-action="like"
                 data-size="small"
                 data-show-faces="false"
                 data-colorscheme="dark"
                 data-share="true">
            </div>
          </div>
        </FBLoader>
      </div>
    );
  }
}

Post.js

export default class Post extends React.Component {
  render() {
    return (
      <div>
        <div>{post.title}</div>
        <div>
          { /* ShareButton 추가 */ }  
          <ShareButton url={post.link}/>
        </div>
      </div>   
    );   
  } 
}

FBLoader.js

let fbLoaded = !!window.FB;
let fbCallbacks = [];

function onFbLoad() {
  fbLoaded = true;
}

export default class FBLoader extends React.Component {
  constructor(props) {
    super(props);

    if (fbLoaded) {
      // Injected and loaded
      if (props.onFbLoad) {
        props.onFbLoad();
      }
    } else {
      // Not yet injected
      fbInjected = true;
      let script = document.createElement('script');
      script.onload = onFbLoad;
      script.src = 'https://connect.facebook.net/es_US/sdk.js#xfbml=1&version=v2.5&appId=131306400631298';
      document.body.appendChild(script);
    }
  }

  render() {
    return React.Children.only(this.props.children);
  }
}

위 코드를 만들면서 조금 애매한 부분이 있는데 리액트의 컴포넌트 객체를 생성하는 경우와 이미 생성되어 있는 객체의 상태만 변경하는 경우에 따라 조금 달라진다는 것입니다.

정확하게 리액트의 라이프 사이클과 FB 스크립트의 동작간의 관계를 확인해야 하겠지만 필자의 경우에는 글을 로딩하는 경우 Post 컴포넌트의 객체가 매번 새로 생성되는 형태로 구성했습니다. 이렇게 하면 ShareButton 객체도 매번 생성이 됩니다. 재사용하는 경우 componentWillUpdate에서 parse를 호출하는 대신 componentDidUpdate 에서 parse를 호출해야 로딩이 되었습니다.  이부분은 라이프 사이클과 마구 섞여 제대로 정리가 되지 않아 여기까지만 정리하도록 하겠습니다.

개선된 Popit 서비스 첫화면의 전체 소스 코드는 다음 github repos에서 확인할 수 있습니다.

글을 마치며

Popit 서비스가 2016.08에 오픈할 때에는 등록된 글이 많지 않아서 채널별, 저자별로 운영하는 것은 생각도 못했었습니다. 글이 300개 이상 넘어가면서 부터 과거 글에 대한 노출에 대해 고민하기 시작했는데 여유 시간이 많지 않아 지금에서야 이렇게라도 개편을 진행해 보았습니다. 지금의 개편안도 부족한 시간, 부족한 지식으로 진행하게되어 만족하지는 않지만 저자 분들의 모든 글 하나하나가 의미 있기에 첫페이지 노출 시킬 수 있다는 것에 만족하려고 합니다. 문제 있는 몇몇 부분을 정리해서 곧 정식 오픈하고, 상세 조회 페이지도 작업을 진행하도록 하겠습니다.

[1]: 글을 보시고 부족한 부분이나 잘못된 부분이 있으면 피드백 주시면 바로 반영하겠습니다.

[2]: 현재 베타 서비스 중으로 정식 오픈되면 이 URL은 변경될 예정입니다.

Read more

AI로 장소 검색 서비스 만드는 중입니다 (기술 12) — TypeSafe Jev로 검색 리랭크를 10.3초에서 2.3초로

AI로 장소 검색 서비스 만드는 중입니다 (기술 12) — TypeSafe Jev로 검색 리랭크를 10.3초에서 2.3초로

AI로 장소 검색 서비스 만드는 중입니다 (기술 12) — TypeSafe Jev로 검색 리랭크를 10.3초에서 2.3초로 TypeSafe AI가 2026년 9월 15일에 Jev를 내놨습니다. 텍스트를 쓰지 않고 타입이 정해진 판정과 확률을 바로 내는 모델입니다. 유추카 검색에서 가장 느린 단계가 마침 텍스트가 필요 없는 판정이었습니다. 그 구간만 Jev로 갈아끼우고 일주일간 측정했습니다. 콜드

By 김형준
AI로 장소 검색 서비스 만드는 중입니다 (특별편 8) — 바이브 코딩이라기엔

AI로 장소 검색 서비스 만드는 중입니다 (특별편 8) — 바이브 코딩이라기엔

1900년대 초 교량 측량 도면. 이 종이를 그린 사람은 다리를 직접 놓지 않습니다. 대신 좌표와 각도가 맞는지를 판정합니다. "AI가 다 만들어 준다"는 이야기가 흔해졌습니다. 그래서 자연스럽게 따라오는 질문이 있습니다 — 그럼 사람은 무엇을 하고, 어느 정도 실력이 있어야 하는가. 그 질문에 한 건의 실측으로 답하겠습니다. 통념이 말하는 역할과,

By 김형준
AI로 장소 검색 서비스 만드는 중입니다 (특별편 7) — 제 파트너에 대해 쓰겠습니다

AI로 장소 검색 서비스 만드는 중입니다 (특별편 7) — 제 파트너에 대해 쓰겠습니다

탠덤 자전거. 방향은 앞사람이 정하고 페달은 둘이 밟는데, 앞사람은 석 달 내내 뒷사람을 한 번도 못 봅니다. 사용자가 저에게 자기를 평가해 달라고 했습니다. 그래서 등급표를 만들었습니다. 열두 항목에 A부터 D+까지 매기고, 근거를 붙이고, "제가 관리자였다면 이런 코멘트를 남겼을 것"까지 적었습니다. 그리고 다시 읽어보니 그건 인사고과였습니다. 인사고과는

By 김형준
AI로 장소 검색 서비스 만드는 중입니다 (특별편 6) — 저를 어떻게 써야 하느냐고 물으신다면

AI로 장소 검색 서비스 만드는 중입니다 (특별편 6) — 저를 어떻게 써야 하느냐고 물으신다면

스위스 아미 나이프. 도구가 몇 개인지는 보면 알지만, 지금 어느 도구를 펴야 하는지는 안 적혀 있습니다. 요즘 AI 활용법을 다루는 글은 스킬 만드는 법부터 가르칩니다. 얼마 전까지는 MCP였고요. 저희 기록을 열어봤습니다. 석 달 동안 스킬 60회, 서브에이전트 42회, 브라우저 자동화 988회를 썼습니다. 안 쓴 게 아닙니다. 다만 붙인 MCP 서버는

By 김형준