Skip to content
Grok

Source Structure

โครงสร้างไฟล์พื้นฐานที่จำเป็นสำหรับการทำ Server-Side Rendering (SSR) ใน Vite ซึ่งประกอบด้วยไฟล์หลักๆ สำหรับทั้งฝั่งเซิร์ฟเวอร์และไคลเอนต์

โครงสร้างไฟล์สำหรับ SSR ใน Vite จะมีลักษณะดังนี้:

bash
- index.html          # ไฟล์เทมเพลตหลัก
- server.js           # เซิร์ฟเวอร์หลัก
- src/
  ├── main.js         # โค้ดแอปที่ใช้ร่วมกัน (universal)
  ├── entry-client.js # จัดการ hydration บนไคลเอนต์
  └── entry-server.js # จัดการ rendering บนเซิร์ฟเวอร์

ตัวอย่าง index.html

html
<!-- ไฟล์เทมเพลตหลัก -->
<div id="app"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.js"></script>
  • <!--ssr-outlet--> เป็น placeholder สำหรับ HTML ที่ render จากเซิร์ฟเวอร์
  • สามารถใช้ placeholder อื่นแทนได้ตามต้องการ

ตัวอย่าง entry-server.js

ts
// ส่งออกฟังก์ชัน render สำหรับเซิร์ฟเวอร์
export async function render(url) {
  // โค้ดเฉพาะ SSR จะอยู่ที่นี่
  const html = await renderToString(app);
  return html;
}

ตัวอย่างการเพิ่มโค้ด SSR

ts
// ตัวอย่างการเพิ่มโค้ด SSR ใน entry-server.js
import { createSSRApp } from "vue";

export async function render(url) {
  const app = createSSRApp(App);
  const html = await renderToString(app);
  return html;
}

Conditional Logic

เทคนิคการเขียนโค้ดที่สามารถทำงานต่างกันระหว่างเซิร์ฟเวอร์และไคลเอนต์ โดยใช้ฟีเจอร์ของ Vite ในการตรวจสอบสภาพแวดล้อม

เมื่อต้องการเขียนโค้ดที่ทำงานต่างกันระหว่างเซิร์ฟเวอร์และไคลเอนต์:

ts
// ใช้ import.meta.env.SSR เพื่อตรวจสอบว่าโค้ดทำงานบนเซิร์ฟเวอร์หรือไม่
// ตัวแปรนี้จะถูกแทนที่ตอน build ทำให้สามารถตัดโค้ดที่ไม่ใช้ออกได้ (tree-shaking)
if (import.meta.env.SSR) {
  // โค้ดนี้จะทำงานเฉพาะบนเซิร์ฟเวอร์
  console.log("Running on server");
} else {
  // โค้ดนี้จะทำงานเฉพาะบนไคลเอนต์
  console.log("Running on client");
}

Setting Up the Dev Server

ขั้นตอนการตั้งค่าเซิร์ฟเวอร์พัฒนาแบบ SSR ด้วย Express และ Vite middleware สำหรับการพัฒนาแอปพลิเคชัน

ts
// การตั้งค่า Express server พร้อม Vite middleware สำหรับโหมดพัฒนา
// ใช้สำหรับพัฒนาแอปพลิเคชันแบบ SSR
import express from "express";
import { createServer as createViteServer } from "vite";

async function createServer() {
  const app = express();

  // สร้าง Vite server ในโหมด middleware
  // - middlewareMode: true ทำให้ Vite ทำงานเป็น middleware แทนที่จะเป็นเซิร์ฟเวอร์เต็มรูปแบบ
  // - appType: 'custom' ปิดการใช้งาน HTML serving ของ Vite
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: "custom",
  });

  // ใช้ Vite middleware เพื่อให้สามารถใช้งาน HMR และคุณสมบัติอื่นๆ ของ Vite ได้
  app.use(vite.middlewares);

  // เริ่มต้นเซิร์ฟเวอร์ที่พอร์ต 3000
  app.listen(3000, () => {
    console.log("Server running at http://localhost:3000");
  });
}

createServer();

Building for Production

กระบวนการ build แอปพลิเคชัน SSR สำหรับ production แยกเป็นส่วนของไคลเอนต์และเซิร์ฟเวอร์

สคริปต์ build ใน package.json

json
{
	"scripts": {
		"build:client": "vite build --outDir dist/client",
		"build:server": "vite build --outDir dist/server --ssr src/entry-server.js"
	}
}
  • build:client สร้างไฟล์สำหรับไคลเอนต์
  • build:server สร้างไฟล์สำหรับเซิร์ฟเวอร์ (ใช้ flag --ssr)

การปรับเปลี่ยน server.js สำหรับ production

ts
// ในโหมด production
if (process.env.NODE_ENV === "production") {
  // ใช้ไฟล์จาก dist/client เป็นเทมเพลต
  const template = fs.readFileSync("dist/client/index.html", "utf-8");

  // โหลด entry-server.js จากผลลัพธ์การ build
  const { render } = await import("./dist/server/entry-server.js");

  // เสิร์ฟไฟล์ static จาก dist/client
  app.use(express.static("dist/client"));
}

