ไทย

สำรวจ Next.js middleware คุณสมบัติอันทรงพลังสำหรับการดักจับและแก้ไข request ที่เข้ามา เรียนรู้วิธีการนำไปใช้ในการยืนยันตัวตน, การให้สิทธิ์, การเปลี่ยนเส้นทาง และการทดสอบ A/B พร้อมตัวอย่างที่นำไปใช้ได้จริง

Next.js Middleware: การควบคุมการดักจับ Request อย่างเชี่ยวชาญสำหรับแอปพลิเคชันแบบไดนามิก

Next.js middleware เป็นวิธีที่ยืดหยุ่นและทรงพลังในการดักจับและแก้ไข request ที่เข้ามาก่อนที่จะไปถึง route ของคุณ ความสามารถนี้ช่วยให้คุณสามารถนำฟีเจอร์ต่างๆ มาใช้งานได้หลากหลาย ตั้งแต่การยืนยันตัวตนและการให้สิทธิ์ ไปจนถึงการเปลี่ยนเส้นทางและการทดสอบ A/B ทั้งหมดนี้ในขณะที่ยังคงประสิทธิภาพสูงสุด คู่มือฉบับสมบูรณ์นี้จะแนะนำแนวคิดหลักของ Next.js middleware และสาธิตวิธีการนำไปใช้งานอย่างมีประสิทธิภาพ

Next.js Middleware คืออะไร?

Middleware ใน Next.js คือฟังก์ชันที่ทำงานก่อนที่ request จะเสร็จสมบูรณ์ ซึ่งช่วยให้คุณสามารถ:

ฟังก์ชัน Middleware จะถูกกำหนดไว้ในไฟล์ middleware.ts (หรือ middleware.js) ที่รากของโปรเจกต์ของคุณ ฟังก์ชันเหล่านี้จะถูกเรียกใช้งานสำหรับทุก route ภายในแอปพลิเคชันของคุณ หรือสำหรับ route ที่ระบุตาม matcher ที่สามารถกำหนดค่าได้

แนวคิดหลักและประโยชน์

อ็อบเจกต์ Request

อ็อบเจกต์ request ให้การเข้าถึงข้อมูลเกี่ยวกับ request ที่เข้ามา ซึ่งรวมถึง:

อ็อบเจกต์ Response

ฟังก์ชัน Middleware จะคืนค่าอ็อบเจกต์ Response เพื่อควบคุมผลลัพธ์ของ request คุณสามารถใช้ response ต่อไปนี้ได้:

Matchers

Matchers ช่วยให้คุณสามารถระบุได้ว่า middleware ของคุณควรถูกนำไปใช้กับ route ใดบ้าง คุณสามารถกำหนด matchers โดยใช้นิพจน์ทั่วไป (regular expressions) หรือรูปแบบของ path ซึ่งจะช่วยให้แน่ใจว่า middleware ของคุณทำงานเฉพาะเมื่อจำเป็นเท่านั้น เป็นการปรับปรุงประสิทธิภาพและลดภาระงานที่ไม่จำเป็น

Edge Runtime

Next.js middleware ทำงานบน Edge Runtime ซึ่งเป็นสภาพแวดล้อมการทำงานของ JavaScript ที่มีน้ำหนักเบาและสามารถติดตั้งใช้งานใกล้กับผู้ใช้ของคุณได้ ความใกล้ชิดนี้ช่วยลดความหน่วงและปรับปรุงประสิทธิภาพโดยรวมของแอปพลิเคชันของคุณ โดยเฉพาะอย่างยิ่งสำหรับผู้ใช้ที่กระจายอยู่ทั่วโลก Edge Runtime มีให้บริการบน Edge Network ของ Vercel และแพลตฟอร์มอื่นๆ ที่เข้ากันได้ Edge Runtime มีข้อจำกัดบางประการ โดยเฉพาะการใช้ Node.js APIs

ตัวอย่างการใช้งานจริง: การนำฟีเจอร์ของ Middleware ไปใช้

1. การยืนยันตัวตน (Authentication)

Authentication middleware สามารถใช้เพื่อป้องกัน route ที่ต้องการให้ผู้ใช้ต้องเข้าสู่ระบบก่อน นี่คือตัวอย่างวิธีการใช้งานการยืนยันตัวตนโดยใช้ cookie:


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
 const token = request.cookies.get('auth_token');

 if (!token) {
 return NextResponse.redirect(new URL('/login', request.url))
 }

 return NextResponse.next()
}

export const config = {
 matcher: ['/dashboard/:path*'],
}

