จาก component ใน React, Vue หรือ Svelte ขั้นต่อไปคือเว็บที่มีหลายหน้าและมี endpoint ของตัวเอง คู่มือนี้ลอง Next.js, Nuxt และ SvelteKit ด้วยงานเดียวกัน: กรอกชื่อ เรียก API ใน origin เดียวกัน แล้วแสดง JSON พร้อมมีหน้า /about ให้ลอง routing

เลือกระดับของเครื่องมือให้ตรงงาน

Next.js ต่อจาก React, Nuxt ต่อจาก Vue และ SvelteKit ต่อจาก Svelte ทั้งสามช่วยจัดโครงแอปและฝั่ง server แต่รายละเอียด routing, rendering, data loading และ deployment ต่างกัน ไม่ใช่แค่เปลี่ยนนามสกุล component

จุดเริ่มของทีมชุดที่ควรลองก่อน
อ่าน JSX และดูแล React อยู่แล้วNext.js กับ App Router และขอบเขต Client Component
ใช้ Vue และชอบ script/templateNuxt กับ file-based routing และ server routes
ใช้ Svelte และต้องการโครงแอปSvelteKit กับ +page.svelte และ +server.ts

เกณฑ์นี้เป็นการประเมินของ Reefsight ให้ตรวจ library ที่ต้องใช้และทีมที่รับช่วงดูแลด้วย เปิด ตาราง Next.js / Nuxt / SvelteKit เพื่ออ่านบทบาทกับข้อจำกัด หากยังไม่เคยเขียน component ให้เริ่มจาก React / Vue / Svelte

กราฟปกใช้ Stack Overflow Developer Survey 2026: Next.js 20.6% (2,117 คน), Nuxt.js 4.4% (454 คน), SvelteKit 5.6% (576 คน) จากผู้ตอบคำถามนี้ 10,281 คนที่รายงานการใช้งานในปีที่ผ่านมา เลือกได้หลายข้อและสมัครใจตอบ ไม่ใช่ส่วนแบ่งตลาดไทย จำนวนเว็บ หรือ benchmark ชื่อ Nuxt.js ในแบบสำรวจหมายถึงรายการที่นำมาอ้างอิง ไม่ได้บอกว่าผู้ตอบทั้งหมดใช้ Nuxt 4 ดู วิธีสำรวจ ข้อมูลโดย Stack Exchange Inc. ภายใต้ ODbL 1.0 กราฟวาดใหม่โดย Reefsight

เตรียมเครื่องและเข้าใจรุ่นที่ตรึงไว้

เราทดลองบน macOS arm64 ด้วย Node.js 22.23.1 ใช้ตัวสร้าง create-next-app 16.4.2, create-nuxt 4.0.1 และ sv 1.1.1 โดยไม่เลือก nightly/RC ส่วนตัวสร้างกับ runtime เป็นคนละแพ็กเกจ

Terminal
node --version
npm --version

ขั้น install ด้านล่างเรียก npm 12.2.0 เฉพาะคำสั่งผ่าน npm exec จึงไม่ต้องเปลี่ยน npm ที่ติดตั้งทั่วเครื่อง รุ่นนี้ต้องใช้ Node ^22.22.2, ^24.15.0 หรือ >=26.0.0 ตรวจ engines ของ CLI และ runtime อีกครั้งเมื่อใช้รุ่นอื่น ในการตรวจพบ npm 10.9.8 อ่าน dependency tree ของชุดนี้ผิดพลาด จึงใช้ npm 12.2.0 แล้วติดตั้งจริงใหม่ ไม่ใช่ข้ามการติดตั้ง

ตรึงรุ่นตัวสร้างยังไม่ล็อก dependency ทั้งหมด เก็บ package-lock.json ที่ติดตั้งแล้ว และใช้ npm ci เมื่อติดตั้งซ้ำจาก lockfile ที่ตรวจไว้

Next.js: แยกหน้า UI กับ route handler

Terminal
npm exec --yes --package=create-next-app@16.4.2 -- create-next-app next-demo --ts --app --use-npm --empty --no-tailwind --disable-git --skip-install --no-agent-feedback --yes
cd next-demo
npm pkg delete devDependencies.eslint devDependencies.eslint-config-next scripts.lint
npm exec --yes --package=npm@12.2.0 -- npm install

