ข้ามไปยังเนื้อหา

การตั้งค่าโปรเจกต์และ Toolchain

การเริ่มทำงานกับ Next.js ให้ลื่นส่วนใหญ่คือ command เดียวสำหรับ scaffold, command เดียวสำหรับรัน และรู้ว่าไฟล์ไหนที่ framework มองเป็น convention ไม่ใช่โค้ดธรรมดา

สร้างโปรเจกต์และสตาร์ท dev server ด้วยสอง command —

Terminal window
npx create-next-app@latest my-app
cd my-app
npm run dev

create-next-app จะถามว่าจะใช้ recommended defaults ไหม — TypeScript, ESLint, Tailwind CSS, App Router และไฟล์ AGENTS.md — หรือจะ customize เอง การเลือก defaults คือทางที่เร็วสุดและตรงกับทุกอย่างในคอร์สนี้

script ใน package.json คือชุดที่คุณจะรันทุกวัน —

{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}

next dev รัน local development server พร้อม hot reloading ส่วน next build สร้าง output สำหรับ production ที่ optimize แล้ว และ next start เสิร์ฟ build นั้น

ตั้งแต่ Next.js 16 Turbopack เป็น bundler เริ่มต้นสำหรับทั้ง next dev และ next build — คุณไม่ต้อง opt in เพราะ Turbopack รันอยู่แล้ว เป็นทายาทของ webpack ที่เขียนด้วย Rust ออกแบบมาเพื่อ cold start ที่เร็วและ update แบบ incremental

คุณ config Turbopack ได้ตอนที่ต้องการ ผ่าน key turbopack ระดับบนสุดในไฟล์ config —

next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
turbopack: {
// Turbopack-specific options go here
},
};
export default nextConfig;

Next.js รองรับ TypeScript แบบ first-class ไฟล์ config เองก็เป็น next.config.ts ที่ type ด้วย NextConfig และ framework จะ generate next-env.d.ts ที่คุณไม่ควรแก้ TypeScript เปิดใช้งานโดย default เมื่อใช้ setup ที่แนะนำ

นิสัยที่ทำให้ Next.js เข้าใจง่ายขึ้นคือการหัด อ่าน convention ของตัวเอง ชื่อจำนวนหนึ่งถูกจองไว้และมีความหมายทุกที่ใต้ app/ —

  • ชื่อไฟล์อย่าง page, layout, loading, error และ route คือพฤติกรรม ไม่ใช่ module ทั่ว ๆ ไป
  • โฟลเดอร์คือ route segment ส่วนการครอบชื่อโฟลเดอร์ด้วยวงเล็บเหลี่ยม อย่าง [slug] ทำให้ segment นั้นเป็น dynamic
  • next.config.ts คือที่เดียวสำหรับเปลี่ยนพฤติกรรมระดับ framework

พอคุณมอง tree แล้วเดา URL และจุดที่โค้ดรันได้ framework ก็เลิกรู้สึกเหมือนเวทมนตร์ —

my-app/
app/
layout.tsx
page.tsx
public/ # static assets served as-is
next.config.ts # framework configuration
tsconfig.json # TypeScript configuration

ขั้นตอน customize ของ create-next-app ถาม linter ตรง ๆ — “Which linter would you like to use? ESLint / Biome / None” ESLint คือตัวเลือกดั้งเดิม มี rule เฉพาะ Next.js จาก @next/eslint-plugin-next; Biome คือ linter+formatter ตัวเดียวจบที่เร็ว รองรับ Next.js/React มาในตัว; None ข้าม linting ไปเลย เพิ่มทีหลังได้ ตั้งแต่ Next.js 16 ทั้ง next lint และ option eslint ในไฟล์ config ถูกถอดออกทั้งคู่ — ESLint ต้องต่อสายเองด้วยการติดตั้ง eslint + eslint-config-next แล้วเขียน flat config (eslint.config.mjs) เอง รันผ่าน ESLint CLI ตรง ๆ —

eslint.config.mjs
import { defineConfig, globalIgnores } from 'eslint/config'
import nextVitals from 'eslint-config-next/core-web-vitals'
const eslintConfig = defineConfig([
...nextVitals,
globalIgnores(['.next/**', 'out/**', 'build/**', 'next-env.d.ts']),
])
export default eslintConfig
Terminal window
npx eslint .

React Compiler (reactCompiler: true ใน next.config.ts) memoize component ให้อัตโนมัติ ลดการเขียน useMemo/useCallback เองส่วนใหญ่ ใน Next.js 16.3 มันไม่เปิดเองลอย ๆ ได้ — มันรันผ่าน Babel plugin ที่ Next.js ไม่ได้ bundle มาให้ —

// next.config.ts — variant reactCompiler
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
reactCompiler: true,
};
export default nextConfig;

เปิดตัวนี้โดยไม่ติดตั้ง plugin ทำให้ build fail จริง พิสูจน์แล้วใน probe —