middleware นี้จะตรวจสอบการมีอยู่ของ cookie ที่ชื่อ auth_token หากไม่พบ cookie ผู้ใช้จะถูกเปลี่ยนเส้นทางไปยังหน้า /login ส่วน config.matcher จะระบุว่า middleware นี้ควรทำงานเฉพาะสำหรับ route ที่อยู่ภายใต้ /dashboard เท่านั้น

มุมมองระดับโลก (Global Perspective): ปรับเปลี่ยน logic การยืนยันตัวตนเพื่อรองรับวิธีการต่างๆ (เช่น OAuth, JWT) และผสานรวมกับผู้ให้บริการข้อมูลประจำตัวที่แตกต่างกัน (เช่น Google, Facebook, Azure AD) เพื่อตอบสนองผู้ใช้จากภูมิภาคที่หลากหลาย

2. การให้สิทธิ์ (Authorization)

Authorization middleware สามารถใช้เพื่อควบคุมการเข้าถึงทรัพยากรตามบทบาทหรือสิทธิ์ของผู้ใช้ ตัวอย่างเช่น คุณอาจมีแดชบอร์ดสำหรับผู้ดูแลระบบที่เฉพาะผู้ใช้บางคนเท่านั้นที่สามารถเข้าถึงได้


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
 const token = request.cookies.get('auth_token');

 if (!token) {
 return NextResponse.redirect(new URL('/login', request.url))
 }

 // ตัวอย่าง: ดึงบทบาทผู้ใช้จาก API (แทนที่ด้วย logic จริงของคุณ)
 const userResponse = await fetch('https://api.example.com/userinfo', {
 headers: {
 Authorization: `Bearer ${token}`,
 },
 });
 const userData = await userResponse.json();

 if (userData.role !== 'admin') {
 return NextResponse.redirect(new URL('/unauthorized', request.url))
 }

 return NextResponse.next()
}

export const config = {
 matcher: ['/admin/:path*'],
}

middleware นี้จะดึงบทบาทของผู้ใช้และตรวจสอบว่าพวกเขามีบทบาทเป็น admin หรือไม่ หากไม่ พวกเขาจะถูกเปลี่ยนเส้นทางไปยังหน้า /unauthorized ตัวอย่างนี้ใช้ API endpoint จำลอง โปรดแทนที่ `https://api.example.com/userinfo` ด้วย endpoint เซิร์ฟเวอร์การยืนยันตัวตนจริงของคุณ

มุมมองระดับโลก (Global Perspective): คำนึงถึงกฎระเบียบด้านความเป็นส่วนตัวของข้อมูล (เช่น GDPR, CCPA) เมื่อจัดการกับข้อมูลผู้ใช้ ควรใช้มาตรการรักษาความปลอดภัยที่เหมาะสมเพื่อปกป้องข้อมูลที่ละเอียดอ่อนและรับรองการปฏิบัติตามกฎหมายท้องถิ่น

3. การเปลี่ยนเส้นทาง (Redirection)

Redirection middleware สามารถใช้เพื่อเปลี่ยนเส้นทางผู้ใช้ตามตำแหน่งที่ตั้ง, ภาษา หรือเกณฑ์อื่นๆ ตัวอย่างเช่น คุณอาจเปลี่ยนเส้นทางผู้ใช้ไปยังเวอร์ชันของเว็บไซต์ที่แปลเป็นภาษาท้องถิ่นตามที่อยู่ IP ของพวกเขา


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
 const country = request.geo?.country || 'US'; // ตั้งค่าเริ่มต้นเป็น US หากการระบุตำแหน่งทางภูมิศาสตร์ล้มเหลว

 if (country === 'DE') {
 return NextResponse.redirect(new URL('/de', request.url))
 }

 if (country === 'FR') {
 return NextResponse.redirect(new URL('/fr', request.url))
 }

 return NextResponse.next()
}

export const config = {
 matcher: ['/'],
}

middleware นี้จะตรวจสอบประเทศของผู้ใช้ตามที่อยู่ IP และเปลี่ยนเส้นทางพวกเขาไปยังเวอร์ชันของเว็บไซต์ที่แปลเป็นภาษาท้องถิ่นที่เหมาะสม (/de สำหรับเยอรมนี, /fr สำหรับฝรั่งเศส) หากการระบุตำแหน่งทางภูมิศาสตร์ล้มเหลว จะใช้เวอร์ชันสหรัฐอเมริกาเป็นค่าเริ่มต้น โปรดทราบว่าฟีเจอร์นี้ต้องอาศัยคุณสมบัติ geo ที่พร้อมใช้งาน (เช่น เมื่อติดตั้งใช้งานบน Vercel)

