Home > Blog > tech

Next.js คืออะไร? สอนสร้าง Full-Stack Web App ด้วย Next.js 14 App Router 2026

nextjs fullstack react guide
Next.js Full-Stack React Guide 2026
2026-04-08 | tech | 3500 words

ในปี 2026 Next.js กลายเป็น Framework อันดับหนึ่งสำหรับการพัฒนา Web Application ด้วย React ไม่ว่าจะเป็นเว็บไซต์ขนาดเล็กหรือระบบ Enterprise ขนาดใหญ่ Next.js ตอบโจทย์ได้หมด ตั้งแต่ Static Site ไปจนถึง Full-Stack Application ที่มี API, Authentication และ Database ครบครัน

บทความนี้จะพาคุณเรียนรู้ Next.js 14 ตั้งแต่พื้นฐานจนถึงการสร้าง Full-Stack Application จริง ครอบคลุมทุกฟีเจอร์สำคัญรวมถึง App Router, Server Components, Data Fetching, API Routes และ Deployment อย่างละเอียดในภาษาไทย

Next.js คืออะไร?

Next.js คือ React Framework ที่พัฒนาโดย Vercel ออกแบบมาเพื่อให้การสร้าง Web Application ด้วย React ง่ายขึ้นและมีประสิทธิภาพมากขึ้น โดยเพิ่มฟีเจอร์สำคัญที่ React เพียวไม่มีให้ เช่น Server-Side Rendering (SSR), Static Site Generation (SSG), File-based Routing, API Routes และ Built-in Optimization ต่างๆ

Next.js แก้ปัญหาหลายอย่างที่ Developer เจอเมื่อใช้ React แบบ Client-Side เพียวอย่างเดียว:

Next.js vs Create React App (CRA)

คุณสมบัติNext.jsCreate React App
RenderingSSR, SSG, ISR, CSRCSR เท่านั้น
RoutingFile-based (built-in)ต้องติดตั้ง React Router
SEOดีเยี่ยม (Server Rendering)แย่ (Client-side only)
API Routesมี Built-inไม่มี ต้องแยก Backend
Image OptimizationBuilt-in next/imageไม่มี
Bundle Sizeเล็กกว่า (Code Splitting auto)ใหญ่กว่า
DeploymentVercel, Self-hosted, DockerStatic hosting
สถานะปี 2026Active พัฒนาต่อเนื่องDeprecated ไม่แนะนำ
คำแนะนำ: ในปี 2026 Create React App ถูก Deprecate อย่างเป็นทางการแล้ว React Team แนะนำให้ใช้ Next.js หรือ Framework อื่นแทน ถ้าเริ่มโปรเจกต์ใหม่ ให้ใช้ Next.js เป็นตัวเลือกแรก

เริ่มต้นกับ Next.js 14

ติดตั้ง Next.js

# สร้างโปรเจกต์ใหม่ด้วย create-next-app
npx create-next-app@latest my-app
# ตัวเลือกที่แนะนำ:
# TypeScript: Yes
# ESLint: Yes
# Tailwind CSS: Yes
# src/ directory: Yes
# App Router: Yes
# Import alias: @/*

cd my-app
npm run dev    # เปิด Development Server ที่ http://localhost:3000

โครงสร้างโปรเจกต์

my-app/
├── src/
│   ├── app/                # App Router (หัวใจของ Next.js 14)
│   │   ├── layout.tsx      # Root Layout (ครอบทุกหน้า)
│   │   ├── page.tsx        # Home page (/)
│   │   ├── globals.css     # Global styles
│   │   ├── about/
│   │   │   └── page.tsx    # /about
│   │   ├── blog/
│   │   │   ├── page.tsx    # /blog
│   │   │   └── [slug]/
│   │   │       └── page.tsx # /blog/:slug (Dynamic route)
│   │   └── api/
│   │       └── hello/
│   │           └── route.ts # API endpoint: /api/hello
│   ├── components/         # Shared components
│   └── lib/                # Utility functions
├── public/                 # Static assets
├── next.config.js          # Next.js configuration
├── tailwind.config.ts      # Tailwind CSS config
├── tsconfig.json           # TypeScript config
└── package.json

App Router vs Pages Router