ตัวอย่างนี้ตัด linter ที่ไม่ได้ใช้ออกจาก template และกำหนด next.config.ts แบบพื้นฐาน เพื่อเน้น routing กับ API อิง Next.js installation แล้วแทนไฟล์ต่อไปนี้ หน้าแรกใช้ state และ event จึงประกาศ use client ส่วน route.ts รันฝั่ง server ไม่ย้าย secret มาใส่หน้า UI

next.config.ts

ts
import type {NextConfig} from 'next';
const config: NextConfig = {};
export default config;

app/layout.tsx

tsx
import './globals.css';
export default function RootLayout({children}: {children: React.ReactNode}) {
  return <html lang="th"><body>{children}</body></html>;
}

app/page.tsx

tsx
"use client";
import {useState, type FormEvent} from 'react';
import Link from 'next/link';
export default function Home() {
  const [name, setName] = useState('ReefSight');
  const [message, setMessage] = useState('พร้อมเรียก API');
  async function submit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    try {
      const response = await fetch('/api/hello?name=' + encodeURIComponent(name));
      const data = await response.json();
      setMessage(response.ok ? data.message : data.error);
    } catch {setMessage('เชื่อมต่อไม่สำเร็จ ลองใหม่อีกครั้ง');}
  }
  return <main><h1>ลอง Next.js</h1><form onSubmit={submit}>
    <label>ชื่อ <input value={name} onChange={e => setName(e.target.value)} /></label>
    <button type="submit">เรียก API</button></form>
    <p role="status">{message}</p><Link href="/about">เกี่ยวกับตัวอย่าง</Link></main>;
}

app/api/hello/route.ts

ts
export function GET(request: Request) {
  const name = new URL(request.url).searchParams.get('name')?.trim() ?? 'โลก';
  if (!name || [...name].length > 40) {
    return Response.json({error: 'ชื่อต้องมี 1–40 ตัวอักษร'}, {status: 400});
  }
  return Response.json({message: `สวัสดี ${name}`});
}

app/about/page.tsx

tsx
import Link from 'next/link';
export default function About() {return <main><h1>เกี่ยวกับตัวอย่าง</h1><p>ฟอร์มส่งชื่อให้ API ฝั่ง server แล้วนำ JSON มาแสดง</p><Link href="/">กลับหน้าทดลอง</Link></main>;}

ชุดที่ตรวจใช้ Webpack ผ่าน flag --webpack ในคำสั่ง build เพราะ native SWC บนเครื่องตรวจโหลดไม่ได้และ Next CLI แนะนำเส้นทางนี้ ยังใช้ Next.js 16.4.2 รุ่นเดิม

GET อ่าน query แล้วตอบ JSON; error ที่ตัวอย่างกำหนดใช้ status 400 อ่าน Route Handlers และ Linking and Navigating ก่อนเพิ่ม dynamic routes หรือวิธี cache ตามงาน

Terminal
npm run build -- --webpack
npm run start -- --hostname 127.0.0.1 --port 3000

Nuxt: เปิด pages แล้ววาง API ใน server

Terminal
npm exec --yes --package=create-nuxt@4.0.1 -- create-nuxt nuxt-demo --template minimal --no-install --no-gitInit --packageManager npm
cd nuxt-demo
npm exec --yes --package=npm@12.2.0 -- npm install

ใช้ Nuxt installation กับโครง Nuxt 4 ตัวอย่างแทน app/app.vue ด้วย NuxtPage เพื่อให้แสดงหน้าใน app/pages ตาม pages directory

app/app.vue

vue
<template><NuxtPage /></template>

app/pages/index.vue

vue
<script setup lang="ts">
const name = ref('ReefSight');
const message = ref('พร้อมเรียก API');
async function submit() {
  try {
    const response = await fetch('/api/hello?name=' + encodeURIComponent(name.value));
    const data = await response.json();
    message.value = response.ok ? data.message : data.error;
  } catch {message.value = 'เชื่อมต่อไม่สำเร็จ ลองใหม่อีกครั้ง';}
}
</script>
<template><main><h1>ลอง Nuxt</h1><form @submit.prevent="submit">
  <label>ชื่อ <input v-model="name" /></label><button type="submit">เรียก API</button>
</form><p role="status">{{ message }}</p><NuxtLink to="/about">เกี่ยวกับตัวอย่าง</NuxtLink></main></template>

server/api/hello.get.ts

ts
export default defineEventHandler(event => {
  const raw = getQuery(event).name;
  const name = raw === undefined ? 'โลก' : typeof raw === 'string' ? raw.trim() : '';
  if (!name || [...name].length > 40) {
    setResponseStatus(event, 400);
    return {error: 'ชื่อต้องมี 1–40 ตัวอักษร'};
  }
  return {message: `สวัสดี ${name}`};
});

app/pages/about.vue

vue
<template><main><h1>เกี่ยวกับตัวอย่าง</h1><p>ฟอร์มส่งชื่อให้ API ฝั่ง server แล้วนำ JSON มาแสดง</p><NuxtLink to="/">กลับหน้าทดลอง</NuxtLink></main></template>

nuxt.config.ts

ts
export default defineNuxtConfig({compatibilityDate: '2026-10-17', devtools: {enabled: false}, css: ['~/assets/main.css']});

ไฟล์ hello.get.ts รับ GET ที่ /api/hello และอ่าน query จาก event; ตัวอย่างใช้ setResponseStatus เพื่อกำหนด 400 พร้อม error JSON ของเรา ดู Nuxt server directory

Terminal
npm run build
node .output/server/index.mjs

คำสั่งหลังใช้ Node server output ที่ build ในรอบนี้ โดยปริยายเปิด port 3000; ถ้ามีโปรแกรมใช้ port นั้นอยู่ให้หยุดตัวอย่างก่อนหน้าหรือกำหนด NITRO_PORT/NITRO_HOST ตาม environment ของเครื่อง

SvelteKit: หน้าและ endpoint อยู่ใน routes

Terminal
npm exec --yes --package=sv@1.1.1 -- sv create sveltekit-demo --template minimal --types ts --no-add-ons --no-install
cd sveltekit-demo
npm exec --yes --package=npm@12.2.0 -- npm install

อิง การสร้าง SvelteKit project แล้วเพิ่มไฟล์เหล่านี้ +page.svelte เป็น UI ส่วน +server.ts เป็น endpoint อ่าน routing เพื่อแยกบทบาทกับ +page.server.ts ที่ใช้โหลดข้อมูลของหน้า

src/routes/+layout.svelte

svelte
<script lang="ts">import '../app.css'; let {children} = $props();</script>
{@render children()}

src/routes/+page.svelte

svelte
<script lang="ts">
let name = $state('ReefSight');
let message = $state('พร้อมเรียก API');
async function submit(event: SubmitEvent) {
  event.preventDefault();
  try {
    const response = await fetch('/api/hello?name=' + encodeURIComponent(name));
    const data = await response.json();
    message = response.ok ? data.message : data.error;
  } catch {message = 'เชื่อมต่อไม่สำเร็จ ลองใหม่อีกครั้ง';}
}
</script>
<main><h1>ลอง SvelteKit</h1><form onsubmit={submit}>
  <label>ชื่อ <input bind:value={name} /></label><button type="submit">เรียก API</button>
</form><p role="status">{message}</p><a href="/about">เกี่ยวกับตัวอย่าง</a></main>

src/routes/api/hello/+server.ts

ts
import {json} from '@sveltejs/kit';
export function GET({url}: {url: URL}) {
  const name = url.searchParams.get('name')?.trim() ?? 'โลก';
  if (!name || [...name].length > 40) {
    return json({error: 'ชื่อต้องมี 1–40 ตัวอักษร'}, {status: 400});
  }
  return json({message: `สวัสดี ${name}`});
}

src/routes/about/+page.svelte

svelte
<main><h1>เกี่ยวกับตัวอย่าง</h1><p>ฟอร์มส่งชื่อให้ API ฝั่ง server แล้วนำ JSON มาแสดง</p><a href="/">กลับหน้าทดลอง</a></main>
Terminal
npm run check
npm run build
npm run preview -- --host 127.0.0.1

