고급 Node.js – 서버 사이드 렌더링(SSR) 이해하기

고급 Node.js – 서버 사이드 렌더링(SSR) 이해하기


서버 사이드 렌더링(Server-Side Rendering, SSR)은 웹 애플리케이션의 초기 페이지 로딩 시간을 줄이고, SEO(검색 엔진 최적화)를 향상시키기 위한 중요한 기법입니다. Node.js는 강력한 서버 사이드 JavaScript 환경으로, SSR을 구현하는 데 이상적인 플랫폼입니다. 이번 포스팅에서는 "Node.js SSR", "서버 사이드 렌더링", "고급 Node.js"를 중심으로 SSR의 개념과 구현 방법을 설명하겠습니다. 고급 개발자를 대상으로 상세히 설명하고, 예제를 통해 실습해 보겠습니다.


서버 사이드 렌더링(SSR)이란?


SSR은 서버에서 HTML을 렌더링하여 클라이언트에게 전달하는 방식입니다. 클라이언트는 서버에서 전달받은 HTML을 즉시 표시할 수 있으므로 초기 로딩 시간이 단축되고, 검색 엔진이 콘텐츠를 더 잘 인식할 수 있습니다. 이는 클라이언트 사이드 렌더링(CSR)과 대비됩니다. CSR에서는 브라우저가 JavaScript를 다운로드하고 실행하여 콘텐츠를 렌더링합니다.


SSR의 장점


  1. 빠른 초기 로딩: HTML이 서버에서 렌더링되므로 초기 로딩 시간이 단축됩니다.
  2. SEO 향상: 검색 엔진이 HTML 콘텐츠를 쉽게 크롤링할 수 있습니다.
  3. 소셜 미디어 공유 최적화: 미리 렌더링된 HTML은 소셜 미디어에서 링크 미리보기를 더 잘 제공합니다.

SSR의 단점


  1. 서버 부하 증가: 모든 요청에 대해 서버가 HTML을 렌더링해야 하므로 서버 부하가 증가합니다.
  2. 복잡성 증가: CSR과 비교하여 구현과 유지보수가 더 복잡할 수 있습니다.

Node.js로 SSR 구현하기


Node.js를 사용하여 SSR을 구현하는 방법을 살펴보겠습니다. Express와 React를 사용하여 간단한 SSR 애플리케이션을 만들어보겠습니다.


프로젝트 설정


먼저, 새로운 Node.js 프로젝트를 생성하고 필요한 패키지를 설치합니다.


mkdir node-ssr-example
cd node-ssr-example
npm init -y
npm install express react react-dom
npm install @babel/core @babel/preset-env @babel/preset-react babel-loader

Babel 설정


Babel을 사용하여 최신 JavaScript와 JSX를 컴파일합니다. 프로젝트 루트에 .babelrc 파일을 생성하고 다음 내용을 추가합니다.


{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

React 컴포넌트 작성


간단한 React 컴포넌트를 작성합니다. src 디렉토리를 만들고, App.js 파일을 생성합니다.


// src/App.js
import React from 'react';

const App = () => {
  return (
    <div>
      <h1>Hello, Server-Side Rendering!</h1>
    </div>
  );
};

export default App;

Express 서버 설정


Express 서버를 설정하고, React 컴포넌트를 서버 사이드에서 렌더링합니다. server.js 파일을 생성합니다.


// server.js
const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const App = require('./src/App').default;

const app = express();
const port = 3000;

app.use(express.static('public'));

app.get('*', (req, res) => {
  const appString = ReactDOMServer.renderToString(React.createElement(App));

  const html = `
    <!DOCTYPE html>
    <html>
      <head>
        <title>Node.js SSR Example</title>
      </head>
      <body>
        <div id="root">${appString}</div>
        <script src="/bundle.js"></script>
      </body>
    </html>
  `;

  res.send(html);
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

클라이언트 번들 생성


Webpack을 사용하여 클라이언트 번들을 생성합니다. 프로젝트 루트에 webpack.config.js 파일을 생성합니다.


// webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx']
  }
};

클라이언트 진입점 설정


React 애플리케이션의 클라이언트 진입점을 설정합니다. src 디렉토리에 index.js 파일을 생성합니다.


// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.hydrate(<App />, document.getElementById('root'));

빌드 및 실행


Webpack을 사용하여 클라이언트 번들을 생성하고, Express 서버를 실행합니다.


npx webpack
node server.js

브라우저에서 http://localhost:3000을 열면 서버 사이드에서 렌더링된 React 애플리케이션을 볼 수 있습니다.


고급 SSR 기법


데이터 프리페칭(Data Prefetching)


서버 사이드 렌더링 시 초기 데이터를 프리페칭하여 클라이언트가 초기 로딩 시 필요한 데이터를 이미 가질 수 있도록 합니다.


// server.js
const fetchData = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Server-Side Data');
    }, 1000);
  });
};