Next.js มี 2 ระบบ Routing คือ App Router (ใหม่ แนะนำ) และ Pages Router (เก่า ยังใช้ได้) ในบทความนี้จะเน้น App Router ซึ่งเป็น Default ตั้งแต่ Next.js 13

คุณสมบัติApp Router (app/)Pages Router (pages/)
React ComponentsServer Components (default)Client Components (default)
Data Fetchingasync/await ใน ComponentgetServerSideProps, getStaticProps
LayoutsNested Layouts (layout.tsx)_app.tsx, _document.tsx
Loading UIloading.tsx (Suspense)ต้องทำเอง
Error Handlingerror.tsx (Error Boundary)ต้องทำเอง
API Routesroute.ts (Route Handlers)pages/api/*.ts
Streamingรองรับไม่รองรับ

File-based Routing ระบบเส้นทางอัตโนมัติ

Next.js App Router ใช้ระบบ File-based Routing ที่สร้าง URL จากโครงสร้างโฟลเดอร์อัตโนมัติ แค่สร้างไฟล์ page.tsx ในโฟลเดอร์ ก็จะได้ Route ทันที

Static Routes

// src/app/page.tsx → /
export default function HomePage() {
  return <h1>หน้าแรก</h1>
}

// src/app/about/page.tsx → /about
export default function AboutPage() {
  return <h1>เกี่ยวกับเรา</h1>
}

// src/app/contact/page.tsx → /contact
export default function ContactPage() {
  return <h1>ติดต่อเรา</h1>
}

Dynamic Routes

// src/app/blog/[slug]/page.tsx → /blog/:slug
// เช่น /blog/nextjs-guide, /blog/react-tutorial

interface PageProps {
  params: { slug: string }
}

export default async function BlogPost({ params }: PageProps) {
  const post = await getPostBySlug(params.slug)
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  )
}

// สร้าง Static Paths สำหรับ SSG
export async function generateStaticParams() {
  const posts = await getAllPosts()
  return posts.map((post) => ({ slug: post.slug }))
}

Catch-all Routes และ Route Groups

// [...slug]/page.tsx → จับทุก path (/docs/a, /docs/a/b, /docs/a/b/c)
// [[...slug]]/page.tsx → Optional catch-all (/ ก็ match)

// Route Groups — จัดกลุ่มโดยไม่สร้าง URL segment
// src/app/(marketing)/about/page.tsx → /about
// src/app/(marketing)/pricing/page.tsx → /pricing
// src/app/(dashboard)/settings/page.tsx → /settings

// Parallel Routes — แสดงหลาย page พร้อมกัน
// src/app/@modal/login/page.tsx
// src/app/@sidebar/page.tsx

Server Components vs Client Components

นี่คือ Concept ที่สำคัญที่สุดใน Next.js 14 App Router ทำความเข้าใจให้ดีเพราะจะกระทบการออกแบบ Component ทั้งหมด

Server Components (Default)

ทุก Component ใน App Router เป็น Server Component โดย Default หมายความว่า Component จะ Render บน Server เท่านั้น ไม่ส่ง JavaScript ไปที่ Browser

// Server Component (default — ไม่ต้องเขียน 'use server')
// สามารถ:
// - ดึงข้อมูลจาก Database โดยตรง
// - อ่านไฟล์บน Server
// - ใช้ Secret Keys / Environment Variables
// - ลด Bundle Size (ไม่ส่ง JS ไป Client)

// src/app/users/page.tsx
import { db } from '@/lib/db'

export default async function UsersPage() {
  // Query database โดยตรง — ไม่ต้องสร้าง API!
  const users = await db.user.findMany()

  return (
    <div>
      <h1>Users ({users.length})</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  )
}

Client Components

ใช้ "use client" directive เมื่อต้องการ Interactivity

// Client Component — ต้องเขียน 'use client' บรรทัดแรก
// ใช้เมื่อต้องการ:
// - useState, useEffect, useRef
// - Event handlers (onClick, onChange, etc.)
// - Browser APIs (localStorage, window, etc.)
// - Third-party libs ที่ต้องการ Browser

'use client'

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        เพิ่ม
      </button>
    </div>
  )
}
หลักการสำคัญ: ใช้ Server Components เป็นค่าเริ่มต้น แล้วค่อยเปลี่ยนเป็น Client Component เฉพาะ Component ที่ต้องการ Interactivity เท่านั้น เพื่อให้ JavaScript Bundle เล็กที่สุด

Layouts และ Templates

Layout เป็น UI ที่ครอบหลายหน้า เช่น Navbar, Sidebar, Footer ใน Next.js ใช้ไฟล์ layout.tsx

// src/app/layout.tsx — Root Layout (บังคับ)
import type { Metadata } from 'next'
import './globals.css'

export const metadata: Metadata = {
  title: { default: 'My App', template: '%s | My App' },
  description: 'Full-Stack Next.js Application',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="th">
      <body>
        <nav>Navbar ที่แสดงทุกหน้า</nav>
        <main>{children}</main>
        <footer>Footer ที่แสดงทุกหน้า</footer>
      </body>
    </html>
  )
}

// src/app/dashboard/layout.tsx — Nested Layout
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="flex">
      <aside>Sidebar เฉพาะหน้า Dashboard</aside>
      <div className="flex-1">{children}</div>
    </div>
  )
}

ข้อสำคัญ: Layout จะไม่ Re-render เมื่อเปลี่ยนหน้าภายใน Segment เดียวกัน ทำให้ Navigation เร็วมาก ถ้าต้องการ Re-render ทุกครั้ง ใช้ template.tsx แทน

Loading UI และ Error Handling

// src/app/blog/loading.tsx — แสดงขณะ Loading (ใช้ React Suspense)
export default function Loading() {
  return (
    <div className="animate-pulse">
      <div className="h-8 bg-gray-200 rounded w-3/4 mb-4"></div>
      <div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
      <div className="h-4 bg-gray-200 rounded w-5/6"></div>
    </div>
  )
}

// src/app/blog/error.tsx — แสดงเมื่อเกิด Error (Error Boundary)
'use client' // Error components ต้องเป็น Client Component

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>เกิดข้อผิดพลาด!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>ลองใหม่</button>
    </div>
  )
}

// src/app/not-found.tsx — หน้า 404 Custom
export default function NotFound() {
  return (
    <div>
      <h1>404 — ไม่พบหน้านี้</h1>
      <p>ขออภัย หน้าที่คุณต้องการไม่มีอยู่ในระบบ</p>
    </div>
  )
}

Data Fetching ใน Next.js 14

การดึงข้อมูลใน App Router ง่ายมาก ใช้ async/await ใน Server Component ได้เลย

Server-side Data Fetching

// Fetch data ใน Server Component
export default async function PostsPage() {
  // fetch() ถูก extend ด้วย Next.js ให้มี caching
  const res = await fetch('https://api.example.com/posts', {
    // Cache options:
    cache: 'force-cache',     // SSG (default) — cache ตลอด
    // cache: 'no-store',     // SSR — ดึงใหม่ทุก request
    // next: { revalidate: 60 } // ISR — cache 60 วินาที
  })
  const posts = await res.json()

  return (
    <ul>
      {posts.map((post: any) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Server Actions — Full-Stack ไม่ต้องสร้าง API

// src/app/actions.ts
'use server'

import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  await db.post.create({
    data: { title, content }
  })

  revalidatePath('/blog')
}

export async function deletePost(id: string) {
  await db.post.delete({ where: { id } })
  revalidatePath('/blog')
}

// ใช้ใน Component
// src/app/blog/new/page.tsx
import { createPost } from '@/app/actions'

export default function NewPostPage() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="หัวข้อ" required />
      <textarea name="content" placeholder="เนื้อหา" required />
      <button type="submit">สร้างบทความ</button>
    </form>
  )
}
Server Actions: คือฟีเจอร์ที่ทำให้ Next.js เป็น Full-Stack จริงๆ คุณสามารถเขียน Logic ที่ทำงานบน Server (เช่น CRUD Database) แล้วเรียกจาก Client ได้เลย โดยไม่ต้องสร้าง API endpoint แยก ลด Boilerplate ได้มาก

API Routes (Route Handlers)

สำหรับกรณีที่ต้องการ REST API แบบดั้งเดิม Next.js มี Route Handlers ให้ใช้

// src/app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'

// GET /api/posts
export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams
  const page = parseInt(searchParams.get('page') || '1')
  const limit = 10

  const posts = await db.post.findMany({
    skip: (page - 1) * limit,
    take: limit,
    orderBy: { createdAt: 'desc' }
  })

  return NextResponse.json({ data: posts, page })
}

// POST /api/posts
export async function POST(request: NextRequest) {
  const body = await request.json()
  const post = await db.post.create({
    data: { title: body.title, content: body.content }
  })
  return NextResponse.json(post, { status: 201 })
}

// src/app/api/posts/[id]/route.ts
// GET /api/posts/:id
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const post = await db.post.findUnique({ where: { id: params.id } })
  if (!post) return NextResponse.json({ error: 'Not Found' }, { status: 404 })
  return NextResponse.json(post)
}

// PUT /api/posts/:id
export async function PUT(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const body = await request.json()
  const post = await db.post.update({
    where: { id: params.id },
    data: body
  })
  return NextResponse.json(post)
}

// DELETE /api/posts/:id
export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  await db.post.delete({ where: { id: params.id } })
  return NextResponse.json({ success: true })
}

Middleware — ดัก Request ก่อนถึงหน้า

// src/middleware.ts (ต้องอยู่ที่ root ของ src/)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // ตรวจ Authentication
  const token = request.cookies.get('session')?.value

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    // Redirect ไปหน้า Login
    return NextResponse.redirect(new URL('/login', request.url))
  }

  // เพิ่ม Custom Header
  const response = NextResponse.next()
  response.headers.set('x-pathname', request.nextUrl.pathname)
  return response
}

// กำหนดว่า Middleware ทำงานกับ Path ไหนบ้าง
export const config = {
  matcher: ['/dashboard/:path*', '/api/admin/:path*']
}

Metadata และ SEO

// Static Metadata — กำหนดตายตัว
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'หน้าแรก | My App',
  description: 'เว็บแอป Next.js สำหรับ...',
  keywords: ['next.js', 'react', 'web development'],
  openGraph: {
    title: 'My App',
    description: 'เว็บแอป Next.js',
    url: 'https://myapp.com',
    siteName: 'My App',
    images: [{ url: 'https://myapp.com/og.jpg', width: 1200, height: 630 }],
    type: 'website',
  },
  twitter: { card: 'summary_large_image' },
  robots: { index: true, follow: true },
}

// Dynamic Metadata — ดึงจาก Database
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
  const post = await getPostBySlug(params.slug)
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  }
}

Authentication Patterns

Next.js สามารถทำ Authentication ได้หลายวิธี วิธีที่นิยมที่สุดคือใช้ NextAuth.js (Auth.js)

// ติดตั้ง
// npm install next-auth@beta

// src/auth.ts
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    GitHub({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    Google({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
    Credentials({
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        // ตรวจสอบ credentials กับ database
        const user = await db.user.findUnique({
          where: { email: credentials.email as string }
        })
        if (!user) return null
        const valid = await bcrypt.compare(
          credentials.password as string, user.hashedPassword
        )
        return valid ? user : null
      },
    }),
  ],
  pages: {
    signIn: '/login',
  },
})

// src/app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlers

// ใช้ใน Server Component
import { auth } from '@/auth'
export default async function DashboardPage() {
  const session = await auth()
  if (!session) redirect('/login')
  return <h1>Welcome, {session.user?.name}</h1>
}

Database Integration — Prisma ORM

Prisma เป็น ORM ยอดนิยมสำหรับ Next.js ทำให้ทำงานกับ Database ง่ายและ Type-safe

# ติดตั้ง Prisma
npm install prisma @prisma/client
npx prisma init
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"  // หรือ mysql, sqlite
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
# Migration
npx prisma migrate dev --name init
npx prisma generate    # สร้าง Client
// src/lib/db.ts — Singleton Prisma Client
import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }

export const db = globalForPrisma.prisma || new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db

Drizzle ORM — ทางเลือกที่เร็วกว่า

// npm install drizzle-orm pg
// npm install -D drizzle-kit

// src/db/schema.ts
import { pgTable, text, timestamp, boolean } from 'drizzle-orm/pg-core'

export const posts = pgTable('posts', {
  id: text('id').primaryKey(),
  title: text('title').notNull(),
  content: text('content'),
  published: boolean('published').default(false),
  createdAt: timestamp('created_at').defaultNow(),
})

// ใช้ Drizzle
import { db } from '@/db'
import { posts } from '@/db/schema'
import { eq } from 'drizzle-orm'

const allPosts = await db.select().from(posts).where(eq(posts.published, true))
const newPost = await db.insert(posts).values({ id: 'xxx', title: 'Hello' }).returning()

ISR และ On-Demand Revalidation

Incremental Static Regeneration (ISR) ช่วยให้หน้า Static สามารถอัพเดทได้โดยไม่ต้อง Re-build ทั้งเว็บ

// Time-based Revalidation — อัพเดททุก N วินาที
export default async function BlogPage() {
  const posts = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 } // Re-fetch ทุก 1 ชั่วโมง
  })
  // ...
}

// หรือกำหนดระดับ Page
export const revalidate = 3600 // Revalidate ทุก 1 ชั่วโมง

// On-Demand Revalidation — อัพเดทเมื่อเรียก
// src/app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache'
import { NextRequest } from 'next/server'

export async function POST(request: NextRequest) {
  const secret = request.headers.get('x-revalidate-secret')
  if (secret !== process.env.REVALIDATE_SECRET) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { path, tag } = await request.json()

  if (tag) {
    revalidateTag(tag)
  } else if (path) {
    revalidatePath(path)
  }

  return Response.json({ revalidated: true })
}

Image Optimization ด้วย next/image

import Image from 'next/image'

// next/image ทำสิ่งเหล่านี้อัตโนมัติ:
// - Lazy loading (โหลดเมื่อเลื่อนถึง)
// - Resize ตามขนาดหน้าจอ
// - แปลงเป็น WebP/AVIF
// - ป้องกัน Layout Shift (CLS)

export default function Gallery() {
  return (
    <div>
      {/* รูปจาก Local */}
      <Image
        src="/images/hero.jpg"
        alt="Hero Image"
        width={1200}
        height={600}
        priority  // โหลดทันที (สำหรับ above-the-fold)
      />

      {/* รูปจาก External URL */}
      <Image
        src="https://cdn.example.com/photo.jpg"
        alt="External Photo"
        width={400}
        height={300}
        sizes="(max-width: 768px) 100vw, 400px"
      />

      {/* รูป Fill container */}
      <div style={{ position: 'relative', width: '100%', height: '400px' }}>
        <Image
          src="/images/cover.jpg"
          alt="Cover"
          fill
          style={{ objectFit: 'cover' }}
        />
      </div>
    </div>
  )
}
// next.config.js — อนุญาต External Images
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
      },
      {
        protocol: 'https',
        hostname: '**.amazonaws.com',
      },
    ],
  },
}
module.exports = nextConfig

Internationalization (i18n)

// Next.js 14 App Router ใช้ Sub-path Routing สำหรับ i18n
// /th/about, /en/about

// src/app/[lang]/layout.tsx
export default function LangLayout({
  children, params
}: {
  children: React.ReactNode
  params: { lang: string }
}) {
  return <html lang={params.lang}><body>{children}</body></html>
}

// src/lib/dictionary.ts
const dictionaries = {
  th: () => import('@/dictionaries/th.json').then(m => m.default),
  en: () => import('@/dictionaries/en.json').then(m => m.default),
}

export const getDictionary = async (lang: 'th' | 'en') =>
  dictionaries[lang]()

// ใช้ใน Page
export default async function AboutPage({ params }: { params: { lang: string } }) {
  const dict = await getDictionary(params.lang as 'th' | 'en')
  return <h1>{dict.about.title}</h1>
}

// Middleware สำหรับ Language Detection
// src/middleware.ts
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'

const locales = ['th', 'en']
const defaultLocale = 'th'

function getLocale(request: NextRequest) {
  const headers = { 'accept-language': request.headers.get('accept-language') || '' }
  const languages = new Negotiator({ headers }).languages()
  return match(languages, locales, defaultLocale)
}

Next.js vs Remix vs Astro

คุณสมบัติNext.jsRemixAstro
ประเภทFull-Stack ReactFull-Stack ReactContent-focused MPA
RenderingSSR, SSG, ISR, CSRSSR เป็นหลักSSG เป็นหลัก
Data FetchingServer Components, Server ActionsLoaders, ActionsAstro.props fetch
Bundle Sizeปานกลางปานกลางเล็กมาก (zero JS default)
UI LibraryReact เท่านั้นReact เท่านั้นReact, Vue, Svelte, etc.
เหมาะกับApp ทุกประเภทApp ที่เน้น Form/DataBlog, Docs, Marketing
HostingVercel, Self-hostทุก Node.js hostingStatic hosting, Vercel
Communityใหญ่ที่สุดกำลังเติบโตเติบโตเร็ว
เลือกอะไรดี? ถ้าสร้าง Web Application ที่มี User Interaction มาก (Dashboard, SaaS, E-commerce) ให้ใช้ Next.js ถ้าสร้าง Content Site (Blog, Docs) ที่ต้องการ Performance สูงสุด ลองพิจารณา Astro ส่วน Remix เหมาะกับ App ที่เน้น Form มากๆ และต้องการ Progressive Enhancement