preview ใช้ตรวจ build ในเครื่อง การ deploy จริงต้องเลือก adapter ให้เหมาะกับปลายทาง ตัวอย่างนี้ยังใช้ adapter-auto; build ผ่านในเครื่องไม่ได้ยืนยันว่ามี production adapter ของทุก host

CSS เดียวกันสำหรับอ่านตัวอย่าง

วาง CSS นี้ใน app/globals.css ของ Next.js, app/assets/main.css ของ Nuxt และ src/app.css ของ SvelteKit ตาม import ที่แสดงข้างต้น สร้างโฟลเดอร์ที่ยังไม่มี

css
* {box-sizing: border-box} body {margin: 0; font-family: system-ui, sans-serif; color: #1d1d1f; background: #f5f5f7} main {max-width: 680px; margin: 10vh auto; padding: 28px; background: white; border-radius: 20px} h1 {font-size: clamp(26px,5vw,40px); margin-top: 0} form {display: flex; gap: 12px; flex-wrap: wrap; align-items: end} label {display: grid; gap: 8px; flex: 1; min-width: 160px} input,button {font: inherit; padding: 12px; border: 1px solid #aaa; border-radius: 9px; min-height: 44px; max-width: 100%} button {background: #0071e3; color: white; border-color: #0071e3; cursor: pointer} [role=status] {padding: 18px 0; overflow-wrap: anywhere} a {color: #0066cc} @media(max-width: 700px) {main {margin: 24px 12px; padding: 24px}}

ลองใช้และตรวจขอบเขต server

เปิด URL ของ server ที่ terminal แสดง กด “เรียก API” ด้วยชื่อ ReefSight ควรได้ “สวัสดี ReefSight” ลองชื่อไทย ลบชื่อให้ว่าง และเปิดหน้า “เกี่ยวกับตัวอย่าง” แล้วกดกลับได้ UI เรียก fetch หลังส่งฟอร์ม ไม่ได้โหลดคำทักทายล่วงหน้าบน server

Terminal
curl -i 'http://127.0.0.1:3000/api/hello?name=ReefSight'
curl -i 'http://127.0.0.1:3000/api/hello?name='

สำหรับ SvelteKit ใช้ port ที่ preview แสดง ตัวอย่าง API ตอบ 200 เมื่อชื่อถูกต้อง และ 400 เมื่อว่างหรือเกิน 40 Unicode code points; ถ้าไม่ส่ง name จะใช้ “โลก” อย่าส่ง secret ผ่าน query ในงานจริง เพราะ URL อาจอยู่ในประวัติและ log

Reefsight ตรวจ build ของทั้งสามโปรเจกต์ และ check ของ SvelteKit จากนั้นทดลองฟอร์มชื่อปกติ/ไทย/ว่าง พร้อม routing ไปกลับใน Chromium ที่ 390 และ 1280px รวมถึงเรียก API ตรงสำหรับค่าปริยาย ชื่อถูกต้อง ว่าง และยาวเกินเกณฑ์ ผลตรวจครอบคลุมโค้ดตัวอย่าง ไม่ใช่การทดสอบโหลดสูง ฐานข้อมูล authentication หรือทุก browser/OS

รุ่นที่ติดตั้งจริงคือ Next.js 16.4.2 / React 19.3.0, Nuxt 4.6.2 / Vue 3.5.44 และ SvelteKit 3.1.0 / Svelte 5.57.2 แยกจากรุ่นตัวสร้างที่ระบุในคำสั่ง

หน้าเริ่มต้นมี HTML ของหัวข้อก่อน JavaScript ทำงาน ส่วนฟอร์มต้องใช้ JavaScript จึงโต้ตอบได้ การวาง route handler ในโปรเจกต์เดียวกับ UI ไม่ได้แปลว่า business logic, session และสิทธิ์เข้าถึงถูกออกแบบให้แล้ว

อ่าน Next.js, Nuxt, SvelteKit และ คู่มือ backend Python ต่อเมื่อจะขยาย API แยกบริการ รุ่นและข้อมูลต้นทางตรวจวันที่ 17 ตุลาคม 2026

คุยกับทีม Reefsight