Nuxt 3: SSR และการสร้างเพจแบบ Static คู่มือฉบับสมบูรณ์
เชี่ยวชาญ SSR และการสร้างเพจแบบ Static ด้วย Nuxt 3 ตั้งแต่ useFetch ไปจนถึง route rules เรียนรู้วิธีเพิ่มประสิทธิภาพแอปพลิเคชัน Vue.js

Nuxt 3 ปรับเปลี่ยนวิธีการสร้างแอปพลิเคชัน Vue.js โดยเสนอโหมดการเรนเดอร์หลายแบบที่เหมาะสมกับกรณีการใช้งานที่แตกต่างกัน ตั้งแต่ Server-Side Rendering (SSR) ไปจนถึงการสร้างเพจแบบ Static และการเรนเดอร์แบบไฮบริด เฟรมเวิร์กนี้ให้ความยืดหยุ่นที่โดดเด่นในการเพิ่มประสิทธิภาพและ SEO
บทช่วยสอนนี้สมมุติว่ามีความรู้พื้นฐานเกี่ยวกับ Vue 3 และ Composition API ความคุ้นเคยกับแนวคิดการเรนเดอร์ฝั่งเซิร์ฟเวอร์มีประโยชน์แต่ไม่จำเป็น เพราะแนวคิดพื้นฐานจะถูกอธิบายตลอดทั้งคู่มือ
ทำความเข้าใจโหมดการเรนเดอร์ของ Nuxt 3
ก่อนที่จะลงลึกในโค้ด สิ่งสำคัญคือต้องเข้าใจความแตกต่างระหว่างโหมดการเรนเดอร์ที่มี โหมดแต่ละแบบตอบสนองความต้องการเฉพาะในด้านประสิทธิภาพ SEO และประสบการณ์ผู้ใช้
SSR (Server-Side Rendering) สร้าง HTML บนเซิร์ฟเวอร์ในทุกคำขอ การสร้างเพจแบบ Static (SSG) สร้างทุกหน้าไว้ล่วงหน้าตอน build โหมดไฮบริดอนุญาตให้รวมแนวทางเหล่านี้เข้าด้วยกันแบบหน้าต่อหน้า
// การกำหนดค่าโหมดการเรนเดอร์ต่าง ๆ
export default defineNuxtConfig({
// SSR เปิดใช้งานโดยค่าเริ่มต้น (แนะนำสำหรับ SEO)
ssr: true,
// การสร้างเพจแบบ Static: เรนเดอร์ทุกหน้าไว้ล่วงหน้า
// ใช้ 'npm run generate' เพื่อ build
// target: 'static', // ไวยากรณ์ Nuxt 2
// โหมดไฮบริด: ปรับแต่งได้ต่อ route
routeRules: {
// หน้าแรก: เรนเดอร์ล่วงหน้าและแคชไว้
'/': { prerender: true },
// บล็อก: การสร้างเพจแบบ Static
'/blog/**': { prerender: true },
// Dashboard: เรนเดอร์เฉพาะฝั่ง client
'/dashboard/**': { ssr: false },
// API: ไม่มี pre-rendering
'/api/**': { prerender: false }
}
})การกำหนดค่านี้แสดงให้เห็นพลังของโหมดไฮบริด: แต่ละส่วนของแอปพลิเคชันใช้โหมดการเรนเดอร์ที่เหมาะสมกับความต้องการของตนมากที่สุด
การดึงข้อมูลด้วย useFetch และ useAsyncData
Nuxt 3 มี composable หลักสองตัวสำหรับการดึงข้อมูลแบบ isomorphic composable เหล่านี้ทำงานได้ทั้งฝั่งเซิร์ฟเวอร์และฝั่ง client พร้อมจัดการ hydration อัตโนมัติ
useFetch คือ wrapper รอบ useAsyncData ที่ทำให้การเรียก HTTP ง่ายขึ้น useAsyncData ให้การควบคุมที่มากขึ้นสำหรับกรณีการใช้งานขั้นสูง
<script setup lang="ts">
// pages/blog/[slug].vue
// หน้ารายละเอียดบทความด้วย useFetch
// รับพารามิเตอร์ของ route
const route = useRoute()
// useFetch: ดึงข้อมูลอัตโนมัติ
// ข้อมูลจะถูกดึงที่ฝั่งเซิร์ฟเวอร์แล้ว hydrate ที่ client
const { data: article, pending, error } = await useFetch(
`/api/articles/${route.params.slug}`,
{
// คีย์เฉพาะสำหรับแคชและการลดความซ้ำซ้อน
key: `article-${route.params.slug}`,
// แปลงข้อมูลเมื่อจำเป็น
transform: (response) => response.data,
// ตัวเลือกแคช
getCachedData: (key) => {
// ตรวจสอบว่าข้อมูลอยู่ในแคชหรือไม่
const nuxtApp = useNuxtApp()
return nuxtApp.payload.data[key]
}
}
)
// การจัดการข้อผิดพลาดด้วยการนำทาง
if (error.value) {
throw createError({
statusCode: 404,
message: 'ไม่พบบทความ'
})
}
</script>
<template>
<div>
<div v-if="pending" class="loading">
กำลังโหลดบทความ...
</div>
<article v-else-if="article">
<h1>{{ article.title }}</h1>
<div v-html="article.content" />
</article>
</div>
</template>สำหรับกรณีที่ต้องการการควบคุมมากขึ้น useAsyncData ช่วยให้สามารถเรียกใช้ฟังก์ชัน asynchronous ใด ๆ ได้
<script setup lang="ts">
// pages/products/index.vue
// รายการสินค้าด้วย useAsyncData และตัวกรอง
const route = useRoute()
// useAsyncData: ควบคุมตรรกะการ fetching ได้อย่างเต็มที่
const { data: products, refresh } = await useAsyncData(
'products-list',
async () => {
// ดึงข้อมูลจากหลายแหล่งหากจำเป็น
const [productsResponse, categoriesResponse] = await Promise.all([
$fetch('/api/products', {
query: {
category: route.query.category,
sort: route.query.sort || 'date'
}
}),
$fetch('/api/categories')
])
// รวมและแปลงข้อมูล
return {
products: productsResponse.data,
categories: categoriesResponse.data,
total: productsResponse.meta.total
}
},
{
// รีเฟรชเมื่อ query params เปลี่ยน
watch: [() => route.query]
}
)
// ฟังก์ชันรีเฟรชด้วยตนเอง
const updateFilters = async (newCategory: string) => {
await navigateTo({
query: { ...route.query, category: newCategory }
})
}
</script>composable เหล่านี้ป้องกันการ fetch ซ้ำสองครั้ง: ข้อมูลที่ได้จากเซิร์ฟเวอร์จะถูก serialize ลงใน HTML payload และนำกลับมาใช้ระหว่างการ hydrate ที่ฝั่ง client
การปรับแต่ง SSR ด้วย server hooks
SSR ของ Nuxt 3 สามารถปรับแต่งได้ผ่าน server hooks hook เหล่านี้ช่วยให้สามารถเข้าแทรกแซงในขั้นตอนต่าง ๆ ของวงจรการเรนเดอร์เพื่อปรับเปลี่ยนพฤติกรรมเริ่มต้น
// ปลั๊กอินเซิร์ฟเวอร์เพื่อปรับแต่งการเรนเดอร์ SSR
export default defineNitroPlugin((nitroApp) => {
// hook ที่ทำงานก่อนการเรนเดอร์ของแต่ละหน้า
nitroApp.hooks.hook('render:html', (html, { event }) => {
// ฉีดสคริปต์หรือเมตาดาต้า
html.head.push(`
<script>
// การวิเคราะห์หรือการตั้งค่าระดับโลก
window.__APP_CONFIG__ = {
environment: '${process.env.NODE_ENV}',
apiUrl: '${process.env.API_URL}'
}
</script>
`)
})
// hook สำหรับการจัดการแคชการเรนเดอร์
nitroApp.hooks.hook('render:response', (response, { event }) => {
// เพิ่ม header แคชแบบกำหนดเอง
const path = event.path
if (path.startsWith('/blog/')) {
// แคชระยะยาวสำหรับบทความบล็อก
response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400'
} else if (path.startsWith('/api/')) {
// ไม่มีแคชสำหรับ API
response.headers['Cache-Control'] = 'no-store'
}
})
})hook render:response เหมาะอย่างยิ่งสำหรับการนำกลยุทธ์แคช HTTP มาใช้ การรวม SSR กับ CDN ที่เคารพ header Cache-Control ช่วยให้สามารถให้บริการหน้าที่เรนเดอร์ล่วงหน้าโดยยังคงรักษาความสามารถในการยกเลิกแคชได้
การสร้างเพจแบบ Static ด้วย nuxt generate
การสร้างเพจแบบ Static สร้างทุกหน้าไว้ล่วงหน้าตอน build วิธีนี้เหมาะสำหรับเว็บไซต์ที่มีเนื้อหาคงที่ เช่น บล็อก เอกสาร หรือเว็บไซต์การตลาด
สำหรับ route แบบไดนามิก Nuxt ต้องรู้ทุก URL ที่จะสร้าง hook prerender:routes ช่วยให้กำหนด route เหล่านี้ในเชิงโปรแกรมได้
// การกำหนดค่าครบถ้วนสำหรับการสร้างเพจแบบ Static
export default defineNuxtConfig({
// เปิดใช้การสร้างเพจแบบ Static
nitro: {
prerender: {
// เปิดการ crawl ลิงก์อัตโนมัติ
crawlLinks: true,
// route ที่ต้องรวมไว้เสมอ
routes: ['/', '/about', '/contact'],
// เพิกเฉย route บางอย่าง
ignore: ['/admin', '/api']
}
},
hooks: {
// hook สำหรับสร้าง route แบบไดนามิก
async 'prerender:routes'(ctx) {
// ดึงบทความจาก API หรือ DB
const articles = await fetch('https://api.example.com/articles')
.then(res => res.json())
// เพิ่ม route บทความ
for (const article of articles) {
ctx.routes.add(`/blog/${article.slug}`)
}
// ดึงหมวดหมู่
const categories = await fetch('https://api.example.com/categories')
.then(res => res.json())
for (const category of categories) {
ctx.routes.add(`/category/${category.slug}`)
}
}
}
})สำหรับโครงการที่มีหน้าจำนวนมาก crawler อัตโนมัติอาจไม่เพียงพอ นี่คือแนวทางที่ทนทานกว่าด้วยไฟล์การกำหนดค่าแยก
// ยูทิลิตี้สำหรับสร้างรายการ route แบบไดนามิก
import { prisma } from './prisma'
export async function getAllStaticRoutes(): Promise<string[]> {
const routes: string[] = []
// บทความบล็อก
const articles = await prisma.article.findMany({
where: { published: true },
select: { slug: true, category: { select: { slug: true } } }
})
for (const article of articles) {
routes.push(`/blog/${article.category.slug}/${article.slug}`)
}
// หน้าสินค้า
const products = await prisma.product.findMany({
where: { active: true },
select: { slug: true }
})
for (const product of products) {
routes.push(`/products/${product.slug}`)
}
// หน้าแท็ก
const tags = await prisma.tag.findMany({
select: { slug: true }
})
for (const tag of tags) {
routes.push(`/tags/${tag.slug}`)
}
return routes
}พร้อมที่จะพิชิตการสัมภาษณ์ Vue.js / Nuxt.js แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
การเรนเดอร์แบบไฮบริดด้วย routeRules
การเรนเดอร์แบบไฮบริดเป็นคุณสมบัติเด่นของ Nuxt 3 ช่วยให้กำหนดกฎการเรนเดอร์ที่แตกต่างกันต่อ route ผสมจุดเด่นของ SSR และ SSG เข้าด้วยกัน
// การกำหนดค่าขั้นสูงสำหรับการเรนเดอร์แบบไฮบริด
export default defineNuxtConfig({
routeRules: {
// หน้าการตลาด: เรนเดอร์ล่วงหน้าและแคชระยะยาว
'/': { prerender: true },
'/pricing': { prerender: true },
'/features/**': { prerender: true },
// บล็อก: ISR (Incremental Static Regeneration)
// ตรวจสอบใหม่ทุกชั่วโมง
'/blog/**': {
isr: 3600,
prerender: true
},
// เอกสาร: แคช CDN พร้อมการตรวจสอบใหม่
'/docs/**': {
swr: 86400, // Stale-while-revalidate
prerender: true
},
// E-commerce: SSR ที่มีแคชระยะสั้น
'/products/**': {
ssr: true,
cache: {
maxAge: 60,
staleMaxAge: 300
}
},
// ตะกร้าและการชำระเงิน: เฉพาะฝั่ง client
'/cart': { ssr: false },
'/checkout/**': { ssr: false },
// Dashboard: โหมด SPA
'/dashboard/**': {
ssr: false,
// ปิดการ pre-rendering
prerender: false
},
// route API: ไม่มีแคชโดยค่าเริ่มต้น
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
}
}
}
})การกำหนดค่านี้สะท้อนสถาปัตยกรรมทั่วไปของแอปพลิเคชันสมัยใหม่: หน้าสาธารณะถูกเพิ่มประสิทธิภาพด้วย SSG เพื่อ SEO ในขณะที่ส่วนที่มีปฏิสัมพันธ์ใช้การเรนเดอร์ฝั่ง client
การเพิ่มประสิทธิภาพด้วยการแคชข้อมูล
นอกเหนือจากการแคชหน้า Nuxt 3 ยังอนุญาตให้แคชข้อมูลที่ดึงมา กลยุทธ์นี้ช่วยลดภาระบน API และปรับปรุงเวลาตอบสนอง
// API endpoint ที่มีการแคชข้อมูล
import { getArticleBySlug } from '~/server/utils/articles'
export default defineCachedEventHandler(
async (event) => {
const slug = getRouterParam(event, 'slug')
if (!slug) {
throw createError({
statusCode: 400,
message: 'ไม่มี slug'
})
}
const article = await getArticleBySlug(slug)
if (!article) {
throw createError({
statusCode: 404,
message: 'ไม่พบบทความ'
})
}
return article
},
{
// คีย์แคชอ้างอิงจาก slug
getKey: (event) => `article-${getRouterParam(event, 'slug')}`,
// ระยะเวลาแคช: 1 ชั่วโมง
maxAge: 3600,
// Stale-while-revalidate: ให้บริการแคชเก่าระหว่างการอัปเดต
staleMaxAge: 7200,
// การยกเลิกแคชด้วย tag
tags: ['articles']
}
)เพื่อยกเลิกแคชเมื่อเนื้อหาเปลี่ยนแปลง Nuxt มีระบบ tag
// การอัปเดตบทความพร้อมยกเลิกแคช
import { updateArticle } from '~/server/utils/articles'
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
const body = await readBody(event)
// อัปเดตบทความ
const article = await updateArticle(slug, body)
// ยกเลิกแคชสำหรับบทความนี้
await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`)
// หรือยกเลิกแคชด้วย tag (บทความทั้งหมด)
// await useStorage('cache').clear('articles')
return article
})ในระบบ production ที่มีหลาย instance แคชในหน่วยความจำไม่เพียงพอ แนะนำให้ตั้งค่า Redis หรือระบบกระจายอื่น ๆ ผ่านการตั้งค่า Nitro เพื่อรับประกันความสอดคล้องระหว่าง instance
การจัดการ SEO และเมตาดาต้า
SSR ช่วยให้ทำการเพิ่มประสิทธิภาพ SEO ได้โดยสร้างเมตาดาต้าฝั่งเซิร์ฟเวอร์ Nuxt 3 มีหลายวิธีในการจัดการ meta tag แบบไดนามิก
<script setup lang="ts">
// pages/blog/[slug].vue
// หน้าบล็อกที่เพิ่มประสิทธิภาพ SEO
const route = useRoute()
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)
// การกำหนดค่า SEO แบบไดนามิกตามบทความ
useSeoMeta({
title: article.value?.title,
description: article.value?.excerpt,
ogTitle: article.value?.title,
ogDescription: article.value?.excerpt,
ogImage: article.value?.coverImage,
ogType: 'article',
twitterCard: 'summary_large_image',
twitterTitle: article.value?.title,
twitterDescription: article.value?.excerpt,
twitterImage: article.value?.coverImage
})
// ข้อมูลโครงสร้างสำหรับ Google
useHead({
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: article.value?.title,
description: article.value?.excerpt,
image: article.value?.coverImage,
datePublished: article.value?.publishedAt,
dateModified: article.value?.updatedAt,
author: {
'@type': 'Organization',
name: 'SharpSkill'
}
})
}
]
})
</script>สำหรับหน้าแบบ Static เมตาดาต้าสามารถกำหนดได้โดยตรงในคอมโพเนนต์
<script setup lang="ts">
// pages/about.vue
// หน้าแบบ Static พร้อม SEO
definePageMeta({
title: 'เกี่ยวกับ'
})
useSeoMeta({
title: 'เกี่ยวกับ SharpSkill | การเตรียมตัวสัมภาษณ์ทางเทคนิค',
description: 'ค้นพบ SharpSkill แพลตฟอร์มเตรียมตัวสัมภาษณ์ทางเทคนิค ภารกิจคือช่วยนักพัฒนาให้ประสบความสำเร็จในการสัมภาษณ์ทางเทคนิค',
ogTitle: 'เกี่ยวกับ SharpSkill',
ogDescription: 'แพลตฟอร์มเตรียมตัวสัมภาษณ์ทางเทคนิค',
ogImage: '/images/og-about.webp'
})
</script>การ Deploy และข้อพิจารณาในระบบ Production
การเลือก deploy ขึ้นอยู่กับโหมดการเรนเดอร์ที่ใช้ ต่อไปนี้คือตัวเลือกหลักและการกำหนดค่า
// การกำหนดค่าสำหรับสภาพแวดล้อมการ deploy ต่าง ๆ
export default defineNuxtConfig({
nitro: {
// Preset ตามแพลตฟอร์มเป้าหมาย
// preset: 'vercel', // Vercel
// preset: 'netlify', // Netlify
// preset: 'cloudflare-pages', // Cloudflare
// preset: 'node-server', // Node.js แบบดั้งเดิม
// การกำหนดค่าสำหรับ Node.js ใน production
preset: 'node-server',
// การบีบอัด response
compressPublicAssets: true,
// การกำหนดค่าที่เก็บแคช
storage: {
cache: {
driver: 'redis',
url: process.env.REDIS_URL
}
}
},
// ตัวแปรสภาพแวดล้อมขณะรันไทม์
runtimeConfig: {
// ความลับ (ไม่เปิดเผยต่อ client)
apiSecret: process.env.API_SECRET,
// การกำหนดค่าสาธารณะ
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
}
}
})สำหรับการ deploy แบบ Static คำสั่ง npm run generate จะสร้างโฟลเดอร์ .output/public ที่พร้อมจะ deploy ไปยังโฮสต์ไฟล์ Static ใด ๆ
# การสร้างเพจแบบ Static
npm run generate
# เนื้อหาของ .output/public สามารถ deploy ได้ที่:
# - Vercel (ตรวจจับอัตโนมัติ)
# - Netlify (กำหนดค่าอัตโนมัติ)
# - GitHub Pages
# - S3 + CloudFront
# - CDN หรือเซิร์ฟเวอร์ไฟล์ Static ใด ๆเริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
สรุป
Nuxt 3 มอบความยืดหยุ่นที่ยอดเยี่ยมในการเรนเดอร์แอปพลิเคชัน Vue.js การเลือกระหว่าง SSR, SSG และการเรนเดอร์แบบไฮบริดขึ้นอยู่กับความต้องการเฉพาะของแต่ละโครงการ
ประเด็นสำคัญ:
✅ SSR: เหมาะสำหรับเนื้อหาแบบไดนามิกที่ต้องการ SEO ที่ดี (e-commerce เว็บไซต์ข่าว)
✅ SSG: เหมาะสำหรับเนื้อหาที่คงที่ (บล็อก เอกสาร เว็บไซต์การตลาด)
✅ ไฮบริด: แนวทางที่ดีที่สุดสำหรับแอปพลิเคชันที่ซับซ้อนและมีความต้องการหลากหลาย
✅ useFetch/useAsyncData: hydration อัตโนมัติและการจัดการแคช
✅ routeRules: การกำหนดค่าอย่างละเอียดสำหรับพฤติกรรมของแต่ละ route
✅ การแคช: หลายกลยุทธ์เพื่อเพิ่มประสิทธิภาพในระบบ production
การรวมการเรนเดอร์แบบไฮบริดกับกลยุทธ์การแคชที่ออกแบบมาอย่างดีช่วยให้สามารถสร้างแอปพลิเคชันที่มีประสิทธิภาพสูงและเหมาะสมสำหรับ SEO โดยยังคงรักษาการตอบสนองของ Single Page Applications ไว้
แท็ก
แชร์
บทความที่เกี่ยวข้อง

คำถามสัมภาษณ์ Vue.js: 25 ข้อสำคัญเพื่อคว้างาน
เตรียมสัมภาษณ์ Vue.js ด้วย 25 คำถามสำคัญ ตั้งแต่ระบบ reactivity ถึง composables เพื่อพิชิตการสัมภาษณ์ครั้งต่อไป

Vue 3 Pinia vs Vuex: State Management สมัยใหม่และคำถามสัมภาษณ์ 2026
วิเคราะห์เปรียบเทียบ Pinia กับ Vuex: สถาปัตยกรรม, TypeScript, Composition API, ประสิทธิภาพ, กลยุทธ์การย้าย และคำถามสัมภาษณ์ Vue state management 2026

Vue 3 Composition API: คู่มือฉบับสมบูรณ์เพื่อเชี่ยวชาญระบบ Reactivity
เชี่ยวชาญ Vue 3 Composition API ผ่านคู่มือเชิงปฏิบัตินี้ เรียนรู้ ref, reactive, computed, watch และ composables เพื่อสร้างแอปพลิเคชัน Vue ที่มีประสิทธิภาพสูง