Deployment และ Production

Deploy บน Vercel (แนะนำ)

# ง่ายที่สุด — Push ไป GitHub แล้ว Connect กับ Vercel
# 1. สร้าง Account ที่ vercel.com
# 2. Import GitHub Repository
# 3. Vercel จะ Deploy อัตโนมัติทุกครั้งที่ Push

# หรือใช้ CLI
npm install -g vercel
vercel          # Deploy to preview
vercel --prod   # Deploy to production

Self-hosted ด้วย Docker

# Dockerfile
FROM node:20-alpine AS base

# Dependencies
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

# Build
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx prisma generate
RUN npm run build

# Production
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
// next.config.js — เปิด Standalone mode สำหรับ Docker
const nextConfig = {
  output: 'standalone',
}

Static Export

// next.config.js — Export เป็น Static HTML
const nextConfig = {
  output: 'export',
  // หมายเหตุ: ไม่สามารถใช้ Server-side features
  // (API Routes, SSR, ISR, Middleware) ได้
}

Performance Optimization

เทคนิคที่ควรใช้

// 1. Dynamic Import — โหลด Component เมื่อต้องการ
import dynamic from 'next/dynamic'

const HeavyChart = dynamic(() => import('@/components/Chart'), {
  loading: () => <p>Loading chart...</p>,
  ssr: false, // ไม่ Render บน Server
})