Error: Turbopack build failed with 1 error:
./next.config.ts
Error: Failed to resolve package babel-plugin-react-compiler while attempting to resolve React Compiler
React compiler is enabled in next.config.ts. We attempted to resolve React Compiler relative to the next package. Is babel-plugin-react-compiler installed in your node_modules directory?

การติดตั้ง dependency ที่ขาดไปคือทางแก้ทั้งหมด — npm install -D babel-plugin-react-compiler แล้ว next.config.ts ตัวเดิมเป๊ะก็ build ผ่านสะอาด create-next-app เองก็มี flag --react-compiler ตรง ๆ (และ prompt ในขั้น customize) ที่ตั้งค่าให้พร้อม plugin เลย

typedRoutes: true ทำให้ <Link href> (และ push/replace/prefetch ของ next/navigation) ถูก type-check กับ route จริงของ app คุณ — string literal ที่ไม่ใช่ route จริงกลายเป็น type error ไม่ใช่ 404 ที่เจอตอน runtime —

// next.config.ts — variant typedRoutes
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
typedRoutes: true,
};
export default nextConfig;

แต่ก่อน route type จะถูก generate เป็นผลพลอยได้จาก next dev/next build เท่านั้น ตอนนี้มีคำสั่ง next typegen แยกต่างหาก (stable ตั้งแต่ v15.5) ที่ generate มันเองได้ ซึ่งสำคัญสำหรับ type-check ใน CI โดยไม่ต้อง build เต็ม —

Terminal window
npx next typegen && npx tsc --noEmit

พิสูจน์แล้วใน probe ด้วย route ที่ไม่มีจริง —

app/page.tsx
import Link from 'next/link'
export default function Home() {
return (
<div>
<Link href="/typed-a">Good link</Link>
<Link href="/nope-not-a-route">Bad link</Link>
</div>
)
}
app/page.tsx(7,13): error TS2322: Type '"/nope-not-a-route"' is not assignable to type 'UrlObject | RouteImpl<"/nope-not-a-route">'.

next typegen พิมพ์ ✓ Types generated successfully และหลังจากนั้นเท่านั้นที่ tsc --noEmit จะมี generated route type ให้ใช้จับ typo ได้

recommended defaults (และ flag เดี่ยว --agents-md) ยังเขียน AGENTS.md ไว้ที่ root โปรเจกต์ ควบคู่กับ CLAUDE.md ที่แค่ re-export มันอีกที (@AGENTS.md) เพื่อให้ Claude Code รับ rule ชุดเดียวกัน จุดประสงค์ของมันตรงไปตรงมา — training data ของคุณล้าสมัยทันทีที่ major version ใหม่ออก —

<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may
all differ from your training data. Read the relevant guide in
`node_modules/next/dist/docs/` before writing any code. Heed deprecation
notices.
This block is written and re-added by `next dev` — removing it from a diff
only re-creates the uncommitted change; committing it with your work keeps
the tree clean.
<!-- END:nextjs-agent-rules -->

จุดสุดท้ายสำคัญมากสำหรับ agent ที่ทำงานใน repo นี้เอง — next dev generate block นี้กลับมาเองทุกครั้ง การลบมันจึงไม่ใช่การแก้ที่ถาวร — มันจะกลับมาอีก

flowchart LR
  Create[create-next-app] --> Tree[app/ structure]
  Tree --> Dev[next dev]
  Dev --> Turbo[Turbopack bundles]
  Turbo --> Browser[Live app in the browser]
  Config[next.config.ts] --> Turbo
จาก scaffold ไปถึง dev server ที่รันอยู่

create-next-app ไม่ใช่ “เริ่มจากโฟลเดอร์เปล่า ๆ” — มันคือการเลือกจากชุด toggle ที่ document ไว้ตายตัว (TypeScript, linter, Tailwind, src/, import alias, React Compiler, AGENTS.md) และทุก toggle มีผลจริงที่เช็คได้กับ tree ที่ generate ออกมา มองทั้ง toolchain แบบเดียวกัน — config key อย่าง reactCompiler หรือ typedRoutes ไม่ใช่ “เวทมนตร์ที่ทำงานหรือไม่ทำงาน” — มันคือ feature ที่ document ไว้ มี dependency ของตัวเอง และมี error message จริงเมื่อ dependency นั้นขาดไป

