Skip to content

Instantly share code, notes, and snippets.

@velopert
Last active September 3, 2018 13:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save velopert/5aa9778c59702813ac817d5fbb636120 to your computer and use it in GitHub Desktop.
Save velopert/5aa9778c59702813ac817d5fbb636120 to your computer and use it in GitHub Desktop.
Server Side Rendering & Async Route (Code Splitting) for React-Router v4
import React from 'react';
export default function asyncComponent(getComponent) {
class AsyncComponent extends React.Component {
static Component = null;
state = { Component: AsyncComponent.Component };
componentWillMount() {
if (!this.state.Component) {
getComponent().then(({default: Component}) => {
AsyncComponent.Component = Component
this.setState({ Component })
});
}
}
render() {
const { Component } = this.state
if (Component) {
return <Component {...this.props} />
}
return null
}
}
AsyncComponent.getComponent = () => {
return getComponent().then(({default: Component}) => {
AsyncComponent.Component = Component;
});
}
return AsyncComponent;
}
// (index.js)
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './Root';
import registerServiceWorker from './registerServiceWorker';
import routes from './routes';
import { matchPath } from 'react-router';
import 'styles/base.scss';
const render = async () => {
// 코드스플리팅된 라우트들을 먼저 불러온 다음에 렌더링
const getComponents = [];
routes.forEach(
route => {
const match = matchPath(window.location.pathname, route);
if(!match) return;
const { getComponent } = route.component;
if(getComponent) {
getComponents.push(getComponent());
}
}
);
await Promise.all(getComponents);
ReactDOM.hydrate(<Root />, document.getElementById('root'));
};
render();
registerServiceWorker();
import asyncRoute from 'lib/asyncRoute';
export const ListPage = asyncRoute(() => import('./ListPage'));
export const PostPage = asyncRoute(() => import('./PostPage'));
export const EditorPage = asyncRoute(() => import('./EditorPage'));
export const NotFoundPage = asyncRoute(() => import('./NotFoundPage'));
import { ListPage, PostPage, EditorPage } from 'pages';
export default [
{
path: '/',
exact: true,
component: ListPage
},
{
path: '/post/:id',
component: PostPage
},
{
path: '/page/:page',
component: ListPage
},
{
path: '/tag/:tag/:page?',
component: ListPage
},
{
path: '/editor',
component: EditorPage
}
];
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter, matchPath } from 'react-router';
import App from 'components/App';
import { Provider } from 'react-redux';
import configure from 'store/configure';
import transit from 'transit-immutable-js';
import { Helmet } from 'react-helmet';
import axios from 'axios';
import routes from './routes';
import queryString from 'query-string';
function parseQuery(url) {
return queryString.parse(url.substring(url.indexOf('?') + 1));
}
const render = async (url, apiBaseURL) => {
if(apiBaseURL) {
axios.defaults.baseURL = apiBaseURL;
}
const store = configure();
const promises = [];
// 서브 라우트도 있는 경우를 대비하여 forEach 사용
routes.forEach(
route => {
const match = matchPath(url, route);
if(match) {
// redux connect 를 사용한 경우 WrappedComponent 조사
const { component } = route;
const { preload } = component;
if(!preload) return;
const { params } = match;
const promise = preload(store.dispatch, params, parseQuery(url));
promises.push(promise);
}
}
);
try {
await Promise.all(promises);
} catch (e) {
}
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={url}>
<App/>
</StaticRouter>
</Provider>
);
const helmet = Helmet.renderStatic();
return {
helmet,
html,
preloadedState: JSON.stringify(transit.toJSON(store.getState())).replace(/</g, '\\u003c')
};
};
export default render;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment