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();
<aside> 📁 index.html 파일을 읽어들입니다.
</aside>
말그대로 readFileSync를 통해서 index.html을 불러옵니다.
<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>