// 2. Parallel Data Fetching — ดึงข้อมูลพร้อมกัน
async function DashboardPage() {
  // แย่: Sequential (ช้า)
  // const users = await getUsers()
  // const posts = await getPosts()

  // ดี: Parallel (เร็ว)
  const [users, posts] = await Promise.all([
    getUsers(),
    getPosts(),
  ])

  return <Dashboard users={users} posts={posts} />
}

// 3. Route Segment Config
export const dynamic = 'force-static' // หรือ 'force-dynamic', 'auto'
export const revalidate = 3600
export const fetchCache = 'force-cache'

// 4. Prefetch Links
import Link from 'next/link'
// Next.js จะ Prefetch หน้าที่ Link ชี้ไปอัตโนมัติ
<Link href="/about" prefetch={true}>About</Link>

// 5. Font Optimization
import { Noto_Sans_Thai } from 'next/font/google'
const notoSansThai = Noto_Sans_Thai({
  subsets: ['thai'],
  display: 'swap',
  weight: ['300', '400', '500', '600', '700'],
})
// ใช้: <body className={notoSansThai.className}>

Bundle Analyzer

# ติดตั้ง
npm install @next/bundle-analyzer

// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer(nextConfig)

# วิเคราะห์ Bundle
ANALYZE=true npm run build

