[React Router] Server Rendering시 라우터 처리

서해빈·2021년 4월 26일
1

Trouble

목록 보기
8/15
post-custom-banner

발단: server side rendering시 에러 발생

react app을 express에서 server side rendering으로 제공하려니 에러가 발생했다.

Invariant failed: Browser history needs a DOM

원인 분석: window 인터페이스의 부재

react-router-dom의 BrowserRouter는 라우터 element 생성하는 과정에서 브라우저 history 객체를 생성한다.

// react-router-dom에서 인용
var BrowserRouter =
/*#__PURE__*/
function (_React$Component) {
  ...
  function BrowserRouter() {
    ...
    _this.history = history.createBrowserHistory(_this.props);  // 문제가 되는 부분
    return _this;
  }
  
  var _proto = BrowserRouter.prototype;

  _proto.render = function render() {
    return React.createElement(reactRouter.Router, {
      history: this.history,  // window.history를 바탕으로 한 history를 사용한다.
      children: this.props.children
    });
  };
  
  return BrowserRouter;
}(React.Component);

이 때, history의 createBrowserHistory() 함수는 HTML DOM과 브라우저의 history 객체를 필요로 하기 때문에 함수 초입 부분에서 canUseDOM boolean 변수로 이를 확인한다.

// history에서 인용
var canUseDOM = !!(typeof window !== 'undefined' && \
                   window.document && window.document.createElement);
...
...
function createBrowserHistory(props) {
  ...
  !canUseDOM ? invariant(false, 'Browser history needs a DOM') : void 0;
  var globalHistory = window.history;
  ...
}

하지만 server side rendering에서는 web api의 window 인터페이스가 존재하지 않으므로 Browser history needs a DOM 에러가 발생한 것이다.

해결: StaticRouter 사용

React Router 가이드 문서에 따르면 Server rendering을 할 때는 <BrowserRouter> 대신 <StaticRouter>으로 react app을 감싸야 한다.

Server Rendering

Rendering on the server is a bit different since it’s all stateless. The basic idea is that we wrap the app in a stateless <StaticRouter\> instead of a <BrowserRouter\>.

또한 react-router의 StaticRouter는 window의 history 객체를 사용하지 않고 전달받은 location prop을 바탕으로 자체적으로 history를 생성하는 것을 확인했다.

var StaticRouter =
/*#__PURE__*/
function (_React$Component) {
  ...
  _proto.render = function render() {
    var _this$props2 = this.props,
        _this$props2$basename = _this$props2.basename,
        basename = _this$props2$basename === void 0 ? "" : _this$props2$basename,
        _this$props2$context = _this$props2.context,
        context = _this$props2$context === void 0 ? {} : _this$props2$context,
        _this$props2$location = _this$props2.location,
        location = _this$props2$location === void 0 ? "/" : _this$props2$location,
        rest = _objectWithoutPropertiesLoose(_this$props2, [
          "basename", "context", "location"
        ]);

    var history$1 = {  // window.history 객체를 사용하지 않고 history 생성
      createHref: function createHref(path) {
        return addLeadingSlash(basename + createURL(path));
      },
      action: "POP",
      location: stripBasename(basename, history.createLocation(location)),
      push: this.handlePush,
      replace: this.handleReplace,
      go: staticHandler("go"),
      goBack: staticHandler("goBack"),
      goForward: staticHandler("goForward"),
      listen: this.handleListen,
      block: this.handleBlock
    };
    return React.createElement(Router, _extends({}, rest, {
      history: history$1,
      staticContext: context
    }));
  };

  return StaticRouter;
}(React.Component);

참고 및 출처

post-custom-banner

0개의 댓글