react app을 express에서 server side rendering으로 제공하려니 에러가 발생했다.
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
에러가 발생한 것이다.
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);