Testing ใน Next.js

// ติดตั้ง Testing Tools
// npm install -D jest @testing-library/react @testing-library/jest-dom
// npm install -D @types/jest jest-environment-jsdom

// jest.config.ts
import type { Config } from 'jest'
import nextJest from 'next/jest'

const createJestConfig = nextJest({ dir: './' })
const config: Config = {
  coverageProvider: 'v8',
  testEnvironment: 'jsdom',
  setupFilesAfterSetup: ['<rootDir>/jest.setup.ts'],
}
export default createJestConfig(config)

// __tests__/components/Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import Counter from '@/components/Counter'

describe('Counter', () => {
  it('should increment count', () => {
    render(<Counter />)
    const button = screen.getByText('เพิ่ม')
    fireEvent.click(button)
    expect(screen.getByText('Count: 1')).toBeInTheDocument()
  })
})

// E2E Testing ด้วย Playwright
// npm install -D @playwright/test
// npx playwright test

Project จริง: Blog Application

มาสร้าง Blog Application จริงด้วย Next.js 14 + Prisma + Tailwind CSS กัน

// 1. Schema — prisma/schema.prisma
model Post {
  id        String   @id @default(cuid())
  title     String
  slug      String   @unique
  content   String
  excerpt   String?
  published Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

// 2. Server Actions — src/app/actions/posts.ts
'use server'
import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string
  const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-')

  await db.post.create({
    data: { title, slug, content, excerpt: content.slice(0, 160) }
  })
  revalidatePath('/blog')
  redirect('/blog')
}