app.get('*', async (req, res) => {
  const data = await fetchData();
  const appString = ReactDOMServer.renderToString(
    React.createElement(App, { data })
  );

  const html = `
    <!DOCTYPE html>
    <html>
      <head>
        <title>Node.js SSR Example</title>
      </head>
      <body>
        <div id="root">${appString}</div>
        <script>window.__INITIAL_DATA__ = ${JSON.stringify(data)}</script>
        <script src="/bundle.js"></script>
      </body>
    </html>
  `;

  res.send(html);
});

// src/App.js
import React from 'react';

const App = ({ data }) => {
  return (
    <div>
      <h1>Hello, Server-Side Rendering!</h1>
      <p>Data: {data}</p>
    </div>
  );
};

export default App;

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

const data = window.__INITIAL_DATA__;
ReactDOM.hydrate(<App data={data} />, document.getElementById('root'));

캐싱(Caching)


SSR은 서버 부하를 증가시킬 수 있으므로, 캐싱을 통해 성능을 최적화할 수 있습니다. 예를 들어, Redis를 사용하여 렌더링된 HTML을 캐싱할 수 있습니다.


// server.js
const redis = require('redis');
const client = redis.createClient();

app.get('*', async (req, res) => {
  const cacheKey = req.url;
  client.get(cacheKey, async (err, cachedHtml) => {
    if (cachedHtml) {
      return res.send(cachedHtml);
    }

    const data = await fetchData();
    const appString = ReactDOMServer.renderToString(
      React.createElement(App, { data })
    );

    const html = `
      <!DOCTYPE html>
      <html>
        <head>
          <title>Node.js SSR Example</title>
        </head>
        <body>
          <div id="root">${appString}</div>
          <script>window.__INITIAL_DATA__ = ${JSON.stringify(data)}</script>
          <script src="/bundle.js"></script>
        </body>
      </html>
    `;

    client.setex(cacheKey, 3600, html); // 1시간 동안 캐싱
    res.send(html);
  });
});

코드 스플리팅(Code Splitting)과 동적 임포트


코드 스플리팅을 통해 초기 로딩 시간을 단축하고, 동적 임포트를 통해 필요한 시점에 필요한 코드만 로드할 수 있습니다.


// src/App.js
import React, { Suspense, lazy } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));

const App = ({ data }) => {
  return (
    <div>
      <h1>Hello, Server-Side Rendering!</h1>
      <p>Data: {data}</p>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
};

export default App;

// src/LazyComponent.js
import React from 'react';

const LazyComponent = () => {
  return <div>This is a lazily loaded component.</div>;
};

export default LazyComponent;

결론


서버 사이드 렌더링(SSR)은 초기 로딩 시간을 단축하고 SEO를 향상시키는 중요한 기술입니다. Node.js를 사용하여 SSR을 구현하면 React와 같은 프론트엔드 라이브러리를 서버 사이드에서 효율적으로 렌더링할 수 있습니다. 이번 포스팅에서는 Express와 React를 사용하여 SSR을 구현하는 기본적인 방법과, 데이터 프리페칭, 캐싱, 코드 스플리팅과 같은 고급 기법을 살펴보았습니다. 이러한 기술을 잘 활용하면 더 빠르고 SEO 친화적인 웹 애플리케이션을 개발할 수 있습니다.

이 포스팅이 Node.js를 사용한 서버 사이드 렌더링을 이해하고 구현하는 데 도움이 되길 바랍니다. 질문이나 추가 정보가 필요하시면 언제든지 댓글로 남겨주세요.

다음 이전