มุมมองระดับโลก (Global Perspective): ตรวจสอบให้แน่ใจว่าเว็บไซต์ของคุณรองรับหลายภาษาและหลายสกุลเงิน ควรมีตัวเลือกให้ผู้ใช้สามารถเลือกภาษาหรือภูมิภาคที่ต้องการได้ด้วยตนเอง ใช้รูปแบบวันที่และเวลาที่เหมาะสมสำหรับแต่ละท้องถิ่น

4. การทดสอบ A/B (A/B Testing)

Middleware สามารถใช้เพื่อทำการทดสอบ A/B โดยการสุ่มแบ่งผู้ใช้ไปยังหน้าเว็บเวอร์ชันต่างๆ และติดตามพฤติกรรมของพวกเขา นี่คือตัวอย่างแบบง่าย:


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

function getRandomVariant() {
 return Math.random() < 0.5 ? 'A' : 'B';
}

export function middleware(request: NextRequest) {
 let variant = request.cookies.get('variant')?.value;

 if (!variant) {
 variant = getRandomVariant();
 const response = NextResponse.next();
 response.cookies.set('variant', variant);
 return response;
 }

 if (variant === 'B') {
 return NextResponse.rewrite(new URL('/variant-b', request.url));
 }

 return NextResponse.next();
}

export const config = {
 matcher: ['/'],
}

middleware นี้จะกำหนดให้ผู้ใช้อยู่ในกลุ่ม 'A' หรือ 'B' หากผู้ใช้ยังไม่มี cookie ชื่อ variant ระบบจะสุ่มและตั้งค่าให้ ผู้ใช้ที่ถูกกำหนดให้อยู่ในกลุ่ม 'B' จะถูกเขียน URL ใหม่ไปยังหน้า /variant-b จากนั้นคุณจะต้องติดตามประสิทธิภาพของแต่ละเวอร์ชันเพื่อตัดสินว่าเวอร์ชันใดมีประสิทธิภาพมากกว่ากัน

มุมมองระดับโลก (Global Perspective): พิจารณาความแตกต่างทางวัฒนธรรมเมื่อออกแบบการทดสอบ A/B สิ่งที่ได้ผลดีในภูมิภาคหนึ่งอาจไม่ได้รับการตอบรับที่ดีจากผู้ใช้ในอีกภูมิภาคหนึ่ง ตรวจสอบให้แน่ใจว่าแพลตฟอร์มการทดสอบ A/B ของคุณสอดคล้องกับกฎระเบียบด้านความเป็นส่วนตัวในภูมิภาคต่างๆ

5. Feature Flags

Feature flags ช่วยให้คุณสามารถเปิดหรือปิดฟีเจอร์ในแอปพลิเคชันของคุณได้โดยไม่ต้องติดตั้งโค้ดใหม่ Middleware สามารถใช้เพื่อตัดสินว่าผู้ใช้ควรจะเข้าถึงฟีเจอร์ที่เฉพาะเจาะจงได้หรือไม่ โดยพิจารณาจาก ID ผู้ใช้, ตำแหน่งที่ตั้ง หรือเกณฑ์อื่นๆ


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
 // ตัวอย่าง: ดึง feature flags จาก API
 const featureFlagsResponse = await fetch('https://api.example.com/featureflags', {
 headers: {
 'X-User-Id': 'user123',
 },
 });
 const featureFlags = await featureFlagsResponse.json();

 if (featureFlags.new_feature_enabled) {
 // เปิดใช้งานฟีเจอร์ใหม่
 return NextResponse.next();
 } else {
 // ปิดใช้งานฟีเจอร์ใหม่ (เช่น เปลี่ยนเส้นทางไปยังหน้าอื่น)
 return NextResponse.redirect(new URL('/alternative-page', request.url));
 }
}

export const config = {
 matcher: ['/new-feature'],
}

middleware นี้จะดึง feature flags จาก API และตรวจสอบว่า flag new_feature_enabled ถูกตั้งค่าไว้หรือไม่ หากใช่ ผู้ใช้จะสามารถเข้าถึงหน้า /new-feature ได้ มิฉะนั้นพวกเขาจะถูกเปลี่ยนเส้นทางไปยังหน้า /alternative-page

