|
| 1 | +import fs from "node:fs/promises"; |
1 | 2 | import express from "express"; |
2 | | -import { renderToString } from "react-dom/server"; |
3 | | -import { createElement } from "react"; |
4 | 3 |
|
5 | 4 | const prod = process.env.NODE_ENV === "production"; |
6 | 5 | const port = process.env.PORT || 5174; |
7 | 6 | const base = process.env.BASE || (prod ? "/front_6th_chapter4-1/react/" : "/"); |
8 | 7 |
|
| 8 | +const templateHtml = prod ? await fs.readFile("./dist/react/index.html", "utf-8") : ""; |
| 9 | + |
9 | 10 | const app = express(); |
10 | 11 |
|
11 | | -app.get("*all", (req, res) => { |
12 | | - res.send( |
13 | | - ` |
14 | | -<!DOCTYPE html> |
15 | | -<html lang="en"> |
16 | | -<head> |
17 | | - <meta charset="UTF-8" /> |
18 | | - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
19 | | - <title>React SSR</title> |
20 | | -</head> |
21 | | -<body> |
22 | | -<div id="app">${renderToString(createElement("div", null, "안녕하세요"))}</div> |
23 | | -</body> |
24 | | -</html> |
25 | | - `.trim(), |
26 | | - ); |
| 12 | +/** @type {import('vite').ViteDevServer | undefined} */ |
| 13 | +let vite; |
| 14 | +if (!prod) { |
| 15 | + const { createServer } = await import("vite"); |
| 16 | + vite = await createServer({ |
| 17 | + server: { middlewareMode: true }, |
| 18 | + appType: "custom", |
| 19 | + base, |
| 20 | + }); |
| 21 | + app.use(vite.middlewares); |
| 22 | +} else { |
| 23 | + const compression = (await import("compression")).default; |
| 24 | + const sirv = (await import("sirv")).default; |
| 25 | + app.use(compression()); |
| 26 | + app.use(base, sirv("./dist/react", { extensions: [] })); |
| 27 | +} |
| 28 | + |
| 29 | +app.use("*all", async (req, res) => { |
| 30 | + try { |
| 31 | + const url = req.originalUrl.replace(base, ""); |
| 32 | + |
| 33 | + /** @type {string} */ |
| 34 | + let template; |
| 35 | + /** @type {import('./src/main-server.tsx').render} */ |
| 36 | + let render; |
| 37 | + |
| 38 | + if (!prod) { |
| 39 | + template = await fs.readFile("./index.html", "utf-8"); |
| 40 | + template = await vite.transformIndexHtml(url, template); |
| 41 | + render = (await vite.ssrLoadModule("/src/main-server.tsx")).render; |
| 42 | + } else { |
| 43 | + template = templateHtml; |
| 44 | + render = (await import("./dist/react-ssr/main-server.js")).render; |
| 45 | + } |
| 46 | + |
| 47 | + // 핵심: SSR 렌더 호출 후 템플릿 치환 |
| 48 | + const { html, head, initialData } = await render(url, req.query); |
| 49 | + const initialDataScript = initialData ? `<script>window.__INITIAL_DATA__=${initialData}</script>` : ""; |
| 50 | + |
| 51 | + const finalHtml = template |
| 52 | + .replace("<!--app-html-->", html ?? "") |
| 53 | + .replace("<!--app-head-->", `${initialDataScript}${head ?? ""}`); |
| 54 | + |
| 55 | + res.status(200).set({ "Content-Type": "text/html" }).send(finalHtml); |
| 56 | + } catch (e) { |
| 57 | + vite?.ssrFixStacktrace?.(e); |
| 58 | + console.error(e.stack || e); |
| 59 | + res.status(500).end(e.stack || String(e)); |
| 60 | + } |
27 | 61 | }); |
28 | 62 |
|
29 | | -// Start http server |
| 63 | +// 서버 시작 |
30 | 64 | app.listen(port, () => { |
31 | 65 | console.log(`React Server started at http://localhost:${port}`); |
32 | 66 | }); |
0 commit comments