ได้อะไรประโยชน์ต้นทุน
recommended defaults ของ create-next-appTypeScript/ESLint/App Router ต่อสายถูกต้องในคำสั่งเดียวมีความเห็นในตัว — customize ทีหลังต้อง undo default บางตัว
ESLint (flat config)ecosystem อยู่ตัว ตัวอย่าง Next.js ส่วนใหญ่สมมติว่าใช้ตัวนี้ต้องเขียนและดูแล eslint.config.mjs เองเพราะ next lint หายไปแล้ว
Biomebinary เดียวเร็ว แทน ESLint+Prettierplugin ecosystem เล็กกว่า ESLint
React Compilerเขียน useMemo/useCallback เองน้อยลงdependency เพิ่ม (babel-plugin-react-compiler) และ build time เพิ่มนิดหน่อย
typedRouteslink ที่พังกลายเป็น type error ไม่ใช่ 404ต้อง next typegen (หรือ dev/build) generate type ใหม่ก่อน tsc จะเห็น
  • “reactCompiler: true แค่ใส่ config key ก็ใช้ได้เลย” มันต้องการ babel-plugin-react-compiler ติดตั้งอยู่ด้วย — ถ้าไม่มี next build จะ fail ทันทีด้วย error จริงที่บอกชื่อ ไม่ใช่ no-op เงียบ ๆ
  • “next lint ยังมีอยู่ใน Next.js 16” มันถูกถอดออกใน v16.0.0 พร้อมกับ option eslint ใน next.config.js — ตอนนี้ ESLint รันผ่าน ESLint CLI ธรรมดากับ flat config ของคุณเอง
  • “tsc --noEmit เฉย ๆ จับ typed-route ผิดพลาดได้ทุกกรณี” route type มาจาก .next/types ที่ generate โดย next dev, next build, หรือ next typegen — รัน tsc เปล่า ๆ ใน checkout ใหม่ (หรือ CI) โดยไม่มีสามอย่างนี้มาก่อนแปลว่า route type ยังไม่มี
  • “block AGENTS.md เป็นแค่คำแนะนำ ลบได้” next dev เติมมันกลับมาเอง — ลบออกจาก diff โดยไม่แก้ต้นตอแค่สร้าง uncommitted change ใหม่ตอนรัน dev ครั้งถัดไป

เวลาโค้ดที่ generate มาแตะ next.config.ts หรือ scaffold ของโปรเจกต์ เช็คสิ่งเหล่านี้ก่อนเชื่อ —

  • มันตั้ง reactCompiler: true โดยไม่พูดถึงการติดตั้ง babel-plugin-react-compiler เลยหรือเปล่า? build จะ fail ด้วย error “Failed to resolve package” ที่ชัดเจนตั้งแต่รันครั้งแรก
  • มันใช้ next lint หรือ key eslint ใน next.config.js หรือเปล่า? ทั้งคู่ถูกถอดออกใน Next.js 16 แล้ว — โค้ดปัจจุบันใช้ ESLint CLI กับ flat config eslint.config.mjs
  • มันสมมติว่า <Link href="..."> type-check กับ route จริงโดยไม่มี typedRoutes: true ใน config และไม่มีการรัน next typegen/dev/build เพื่อ generate type หรือเปล่า?
  • มันอ้างถึง getInitialProps, next.config.js แบบ module.exports, หรือ pattern อื่นที่เก่ากว่า next.config.ts แบบ typed, ESM-friendly ปัจจุบันหรือเปล่า?

🐛 หา bug ในโค้ดนี้

next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactCompiler: true,
typedRoutes: true,
}
export default nextConfig
เฉลย

config นี้เป็นแค่ครึ่งเรื่อง — reactCompiler: true ต้องการ babel-plugin-react-compiler ติดตั้งเป็น dependency จริง ๆ ด้วย และ typedRoutes: true ต้องการรัน next typegen (หรือ dev/build) ก่อนที่ tsc จะเห็น route type ที่ generate มา พิสูจน์แล้วใน probe — ด้วย config เป๊ะแบบนี้และไม่มี babel-plugin-react-compiler ใน node_modules, next build fail ทันทีด้วย Error: Failed to resolve package babel-plugin-react-compiler while attempting to resolve React Compiler แค่ config key อย่างเดียวไม่เปลี่ยนอะไรเลย — dependency (และสำหรับ typed routes คือขั้น generate type) ต้องมีอยู่จริงด้วย

command ไหนใช้ scaffold โปรเจกต์ Next.js ใหม่
bundler เริ่มต้นของ Next.js ปัจจุบันสำหรับ dev และ build คืออะไร
ไฟล์ config แบบ TypeScript ของ framework ชื่ออะไร
การครอบชื่อโฟลเดอร์ด้วยวงเล็บเหลี่ยม อย่าง [slug] ทำอะไร
เกิดอะไรขึ้นเมื่อ build โค้ดนี้โดยไม่ได้ติดตั้ง babel-plugin-react-compiler
// next.config.ts
const nextConfig: NextConfig = {
  reactCompiler: true,
}
ตั้งแต่ Next.js 16 คุณรัน ESLint ในโปรเจกต์ที่ scaffold ด้วย option ESLint ยังไง
ตั้ง typedRoutes: true แล้ว แต่ CI job รัน checkout ใหม่ผ่าน tsc --noEmit ตรง ๆ โดยไม่มี next dev, next build, หรือ next typegen มาก่อนเลย เกิดอะไรกับ <Link href="/typo"> ที่ /typo ไม่ใช่ route จริง