มุมมองระดับโลก (Global Perspective): ใช้ feature flags เพื่อทยอยเปิดตัวฟีเจอร์ใหม่ให้กับผู้ใช้ในภูมิภาคต่างๆ ซึ่งช่วยให้คุณสามารถตรวจสอบประสิทธิภาพและแก้ไขปัญหาใดๆ ก่อนที่จะปล่อยฟีเจอร์ให้กับผู้ใช้ในวงกว้างขึ้น นอกจากนี้ ตรวจสอบให้แน่ใจว่าระบบ feature flagging ของคุณสามารถขยายขนาดได้ทั่วโลกและให้ผลลัพธ์ที่สอดคล้องกันโดยไม่คำนึงถึงตำแหน่งของผู้ใช้ พิจารณาข้อจำกัดด้านกฎระเบียบของภูมิภาคสำหรับการเปิดตัวฟีเจอร์

เทคนิคขั้นสูง

การเชื่อมต่อ Middleware (Chaining Middleware)

คุณสามารถเชื่อมต่อฟังก์ชัน middleware หลายตัวเข้าด้วยกันเพื่อดำเนินการเป็นลำดับบน request เดียว วิธีนี้มีประโยชน์สำหรับการแบ่ง logic ที่ซับซ้อนออกเป็นโมดูลที่เล็กลงและจัดการได้ง่ายขึ้น


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
 const response = NextResponse.next();

 // ฟังก์ชัน middleware ตัวแรก
 const token = request.cookies.get('auth_token');
 if (!token) {
 return NextResponse.redirect(new URL('/login', request.url))
 }

 // ฟังก์ชัน middleware ตัวที่สอง
 response.headers.set('x-middleware-custom', 'value');

 return response;
}

export const config = {
 matcher: ['/dashboard/:path*'],
}

ตัวอย่างนี้แสดง middleware สองตัวในฟังก์ชันเดียว ตัวแรกทำหน้าที่ยืนยันตัวตนและตัวที่สองตั้งค่า header ที่กำหนดเอง

การใช้ตัวแปรสภาพแวดล้อม (Environment Variables)

จัดเก็บข้อมูลที่ละเอียดอ่อน เช่น API key และข้อมูลการเข้าถึงฐานข้อมูล ไว้ในตัวแปรสภาพแวดล้อมแทนที่จะเขียนลงในโค้ดของฟังก์ชัน middleware โดยตรง ซึ่งจะช่วยปรับปรุงความปลอดภัยและทำให้การจัดการการกำหนดค่าของแอปพลิเคชันของคุณง่ายขึ้น


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

const API_KEY = process.env.API_KEY;

export async function middleware(request: NextRequest) {
 const response = await fetch('https://api.example.com/data', {
 headers: {
 'X-API-Key': API_KEY,
 },
 });

 // ...
}

export const config = {
 matcher: ['/data'],
}

ในตัวอย่างนี้ API_KEY ถูกดึงมาจากตัวแปรสภาพแวดล้อม

การจัดการข้อผิดพลาด (Error Handling)

ควรมีการจัดการข้อผิดพลาดที่แข็งแกร่งในฟังก์ชัน middleware ของคุณเพื่อป้องกันไม่ให้ข้อผิดพลาดที่ไม่คาดคิดทำให้แอปพลิเคชันของคุณล่ม ใช้บล็อก try...catch เพื่อดักจับ exception และบันทึกข้อผิดพลาดอย่างเหมาะสม


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
 try {
 const response = await fetch('https://api.example.com/data');
 // ...
 } catch (error) {
 console.error('Error fetching data:', error);
 return NextResponse.error(); // หรือเปลี่ยนเส้นทางไปยังหน้าข้อผิดพลาด
 }
}

export const config = {
 matcher: ['/data'],
}

แนวทางปฏิบัติที่ดีที่สุด (Best Practices)

การแก้ไขปัญหาที่พบบ่อย

สรุป

Next.js middleware เป็นเครื่องมือที่ทรงพลังสำหรับการสร้างเว็บแอปพลิเคชันแบบไดนามิกและเป็นส่วนตัว ด้วยการควบคุมการดักจับ request อย่างเชี่ยวชาญ คุณสามารถนำฟีเจอร์ต่างๆ มาใช้งานได้หลากหลาย ตั้งแต่การยืนยันตัวตนและการให้สิทธิ์ ไปจนถึงการเปลี่ยนเส้นทางและการทดสอบ A/B การปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดที่ระบุไว้ในคู่มือนี้ จะช่วยให้คุณสามารถใช้ Next.js middleware เพื่อสร้างแอปพลิเคชันที่มีประสิทธิภาพสูง ปลอดภัย และขยายขนาดได้ ซึ่งตอบสนองความต้องการของผู้ใช้ทั่วโลกของคุณ เปิดรับพลังของ middleware เพื่อปลดล็อกความเป็นไปได้ใหม่ๆ ในโปรเจกต์ Next.js ของคุณ และมอบประสบการณ์ผู้ใช้ที่ยอดเยี่ยม