// 3. Blog List — src/app/blog/page.tsx
export default async function BlogPage() {
  const posts = await db.post.findMany({
    where: { published: true },
    orderBy: { createdAt: 'desc' },
  })
  return (
    <div className="grid gap-6">
      {posts.map(post => (
        <article key={post.id} className="border p-4 rounded-lg">
          <Link href={`/blog/${post.slug}`}>
            <h2 className="text-xl font-bold">{post.title}</h2>
          </Link>
          <p className="text-gray-600">{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}

// 4. Blog Detail — src/app/blog/[slug]/page.tsx
export default async function BlogDetailPage({ params }: { params: { slug: string } }) {
  const post = await db.post.findUnique({ where: { slug: params.slug } })
  if (!post) notFound()
  return (
    <article>
      <h1 className="text-3xl font-bold">{post.title}</h1>
      <time className="text-gray-500">{post.createdAt.toLocaleDateString('th-TH')}</time>
      <div className="prose mt-6">{post.content}</div>
    </article>
  )
}

Best Practices สำหรับ Next.js 2026

  1. ใช้ Server Components เป็นหลัก: ลด JavaScript Bundle ได้มาก ใช้ Client Component เฉพาะที่จำเป็น
  2. ใช้ Server Actions แทน API Routes: สำหรับ Mutation (Create, Update, Delete) ลด Boilerplate
  3. จัดโครงสร้าง Feature-based: แยกโฟลเดอร์ตาม Feature ไม่ใช่ตาม Type (components, hooks, utils)
  4. ใช้ TypeScript: Type Safety ช่วยลด Bug และทำให้ Refactor ง่าย
  5. Optimize Images ด้วย next/image: ไม่ใช้ img tag ตรงๆ
  6. ใช้ Parallel Data Fetching: Promise.all() สำหรับ Query ที่ไม่ขึ้นกัน
  7. ตั้ง Revalidation ให้เหมาะสม: ไม่ต้อง SSR ทุกอย่าง ใช้ ISR ถ้าข้อมูลไม่เปลี่ยนบ่อย
  8. ใช้ Middleware สำหรับ Auth Guard: ไม่ต้องตรวจ Auth ทุก Page ซ้ำๆ
  9. Environment Variables: ใช้ NEXT_PUBLIC_ prefix เฉพาะตัวแปรที่ Client ต้องใช้
  10. Error Handling: สร้าง error.tsx และ not-found.tsx ทุก Route Segment ที่สำคัญ

สรุป

Next.js 14 เป็น Framework ที่ทรงพลังสำหรับการสร้าง Full-Stack Web Application ด้วย React ด้วย App Router, Server Components และ Server Actions คุณสามารถสร้างแอปพลิเคชันที่มีทั้ง Frontend และ Backend ใน Project เดียวกัน โดยไม่ต้องแยก API Server ออกไป

ฟีเจอร์อย่าง ISR ทำให้เว็บเร็วเหมือน Static Site แต่อัพเดทข้อมูลได้เหมือน Dynamic Site ขณะที่ Server Components ช่วยลด JavaScript ที่ส่งไป Browser ให้น้อยที่สุด ทำให้ User Experience ดีเยี่ยม

ไม่ว่าคุณจะเป็น Frontend Developer ที่อยากขยายไป Full-Stack หรือ Backend Developer ที่อยากสร้าง UI สวยๆ Next.js คือเครื่องมือที่ตอบโจทย์ทั้งสองฝั่ง เริ่มต้นง่ายด้วย npx create-next-app@latest แล้วลองสร้างโปรเจกต์แรกของคุณวันนี้


Back to Blog | iCafe Forex | SiamLanCard | Siam2R