서버 사이드 렌더링과 코드 스플리팅 충돌 해결하기

이렇게 코드 스플리팅을 구현하고 나면 서버사이드 렌더링을 하게 될 때 깜박임 이슈가 발생하게 됩니다. 한번 애플리케이션을 다시 빌드하고 서버사이드 렌더링 서버를 다시 구동해보세요.

$ yarn build
$ yarn build:server
$ yarn start:server

그리고, http://localhost:5000/red 페이지에 들어간다음에 개발자도구의 Network 탭을 열어서 우측 상단의 Online 을 누르고 Slow 3G 를 선택하세요. 이렇게 하면 느린 네트워크 상황을 시뮬레이트 할 수 있습니다.

04

그리고 새로 고침을 하고나서 어떤 상황이 발생하는지 지켜보세요.

screencapture

이렇게 깜박임이 발생하게되면 유저에게 나쁜 경험을 제공하게 됩니다. 이 현상을 고치려면 loadable-components 에서 제공하는 부가기능들을 활용해야합니다.

가장 먼저 설정해야 하는 것은 babel 플러그인과 webpack 플러그인을 적용하는 것 입니다.

package.json 을 열어서 스크롤을 아래로 내려서 babel 을 찾은 뒤 그 안에 plugins 를 입력하세요.

package.json - babel

  "babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      "@loadable/babel-plugin"
    ]
  }

그 다음엔 webpack.config.js 를 열어서 상단에 LoadablePlugin 을 불러와주고 하단에서는 plugins 를 찾아서 이 플러그인을 적용해주세요. (주의: webpack.config.server.js 가. 아닙니다.)

webpack.config.js

const fs = require('fs');
(...)
const LoadablePlugin = require('@loadable/webpack-plugin');

(...)
    plugins: [
      new LoadablePlugin(),
      // Generates an `index.html` file with the <script> injected.
      new HtmlWebpackPlugin(
(...)

수정 후에는 yarn bulid 명령어를 한번 더 실행해보세요. build 디렉터리에 loadable-stats.json 라는 파일이 잘 만들어졌나요?

loadable-stats.json

{
  "errors": [],
  "warnings": [],
  "version": "4.28.3",
  "hash": "ce7635178619a87fe263",
  "publicPath": "/",
  "outputPath": "/Users/velopert/workspace/fc7/ssr-and-splitting/build",
  "assetsByChunkName": {
    "main": [
      "static/css/main.2930fd5d.chunk.css",
      "static/js/main.e46bc05d.chunk.js",
      "static/js/main.e46bc05d.chunk.js.map"
    ],
    "pages-BluePage": [
      "static/css/pages-BluePage.6c98164d.chunk.css",
      "static/js/pages-BluePage.8ea832a2.chunk.js",
      "static/js/pages-BluePage.8ea832a2.chunk.js.map"
    ],
    (...)

이 파일은 위와 같은 구조로 이루어져있습니다. 우리가 서버사이드렌더링을 할 때는 이 파일을 참고하여 렌더링 할 때 어떤 스크립트 및 스타일 파일들을 미리 불러와야 하는지 알 수 있게 됩니다.

이제 서버 엔트리에서 ChunkExtractor 와 ChunkExtractorManager 를 사용하여 렌더링을 하는 과정에서 어떤 파일을 사용하는지 추출해봅시다. 기존에 fs 를 사용하여 asset-manifest.json 를 참조하던 코드는 이제 불필요해지기때문에 지워주셔도 됩니다.

index.server.js

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import express from 'express';
import path from 'path';
import { StaticRouter } from 'react-router';
import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server';
import App from './App';

const statsFile = path.resolve('./build/loadable-stats.json');

const app = express();

// 서버사이드 렌더링된 결과에 따라 html 을 조합해서 보여줍니다.
function createPage(root, tags) {
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <link rel="shortcut icon" href="/favicon.ico" />
  <meta
    name="viewport"
    content="width=device-width,initial-scale=1,shrink-to-fit=no"
  />
  <meta name="theme-color" content="#000000" />
  <title>React App</title>
  ${tags.styles}
  ${tags.links}
</head>
<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root">
    ${root}
    ${tags.scripts}
  </div>
</body>
</html>
  `;
}

// 서버사이드 렌더링을 처리 할 핸들러 함수입니다.
const serverRender = (req, res, next) => {
  // 이 함수는 404가 떠야 하는 상황에 404를 띄우지 않고 서버사이드 렌더링을 해줍니다.
  if (req.route) return next();

  // 필요한 파일 추출하기 위한 사전 작업
  const extractor = new ChunkExtractor({ statsFile });
  const context = {};
  const jsx = (
    <ChunkExtractorManager extractor={extractor}>
      <StaticRouter location={req.url} context={context}>
        <App />
      </StaticRouter>
    </ChunkExtractorManager>
  );
  const root = ReactDOMServer.renderToString(jsx); // 렌더링을 하고
  const tags = {
    // 미리 불러와야 하는 스타일 / 스크립트를 추출하고
    scripts: extractor.getScriptTags(),
    links: extractor.getLinkTags(),
    styles: extractor.getStyleTags()
  };
  res.send(createPage(root, tags)); // 결과물을 응답합니다.
};

app.use(
  express.static(path.resolve('./build'), {
    index: false // "/" 경로에서 index.html 을 보여주지 않고 서버사이드 렌더링을 합니다.
  })
);
app.use(serverRender);

// 5000 포트로 서버를 가동합니다.
app.listen(5000, () => {
  console.log('Running on http://localhost:5000');
});

Loadable Component 를 사용하게 되면 쾌적한 성능을 위하여 모든 자바스크립트 파일을 동시에 받아오게 됩니다. 모든 스크립트가 로딩되고 나서 렌더링을 하기 위해서는 loadableReady 라는 함수를 사용해주어야 합니다. 추가적으로, 리액트에는 render 함수 대신에 사용 할 수 있는 hydrate 라는 함수가 있는데요, 이 함수는 기존에 서버사이드 렌더링된 결과물이 이미 있다면 렌더링을 새로 하지 않고 기존에 존재하는 UI 에 이벤트만 연동을 하여 애플리케이션 초기 구동 성능을 최적화해줍니다. 이를 적용하기 위하여 index.js 를 다음과 같이 수정해주세요.

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { loadableReady } from '@loadable/component';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

const Root = () => (
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

const root = document.getElementById('root');

// 프로덕션 환경 에서는 loadableReady 와 hydrate 를 사용하고
// 개발 환경에서는 기존 하던 방식으로 처리
if (process.env.NODE_ENV === 'production') {
  loadableReady(() => {
    ReactDOM.hydrate(<Root />, root);
  });
} else {
  ReactDOM.render(<Root />, root);
}

serviceWorker.unregister();

이제 서버사이드 렌더링과 코드 스플리팅 충돌현상을 모두 고쳐주었습니다. 애플리케이션을 다시 빌드하고 서버사이드 렌더링 서버를 실행해보세요.

$ yarn build
$ yarn build:server
$ yarn start:server

아까처럼 Network 탭에서 속도를 Slow 3G 로 맞춰놓고 페이지를 새로고침 해보세요. 깜박임 이슈가 잘 해결 됐나요?

results matching ""

    No results matching ""