Generating Preload Directives

วิธีการสร้างและใช้งาน SSR Manifest เพื่อเพิ่มประสิทธิภาพการโหลดทรัพยากร

การสร้าง SSR Manifest

ts
// การตั้งค่า script ใน package.json สำหรับสร้าง manifest file
{
  "scripts": {
    // คำสั่ง build สำหรับ client พร้อมสร้าง manifest
    "build:client": "vite build --outDir dist/client --ssrManifest"
  }
}
// เพิ่ม flag `--ssrManifest` เพื่อสร้างไฟล์ manifest
// ไฟล์จะถูกสร้างที่ `dist/client/.vite/ssr-manifest.json`
// ใช้ map module IDs ไปยัง client files

การใช้งานกับ Vue

ts
// การใช้งาน Vue SSR Context เพื่อเก็บ module IDs
// สร้าง context object สำหรับเก็บข้อมูลระหว่าง render
const ctx = {};
// Render หน้าเป็น HTML และเก็บ module IDs ที่ใช้ใน ctx
const html = await vueServerRenderer.renderToString(app, ctx);
// ctx.modules จะมี Set ของ module IDs ที่ใช้
// `@vitejs/plugin-vue` รองรับการเก็บ module IDs อัตโนมัติ
// ใช้ข้อมูลนี้เพื่อสร้าง preload directives

การอ่าน manifest ใน production

ts
// ในโหมด production
const manifest = fs.readFileSync(
  "dist/client/.vite/ssr-manifest.json",
  "utf-8",
);
const html = await render(url, manifest);
  • ส่ง manifest ไปยังฟังก์ชัน render
  • ใช้สร้าง preload directives สำหรับ async routes

Pre-Rendering / SSG

Static Site Generation

ts
// ตัวอย่างสคริปต์ pre-render
const routes = ["/", "/about", "/contact"];

for (const url of routes) {
  const html = await render(url);
  fs.writeFileSync(`dist/static${url}.html`, html);
}
  • ใช้ logic เดียวกับ production SSR
  • เหมาะสำหรับ routes ที่รู้ข้อมูลล่วงหน้า
  • สามารถใช้เป็นรูปแบบ Static Site Generation (SSG)

SSR Externals

การตั้งค่า dependencies สำหรับ SSR ในไฟล์ vite.config.js เพื่อควบคุมการทำงานของ packages ต่างๆ

การตั้งค่าใน vite.config.js

ts
export default {
  ssr: {
    noExternal: ["package-to-transform"],
    external: ["linked-package"],
  },
};
  • Dependency ส่วนใหญ่จะถูก externalize โดยอัตโนมัติ
  • ใช้ noExternal สำหรับ dependency ที่ต้องผ่านการ transform
  • ใช้ external สำหรับ dependency ที่ต้องการให้ทำงานเหมือนไม่ถูก link

การทำงานกับ Aliases

bash
# ใน Yarn/pnpm ใช้ npm: prefix
"dependencies": {
  "my-alias": "npm:actual-package@^1.0.0"
}

SSR-specific Plugin Logic

ตัวอย่าง Plugin สำหรับ SSR

ts
export function mySSRPlugin() {
  return {
    name: "my-ssr",
    transform(code, id, options) {
      if (options?.ssr) {
        // ทำการ transform เฉพาะ SSR
        return transformSSRCode(code);
      }
      return code;
    },
  };
}
  • Vite ส่งค่า ssr ใน options object ให้ plugin
  • ทำงานกับ hooks: resolveId, load, transform
  • ใช้สำหรับแปลงโค้ดต่างกันระหว่าง SSR และ Client

SSR Target

การตั้งค่า target สำหรับ SSR build

ts
export default {
  ssr: {
    target: "node", // หรือ 'webworker'
  },
};
  • node (ค่าเริ่มต้น): สำหรับการรันบน Node.js
  • webworker: สำหรับการรันบน Web Worker
  • แต่ละ platform มีวิธี resolve packages ที่ต่างกัน

SSR Bundle

SSR Resolve Conditions

การกำหนดเงื่อนไขการ resolve packages

ts
export default {
  ssr: {
    resolve: {
      conditions: ["my-ssr-condition"],
      externalConditions: ["node"],
    },
  },
};
  • conditions: เงื่อนไขสำหรับ SSR build
  • externalConditions: เงื่อนไขสำหรับ externalized dependencies
  • ค่าเริ่มต้นใช้จาก resolve.conditions

Vite CLI

การใช้ CLI กับ SSR

ts
export default {
  server: {
    configureServer(server) {
      // เพิ่ม SSR middleware
      server.middlewares.use((req, res, next) => {
        // SSR logic
      });
    },
  },
};
  • ใช้ configureServer สำหรับ development server
  • ใช้ configurePreviewServer สำหรับ preview server
  • ควรใช้ post hook เพื่อให้ middleware ทำงานหลัง Vite middlewares