개요

Vite의 공식문서를 찾아보면 config를 통해서 SSR 옵션을 설정해줄 수 있습니다. [참고]

그리고 개발자가 직접 Node 서버에서 middleware로 커스텀해서 사용할 수도 있습니다.[참고]

이 글에서는 어떻게 동작을 하는지 살펴보도록 하겠습니다.

동작 설명

기본 예제

공식문서 코드

공식 문서에서 설명하는 코드는 아래와 같습니다. (주석은 공식문서에 작성된 내용입니다)

<!DOCTYPE html>
<html>
<head>
    ...
</head>
<body>
	<!--ssr-outlet-->
	<script src="/index.tsx" type="module"></script>
</body>
</html>
import express from 'express';
import {createServer} from 'vite';
import axios from 'axios';
import fs from 'fs';

async function createDevServer() {
  const port = process.env.PORT || 5174;

  // Create http server
  const app = express();

  const vite = await createServer({
    server: { middlewareMode: true },
    appType: 'custom',
  });

  app.use('*', (req, res, next) => {
    vite.middlewares.handle(req, res, next);
  });

  app.get('*', async (req, res) => {
    try {
			// [1] *index.html 파일을 읽어들입니다.*
      let template = fs.readFileSync('./index.html', 'utf-8');

			// [2] Vite의 HTML 변환 작업을 통해 Vite HMR 클라이언트를 주입하고,
			// Vite 플러그인의 HTML 변환도 적용합니다.
			// (예시: @vitejs/plugin-react의 Global Preambles)
			template = await vite.transformIndexHtml(url, template)

			// [3] 서버의 진입점(Entry)을 로드합니다.
			// ssrLoadModule은 Node.js에서 사용할 수 있도록 ESM 소스 코드를 자동으로 변환합니다.
			// 추가적인 번들링이 필요하지 않으며, HMR과 유사한 동작을 수행합니다.
			const { render } = await vite.ssrLoadModule('/src/entry-server.js')

			// [4] 앱의 HTML을 렌더링합니다.
	    // 이는 entry-server.js에서 내보낸(Export) `render` 함수가
	    // ReactDOMServer.renderToString()과 같은 적절한 프레임워크의 SSR API를 호출한다고 가정합니다.
      const appHtml = await render(url)

			// [5] 렌더링된 HTML을 템플릿에 주입합니다.
      const html = template.replace(`<!--ssr-outlet-->`, appHtml)

      res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
    } catch (e) {
      vite?.ssrFixStacktrace(e);
			next(e)    }
  });

  // Start http server
  app.listen(port, () => {
    console.log(`Server started at <http://localhost>:${port}`);
  });
}

createDevServer();

[1] index.html 불러오기

<aside> 📁 index.html 파일을 읽어들입니다.

</aside>

말그대로 readFileSync를 통해서 index.html을 불러옵니다.

[2] transformIndexHtml

<aside> 📁 Vite의 HTML 변환 작업을 통해 Vite HMR 클라이언트를 주입하고,

Vite 플러그인의 HTML 변환도 적용합니다.

(예시: @vitejs/plugin-react의 Global Preambles)

</aside>

크로스 브라우징에서 렌더링하는데 필요한 각종 폴리필들과 동적 모듈들을 위한 import들을 처리해줍니다.

그리고 HMR을 위한 코드와 index.tsx로 로컬 경로로 되어있는 코드를 vite dev server와 매핑된 script로 변경해줍니다.

공식문서에서 제시하는 기존 백엔드 프레임워크에 해당 방식을 적용시키기 작업을 해당 함수에서 해준다고 보시면됩니다.

<!DOCTYPE html>
<html>
<head>
    <script type="module">
        import RefreshRuntime from '<http://localhost:5173/@react-refresh>'

        RefreshRuntime.injectIntoGlobalHook(window)
        window.$RefreshReg$ = () => {
        }
        window.$RefreshSig$ = () => (type) => type
        window.__vite_plugin_react_preamble_installed__ = true
    </script>
</head>
<body>
<!--ssr-outlet-->
<script src="<http://localhost:5173/@vite/client>" type="module"></script>
<script src="<http://localhost:5173/index.tsx>" type="module"></script>
<!--<script src="/index.tsx" type="module"></script>-->
</body>
</html>